diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bb79e8453..620bffb04f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: uses: ./.github/actions/env - run: > earthly - --allow-privileged --auto-skip + --allow-privileged --secret SPEAKEASY_API_KEY=$SPEAKEASY_API_KEY ${{ contains(github.event.pull_request.labels.*.name, 'no-cache') && '--no-cache' || '' }} +pre-commit @@ -52,7 +52,7 @@ jobs: uses: ./.github/actions/env - run: > earthly - --no-output --auto-skip + --no-output --allow-privileged --secret SPEAKEASY_API_KEY=$SPEAKEASY_API_KEY ${{ contains(github.event.pull_request.labels.*.name, 'no-cache') && '--no-cache' || '' }} @@ -72,7 +72,7 @@ jobs: uses: ./.github/actions/env - run: > earthly - --no-output --auto-skip + --no-output --allow-privileged --secret SPEAKEASY_API_KEY=$SPEAKEASY_API_KEY ${{ contains(github.event.pull_request.labels.*.name, 'no-cache') && '--no-cache' || '' }} diff --git a/Earthfile b/Earthfile index b10eca7eed..f982743ea7 100644 --- a/Earthfile +++ b/Earthfile @@ -2,6 +2,14 @@ VERSION 0.8 PROJECT FormanceHQ/stack IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core +IMPORT github.com/formancehq/ledger:main AS ledger +IMPORT github.com/formancehq/payments:main AS payments +IMPORT github.com/formancehq/gateway:main AS gateway +IMPORT github.com/formancehq/auth:main AS auth +IMPORT github.com/formancehq/search:main AS search +IMPORT github.com/formancehq/stargate:main AS stargate +IMPORT github.com/formancehq/webhooks:main AS webhooks + sources: FROM core+base-image @@ -22,10 +30,14 @@ build-final-spec: COPY releases/openapi-merge.json . RUN mkdir ./build - FOR c IN payments ledger - COPY (./components/$c+openapi/openapi.yaml) /src/components/$c/ - END - FOR c IN auth webhooks search wallets reconciliation orchestration gateway + COPY (ledger+openapi/openapi.yaml) /src/components/ledger/ + COPY (payments+openapi/openapi.yaml) /src/components/payments/ + COPY (gateway+openapi/openapi.yaml) /src/ee/gateway/ + COPY (auth+openapi/openapi.yaml) /src/ee/auth/ + COPY (search+openapi/openapi.yaml) /src/ee/search/ + COPY (webhooks+openapi/openapi.yaml) /src/ee/webhooks/ + + FOR c IN wallets reconciliation orchestration COPY (./ee/$c+openapi/openapi.yaml) /src/ee/$c/ END diff --git a/components/Earthfile b/components/Earthfile index a16ae5036b..2521e19ba1 100644 --- a/components/Earthfile +++ b/components/Earthfile @@ -16,6 +16,5 @@ deploy-staging: run: LOCALLY ARG --required TARGET - BUILD ./ledger+$TARGET BUILD ./operator+$TARGET - BUILD ./payments+$TARGET \ No newline at end of file + \ No newline at end of file diff --git a/components/ledger/.all-contributorsrc b/components/ledger/.all-contributorsrc deleted file mode 100644 index 255ca97e8c..0000000000 --- a/components/ledger/.all-contributorsrc +++ /dev/null @@ -1,136 +0,0 @@ -{ - "projectName": "ledger", - "projectOwner": "formancehq", - "repoType": "github", - "repoHost": "https://github.com", - "files": [ - "README.md" - ], - "imageSize": 100, - "commit": true, - "commitConvention": "none", - "contributors": [ - { - "login": "Azorlogh", - "name": "Alix Bott", - "avatar_url": "https://avatars.githubusercontent.com/u/17968319?v=4", - "profile": "https://github.com/Azorlogh", - "contributions": [ - "code" - ] - }, - { - "login": "flemzord", - "name": "Maxence Maireaux", - "avatar_url": "https://avatars.githubusercontent.com/u/1952914?v=4", - "profile": "https://www.flemzord.fr/", - "contributions": [ - "infra", - "platform", - "code" - ] - }, - { - "login": "henry-jackson", - "name": "Henry Jackson", - "avatar_url": "https://avatars.githubusercontent.com/u/34102861?v=4", - "profile": "https://github.com/henry-jackson", - "contributions": [ - "code" - ] - }, - { - "login": "matiasinsaurralde", - "name": "Matias Insaurralde", - "avatar_url": "https://avatars.githubusercontent.com/u/20110?v=4", - "profile": "https://matias.insaurral.de/", - "contributions": [ - "code", - "review" - ] - }, - { - "login": "S0c5", - "name": "David barinas", - "avatar_url": "https://avatars.githubusercontent.com/u/5241972?v=4", - "profile": "https://github.com/S0c5", - "contributions": [ - "code" - ] - }, - { - "login": "djimnz", - "name": "David Jimenez", - "avatar_url": "https://avatars.githubusercontent.com/u/949997?v=4", - "profile": "https://github.com/djimnz", - "contributions": [ - "code" - ] - }, - { - "login": "altitude", - "name": "Clément Salaün", - "avatar_url": "https://avatars.githubusercontent.com/u/1770991?v=4", - "profile": "http://32b6.com/", - "contributions": [ - "ideas" - ] - }, - { - "login": "karmanyaahm", - "name": "Karmanyaah Malhotra", - "avatar_url": "https://avatars.githubusercontent.com/u/32671690?v=4", - "profile": "https://karmanyaah.malhotra.cc/", - "contributions": [ - "userTesting" - ] - }, - { - "login": "antoinegelloz", - "name": "Antoine Gelloz", - "avatar_url": "https://avatars.githubusercontent.com/u/42968436?v=4", - "profile": "https://www.linkedin.com/in/antoinegelloz/", - "contributions": [ - "code" - ] - }, - { - "login": "jdupas22", - "name": "jdupas22", - "avatar_url": "https://avatars.githubusercontent.com/u/106673437?v=4", - "profile": "https://github.com/jdupas22", - "contributions": [ - "code" - ] - }, - { - "login": "edwardmp", - "name": "Edward Poot", - "avatar_url": "https://avatars.githubusercontent.com/u/1686739?v=4", - "profile": "https://edwardpoot.com", - "contributions": [ - "code" - ] - }, - { - "login": "nicoabie", - "name": "Nico Gallinal", - "avatar_url": "https://avatars.githubusercontent.com/u/2797992?v=4", - "profile": "https://github.com/nicoabie", - "contributions": [ - "bug" - ] - }, - { - "login": "gfyrag", - "name": "Ragot Geoffrey", - "avatar_url": "https://avatars.githubusercontent.com/u/9094799?v=4", - "profile": "https://github.com/gfyrag", - "contributions": [ - "code" - ] - } - ], - "contributorsPerLine": 7, - "skipCi": true -} diff --git a/components/ledger/.dockerignore b/components/ledger/.dockerignore deleted file mode 100644 index 25a7044944..0000000000 --- a/components/ledger/.dockerignore +++ /dev/null @@ -1,14 +0,0 @@ -vendor -sdk -.git -.devbox -.direnv -.github -.moon -sdks -config -docs -nix -openapi -tests -worktrees diff --git a/components/ledger/.github/CODEOWNERS b/components/ledger/.github/CODEOWNERS deleted file mode 100644 index c381fc0f50..0000000000 --- a/components/ledger/.github/CODEOWNERS +++ /dev/null @@ -1,9 +0,0 @@ -/api/ @formancehq/dev-backend -/cmd/ @formancehq/dev-backend -/config/ @formancehq/dev-backend -/core/ @formancehq/dev-backend -/ledger/ @formancehq/dev-backend -*.go @formancehq/dev-backend - -/.devcontainer @flemzord -/.github @flemzord diff --git a/components/ledger/.github/FUNDING.yml b/components/ledger/.github/FUNDING.yml deleted file mode 100644 index f90bcb15ae..0000000000 --- a/components/ledger/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: FormanceHQ diff --git a/components/ledger/.github/ISSUE_TEMPLATE/bug_report.md b/components/ledger/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index f7757cdf96..0000000000 --- a/components/ledger/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Logs** -If applicable, add logs to help explain your problem. - -**Environment (please complete the following information):** - - OS: [e.g. ubuntu 20.04] - - Numary Version [e.g. 1.0.0-beta.4] - -**Additional context** -Add any other context about the problem here. diff --git a/components/ledger/.github/ISSUE_TEMPLATE/feature_request.md b/components/ledger/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b36319bac5..0000000000 --- a/components/ledger/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement, rfc -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Summary** - -**Solution proposal** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_sdk_template.md b/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_sdk_template.md deleted file mode 100644 index 3f9f8c2a96..0000000000 --- a/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_sdk_template.md +++ /dev/null @@ -1,8 +0,0 @@ -# SDK : OPEN API GENERATOR NAME - -__Please add a description to this PR.__ - -## How to deploy - -__Please provide steps to deploy the generated sdk to official channels.__ -__Please be as exhaustive as possible__ diff --git a/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 3d9bbad90e..0000000000 --- a/components/ledger/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,22 +0,0 @@ -# Title - -__Please add a description to this PR.__ - -## Type of change - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Refactoring / Technical debt - -## What parts of the code are impacted ? -__Please describe the impacted parts of the code.__ - -# Checklist: - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes diff --git a/components/ledger/.github/dependabot.yml b/components/ledger/.github/dependabot.yml deleted file mode 100644 index cd51d2c60c..0000000000 --- a/components/ledger/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - # Check for updates to GitHub Actions every weekday - interval: "daily" diff --git a/components/ledger/.github/labeler.yml b/components/ledger/.github/labeler.yml deleted file mode 100644 index 66ae675cd2..0000000000 --- a/components/ledger/.github/labeler.yml +++ /dev/null @@ -1,23 +0,0 @@ -'@domain/core': - - core/* - - core/**/* - -'@domain/api': - - api/* - - api/**/* - -'@domain/ledger': - - ledger/* - - ledger/**/* - -'@domain/storage': - - storage/* - - storage/**/* - -'@domain/cmd': - - cmd/* - - cmd/**/* - -'@domain/ci': - - .github/* - - .github/**/* diff --git a/components/ledger/.github/workflows/main.yml b/components/ledger/.github/workflows/main.yml deleted file mode 100644 index 74b43540b6..0000000000 --- a/components/ledger/.github/workflows/main.yml +++ /dev/null @@ -1,33 +0,0 @@ -on: - push: - branches: - - main - -name: Main -jobs: - Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Install Task - uses: arduino/setup-task@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/setup-go@v4 - with: - go-version-file: go.mod - cache-dependency-path: go.sum - cache: true - - run: task tests - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 - with: - name: 'Ledger' - fail_ci_if_error: false # optional (default = false) - verbose: true # optional (default = false) diff --git a/components/ledger/.github/workflows/release.yml b/components/ledger/.github/workflows/release.yml deleted file mode 100644 index 42c1bd2a69..0000000000 --- a/components/ledger/.github/workflows/release.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Ledger - Release -on: - push: - tags: - - 'v*.*.*' - -jobs: - Tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Install Task - uses: arduino/setup-task@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/setup-go@v4 - with: - go-version-file: go.mod - cache-dependency-path: go.sum - cache: true - - run: task tests - - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v3 - with: - name: 'Ledger' - fail_ci_if_error: false # optional (default = false) - verbose: true # optional (default = false) - - GoReleaser: - name: GoReleaser - runs-on: ubuntu-latest - env: - DOCKER_CLI_EXPERIMENTAL: "enabled" - needs: - - Tests - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v4 - with: - go-version-file: 'go.mod' - cache: true - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: "NumaryBot" - password: ${{ secrets.NUMARY_GITHUB_TOKEN }} - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 - with: - distribution: goreleaser-pro - version: latest - install-only: true - - run: goreleaser release --clean -f .goreleaser.ledger.yml - env: - GITHUB_TOKEN: ${{ secrets.NUMARY_GITHUB_TOKEN }} - FURY_TOKEN: ${{ secrets.FURY_TOKEN }} - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file diff --git a/components/ledger/.gitignore b/components/ledger/.gitignore deleted file mode 100644 index ae122c5da3..0000000000 --- a/components/ledger/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -coverage* -/dist/ -cmd/control/* -!cmd/control/gitkeep -.DS_Store -.idea -vendor -sdk/swagger.yaml -sdk/swagger.yaml-e -sdk/sdks -.vscode -.env -sqlstorage.test -ledger.test -antlr-*-complete.jar -go.work -go.work.sum -benchs diff --git a/components/ledger/.gitrepo b/components/ledger/.gitrepo deleted file mode 100644 index 7b7ba7e66c..0000000000 --- a/components/ledger/.gitrepo +++ /dev/null @@ -1,12 +0,0 @@ -; DO NOT EDIT (unless you know what you are doing) -; -; This subdirectory is a git "subrepo", and this file is maintained by the -; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme -; -[subrepo] - remote = ledger - branch = main - commit = b2cc1d284edad80e8ecb5faa83ed0c64aa57e0ba - method = merge - cmdver = 0.4.6 - parent = ce1a99ea700f99e23777eddd863e0155537a4c48 diff --git a/components/ledger/.goreleaser.ledger.yml b/components/ledger/.goreleaser.ledger.yml deleted file mode 100644 index 54fa95176d..0000000000 --- a/components/ledger/.goreleaser.ledger.yml +++ /dev/null @@ -1,147 +0,0 @@ -project_name: ledger -builds: - - binary: ledger - id: ledger - ldflags: - - -X github.com/formancehq/ledger/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/ledger/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/ledger/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - goarch: - - amd64 - - arm64 - -archives: - - id: "ledger" - builds: - - ledger - format: tar.gz - format_overrides: - - goos: windows - format: zip - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - -nfpms: - - id: packages - package_name: ledger - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - builds: - - ledger - homepage: https://formance.com - maintainer: Maxence Maireaux - formats: - - deb - - rpm - -publishers: - - name: fury.io - ids: - - packages - dir: "{{ dir .ArtifactPath }}" - cmd: curl --http1.1 -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/ledger/ - -brews: - - repository: - owner: formancehq - name: homebrew-tap - name: ledger - folder: Formula - homepage: https://formance.com - skip_upload: auto - test: | - system "#{bin}/ledger version" - install: | - bin.install "ledger" - -nightly: - name_template: '{{ .FullCommit }}' - publish_release: false - -checksum: - name_template: '{{.ProjectName}}_checksums.txt' - -snapshot: - name_template: "{{ .Version }}" - -changelog: - sort: asc - use: github - filters: - exclude: - - '^docs:' - - '^test:' - - '^spec:' - - Merge pull request - - Merge remote-tracking branch - - Merge branch - - go mod tidy - groups: - - title: 'New Features' - regexp: "^.*feat[(\\w)]*:+.*$" - order: 0 - - title: 'Bug fixes' - regexp: "^.*fix[(\\w)]*:+.*$" - order: 10 - - title: Other work - order: 999 - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) - - - -dockers: - - image_templates: ["ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-amd64"] - goarch: amd64 - dockerfile: build.Dockerfile - use: buildx - build_flag_templates: - - --platform=linux/amd64 - - --label=org.opencontainers.image.title={{ .ProjectName }} - - --label=org.opencontainers.image.description={{ .ProjectName }} - - --label=org.opencontainers.image.url=https://github.com/formancehq/stack - - --label=org.opencontainers.image.source=https://github.com/formancehq/stack - - --label=org.opencontainers.image.version={{ .Version }} - - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} - - --label=org.opencontainers.image.revision={{ .FullCommit }} - - --label=org.opencontainers.image.licenses=MIT - - image_templates: [ "ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-arm64" ] - goarch: arm64 - dockerfile: build.Dockerfile - use: buildx - build_flag_templates: - - --platform=linux/arm64/v8 - - --label=org.opencontainers.image.title={{ .ProjectName }} - - --label=org.opencontainers.image.description={{ .ProjectName }} - - --label=org.opencontainers.image.url=https://github.com/formancehq/stack - - --label=org.opencontainers.image.source=https://github.com/formancehq/stack - - --label=org.opencontainers.image.version={{ .Version }} - - --label=org.opencontainers.image.created={{ time "2006-01-02T15:04:05Z07:00" }} - - --label=org.opencontainers.image.revision={{ .FullCommit }} - - --label=org.opencontainers.image.licenses=MIT - -docker_manifests: - - name_template: 'ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}' - image_templates: - - 'ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-amd64' - - 'ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-arm64' - - name_template: '{{ if not .IsNightly }}ghcr.io/formancehq/{{ .ProjectName }}:latest{{ end }}' - image_templates: - - 'ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-amd64' - - 'ghcr.io/formancehq/{{ .ProjectName }}:{{ if not .IsNightly }}v{{ end }}{{ .Version }}-arm64' diff --git a/components/ledger/.goreleaser.yml b/components/ledger/.goreleaser.yml deleted file mode 100644 index a10afbcbc9..0000000000 --- a/components/ledger/.goreleaser.yml +++ /dev/null @@ -1,57 +0,0 @@ -project_name: ledger -monorepo: - tag_prefix: v - dir: ./ - -includes: - - from_file: - path: ./../../.goreleaser.default.yaml - -builds: - - binary: ledger - id: ledger - ldflags: - - -X github.com/formancehq/ledger/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/ledger/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/ledger/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) - -archives: - - id: "{{.ProjectName}}" - builds: - - gateway - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - -nfpms: - - id: packages - package_name: ledger - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - builds: - - ledger - homepage: https://formance.com - maintainer: Maxence Maireaux - formats: - - deb - - rpm - -publishers: - - name: fury.io - ids: - - packages - dir: "{{ dir .ArtifactPath }}" - cmd: curl --http1.1 -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/ledger/ diff --git a/components/ledger/Earthfile b/components/ledger/Earthfile deleted file mode 100644 index 4a629183f4..0000000000 --- a/components/ledger/Earthfile +++ /dev/null @@ -1,193 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.16.0 AS core -IMPORT ../../releases AS releases -IMPORT ../.. AS stack -IMPORT .. AS components - -FROM core+base-image - -sources: - WORKDIR /src/components/ledger - COPY go.mod go.sum . - COPY --dir internal pkg cmd . - COPY main.go . - SAVE ARTIFACT /src - -generate: - FROM core+builder-image - RUN apk update && apk add openjdk11 - DO --pass-args core+GO_INSTALL --package=go.uber.org/mock/mockgen@latest - COPY (+sources/*) /src - WORKDIR /src/components/ledger - DO --pass-args core+GO_GENERATE - SAVE ARTIFACT internal AS LOCAL internal - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT cmd AS LOCAL cmd - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/components/ledger - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/ledger"] - CMD ["serve"] - COPY --pass-args (+compile/main) /bin/ledger - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO --pass-args core+SAVE_IMAGE --COMPONENT=ledger --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - RUN go install github.com/onsi/ginkgo/v2/ginkgo@latest - - COPY (+sources/*) /src - WORKDIR /src/components/ledger - COPY --dir --pass-args (+generate/*) . - COPY --dir test . - - ARG includeIntegrationTests="true" - ARG coverage="" - ARG debug=false - - ENV DEBUG=$debug - ENV CGO_ENABLED=1 # required for -race - RUN apk add gcc musl-dev - - LET goFlags="-race" - IF [ "$coverage" = "true" ] - SET goFlags="$goFlags -covermode=atomic" - SET goFlags="$goFlags -coverpkg=github.com/formancehq/stack/components/ledger/internal/..." - SET goFlags="$goFlags,github.com/formancehq/stack/components/ledger/cmd/..." - SET goFlags="$goFlags -coverprofile cover.out" - END - IF [ "$includeIntegrationTests" = "true" ] - SET goFlags="$goFlags -tags it" - WITH DOCKER \ - --pull=postgres:15-alpine \ - --pull=clickhouse/clickhouse-server:head \ - --pull=elasticsearch:8.14.3 - RUN --mount type=cache,id=gopkgcache,target=${GOPATH}/pkg/mod \ - --mount type=cache,id=gobuildcache,target=/root/.cache/go-build \ - ginkgo -r -p $goFlags - END - ELSE - RUN --mount type=cache,id=gopkgcache,target=${GOPATH}/pkg/mod \ - --mount type=cache,id=gobuildcache,target=/root/.cache/go-build \ - ginkgo -r -p $goFlags - END - IF [ "$coverage" = "true" ] - # exclude files suffixed with _generated.go, these are mocks used by tests - RUN cat cover.out | grep -v "_generated.go" > cover2.out - RUN mv cover2.out cover.out - SAVE ARTIFACT cover.out AS LOCAL cover.out - END - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"ledger\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=ledger - -lint: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/components/ledger - COPY --pass-args +tidy/go.* . - COPY --dir test . - DO --pass-args stack+GO_LINT --ADDITIONAL_ARGUMENTS="--build-tags it" - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT internal AS LOCAL internal - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT test AS LOCAL test - SAVE ARTIFACT main.go AS LOCAL main.go - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - WAIT - BUILD +openapi - END - BUILD +generate-client - -bench: - FROM core+builder-image - DO --pass-args core+GO_INSTALL --package=golang.org/x/perf/cmd/benchstat@latest - COPY (+sources/*) /src - WORKDIR /src/components/ledger - COPY --dir test . - WORKDIR /src/components/ledger/test/performance - - ARG benchTime=1s - ARG count=1 - ARG GOPROXY - ARG testTimeout=10m - ARG bench=. - ARG verbose=0 - ARG GOMAXPROCS=2 - ARG GOMEMLIMIT=1024MiB - LET additionalArgs="" - IF [ "$verbose" = "1" ] - SET additionalArgs=-v - END - WITH DOCKER --pull postgres:15-alpine - RUN --mount type=cache,id=gopkgcache,target=${GOPATH}/pkg/mod \ - --mount type=cache,id=gobuild,target=/root/.cache/go-build \ - go test -timeout $testTimeout -bench=$bench -run ^$ -tags it $additionalArgs \ - -benchtime=$benchTime | tee -a /results.txt - END - RUN benchstat /results.txt - SAVE ARTIFACT /results.txt - -benchstat: - FROM core+builder-image - DO --pass-args core+GO_INSTALL --package=golang.org/x/perf/cmd/benchstat@latest - ARG compareAgainstRevision=main - COPY --pass-args github.com/formancehq/stack/components/ledger:$compareAgainstRevision+bench/results.txt /tmp/main.txt - COPY --pass-args +bench/results.txt /tmp/branch.txt - RUN --no-cache benchstat /tmp/main.txt /tmp/branch.txt - -openapi: - FROM node:20-alpine - RUN apk update && apk add yq - RUN npm install -g openapi-merge-cli - WORKDIR /src/components/ledger - COPY --dir openapi openapi - RUN openapi-merge-cli --config ./openapi/openapi-merge.json - RUN yq -oy ./openapi.json > openapi.yaml - SAVE ARTIFACT ./openapi.yaml AS LOCAL ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/components/ledger - COPY --dir test . - DO --pass-args stack+GO_TIDY - -release: - BUILD --pass-args stack+goreleaser --path=components/ledger - -generate-client: - FROM node:20-alpine - RUN apk update && apk add yq jq - WORKDIR /src - COPY (core+sources-speakeasy/speakeasy) /bin/speakeasy - COPY (+openapi/openapi.yaml) openapi.yaml - RUN cat ./openapi.yaml | yq e -o json > openapi.json - COPY (releases+sources/src/openapi-overlay.json) openapi-overlay.json - RUN jq -s '.[0] * .[1]' openapi.json openapi-overlay.json > final.json - COPY --dir pkg/client client - RUN --secret SPEAKEASY_API_KEY speakeasy generate sdk -s ./final.json -o ./client -l go - SAVE ARTIFACT client AS LOCAL ./pkg/client diff --git a/components/ledger/LICENSE b/components/ledger/LICENSE deleted file mode 100644 index 90bf910c22..0000000000 --- a/components/ledger/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Formance, Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/components/ledger/README.md b/components/ledger/README.md deleted file mode 100644 index a51d3305d6..0000000000 --- a/components/ledger/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Formance Ledger - -Formance Ledger (fka numary) is a programmable financial ledger that provides a foundation for money-moving applications. The ledger provides atomic multi-postings transactions and is programmable in [Numscript](doc:machine-instructions), a built-in language dedicated to money movements. It can be used either as a standalone micro-service or as part of the greater Formance Stack, and will shine for apps that require a lot of custom, money-moving code, e.g: - -* E-commerce with complex payments flows, payments splitting, such as marketplaces -* Company-issued currencies systems, e.g. Twitch Bits -* In-game currencies, inventories and trading systems, e.g. Fortnite V-Bucks -* Payment gateways using non-standard assets, e.g. learning credits -* Local currencies and complementary finance - -# Getting started - -Formance Ledger works as a standalone binary, the latest of which can be downloaded from the [releases page](https://github.com/formancehq/ledger/releases). You can move the binary to any executable path, such as to `/usr/local/bin`. Installations using brew, apt, yum or docker are also [available](https://docs.formance.com/docs/installation-1). - -```SHELL - -ledger server start - -# Submit a first transaction -echo " -send [USD/2 599] ( - source = @world - destination = @payments:001 -) - -send [USD/2 599] ( - source = @payments:001 - destination = @rides:0234 -) - -send [USD/2 599] ( - source = @rides:0234 - destination = { - 85/100 to @drivers:042 - 15/100 to @platform:fees - } -) -" > example.num - -ledger exec quickstart example.num - -# Get the balances of drivers:042 -curl -X GET http://localhost:3068/quickstart/accounts/drivers:042 - -# List transactions -curl -X GET http://localhost:3068/quickstart/transactions -``` - -# Documentation - -You can find the complete Numary documentation at [docs.formance.com](https://docs.formance.com) - -# Community - -If you need help, want to show us what you built or just hang out and chat about ledgers you are more than welcome on our [Slack](https://bit.ly/formance-slack) - looking forward to see you there! diff --git a/components/ledger/build.Dockerfile b/components/ledger/build.Dockerfile deleted file mode 100644 index 66a786a6ff..0000000000 --- a/components/ledger/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY ledger /usr/bin/ledger -ENV OTEL_SERVICE_NAME ledger -ENTRYPOINT ["/usr/bin/ledger"] -CMD ["serve"] diff --git a/components/ledger/cmd/buckets.go b/components/ledger/cmd/buckets.go deleted file mode 100644 index d9ec9107a4..0000000000 --- a/components/ledger/cmd/buckets.go +++ /dev/null @@ -1,70 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/spf13/cobra" -) - -func NewBucket() *cobra.Command { - return &cobra.Command{ - Use: "buckets", - Aliases: []string{"storage"}, - } -} - -func NewBucketUpgrade() *cobra.Command { - cmd := &cobra.Command{ - Use: "upgrade", - Args: cobra.ExactArgs(1), - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return err - } - - driver := driver.New(*connectionOptions) - if err := driver.Initialize(cmd.Context()); err != nil { - return err - } - defer func() { - _ = driver.Close() - }() - - name := args[0] - - bucket, err := driver.OpenBucket(cmd.Context(), name) - if err != nil { - return err - } - - logger := logging.NewDefaultLogger(cmd.OutOrStdout(), service.IsDebug(cmd), false) - - return bucket.Migrate(logging.ContextWithLogger(cmd.Context(), logger)) - }, - } - return cmd -} - -func upgradeAll(cmd *cobra.Command, _ []string) error { - logger := logging.NewDefaultLogger(cmd.OutOrStdout(), service.IsDebug(cmd), false) - ctx := logging.ContextWithLogger(cmd.Context(), logger) - - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return err - } - - driver := driver.New(*connectionOptions) - if err := driver.Initialize(ctx); err != nil { - return err - } - defer func() { - _ = driver.Close() - }() - - return driver.UpgradeAllBuckets(ctx) -} diff --git a/components/ledger/cmd/container.go b/components/ledger/cmd/container.go deleted file mode 100644 index 7482b704ee..0000000000 --- a/components/ledger/cmd/container.go +++ /dev/null @@ -1,39 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/ledger/internal/engine" - driver "github.com/formancehq/ledger/internal/storage/driver" - "github.com/spf13/cobra" - "go.uber.org/fx" -) - -const ServiceName = "ledger" - -func resolveOptions(cmd *cobra.Command, userOptions ...fx.Option) []fx.Option { - options := make([]fx.Option, 0) - options = append(options, fx.NopLogger) - - numscriptCacheMaxCountFlag, _ := cmd.Flags().GetInt(NumscriptCacheMaxCountFlag) - ledgerBatchSizeFlag, _ := cmd.Flags().GetInt(ledgerBatchSizeFlag) - - options = append(options, - publish.FXModuleFromFlags(cmd, service.IsDebug(cmd)), - otlptraces.FXModuleFromFlags(cmd), - otlpmetrics.FXModuleFromFlags(cmd), - auth.FXModuleFromFlags(cmd), - driver.FXModuleFromFlags(cmd), - engine.Module(engine.Configuration{ - NumscriptCache: engine.NumscriptCacheConfiguration{ - MaxCount: numscriptCacheMaxCountFlag, - }, - LedgerBatchSize: ledgerBatchSizeFlag, - }), - ) - - return append(options, userOptions...) -} diff --git a/components/ledger/cmd/doc.go b/components/ledger/cmd/doc.go deleted file mode 100644 index 9579552981..0000000000 --- a/components/ledger/cmd/doc.go +++ /dev/null @@ -1,62 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - "sort" - "strings" - "text/tabwriter" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -func NewDocCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "doc", - } - cmd.AddCommand(NewDocFlagCommand()) - return cmd -} - -func NewDocFlagCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "flags", - Run: func(cmd *cobra.Command, args []string) { - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug) - defer func(w *tabwriter.Writer) { - if err := w.Flush(); err != nil { - panic(err) - } - }(w) - - allKeys := make([]string, 0) - cmd.Flags().VisitAll(func(f *pflag.Flag) { - allKeys = append(allKeys, f.Name) - }) - sort.Strings(allKeys) - - if _, err := fmt.Fprintf(w, - "\tFlag\tEnv var\tDefault value\tDescription\t\r\n"); err != nil { - panic(err) - } - if _, err := fmt.Fprintf(w, - "\t-\t-\t-\t-\t\r\n"); err != nil { - panic(err) - } - for _, key := range allKeys { - asEnvVar := strings.ToUpper(strings.Replace(key, "-", "_", -1)) - flag := cmd.Parent().Parent().PersistentFlags().Lookup(key) - if flag == nil { - continue - } - if _, err := fmt.Fprintf(w, - "\t--%s\t%s\t%s\t%s\t\r\n", key, asEnvVar, flag.DefValue, flag.Usage); err != nil { - panic(err) - } - } - }, - } - return cmd -} diff --git a/components/ledger/cmd/root.go b/components/ledger/cmd/root.go deleted file mode 100644 index 43103a632c..0000000000 --- a/components/ledger/cmd/root.go +++ /dev/null @@ -1,69 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/bun/bunmigrate" - "github.com/formancehq/go-libs/service" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/ledger/internal/storage/systemstore" - "github.com/spf13/cobra" -) - -const ( - BindFlag = "bind" -) - -var ( - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func NewRootCommand() *cobra.Command { - root := &cobra.Command{ - Use: "ledger", - Short: "ledger", - DisableAutoGenTag: true, - Version: Version, - } - - serve := NewServe() - version := NewVersion() - - buckets := NewBucket() - buckets.AddCommand(NewBucketUpgrade()) - - root.AddCommand(serve) - root.AddCommand(buckets) - root.AddCommand(version) - root.AddCommand(bunmigrate.NewDefaultCommand(func(cmd *cobra.Command, args []string, db *bun.DB) error { - return upgradeAll(cmd, args) - })) - - root.AddCommand(NewDocCommand()) - - root.PersistentFlags().String(BindFlag, "0.0.0.0:3068", "API bind address") - - service.AddFlags(root.PersistentFlags()) - otlpmetrics.AddFlags(root.PersistentFlags()) - otlptraces.AddFlags(root.PersistentFlags()) - auth.AddFlags(root.PersistentFlags()) - publish.AddFlags(ServiceName, root.PersistentFlags(), func(cd *publish.ConfigDefault) { - cd.PublisherCircuitBreakerSchema = systemstore.Schema - }) - bunconnect.AddFlags(root.PersistentFlags()) - iam.AddFlags(root.PersistentFlags()) - - return root -} - -func Execute() { - service.Execute(NewRootCommand()) -} diff --git a/components/ledger/cmd/serve.go b/components/ledger/cmd/serve.go deleted file mode 100644 index 39f0210b9c..0000000000 --- a/components/ledger/cmd/serve.go +++ /dev/null @@ -1,94 +0,0 @@ -package cmd - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/ledger/internal/storage/driver" - - "github.com/formancehq/ledger/internal/api" - - "github.com/formancehq/go-libs/ballast" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/service" - "github.com/spf13/cobra" - "go.uber.org/fx" -) - -const ( - BallastSizeInBytesFlag = "ballast-size" - NumscriptCacheMaxCountFlag = "numscript-cache-max-count" - ledgerBatchSizeFlag = "ledger-batch-size" - ReadOnlyFlag = "read-only" - AutoUpgradeFlag = "auto-upgrade" -) - -func NewServe() *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - RunE: func(cmd *cobra.Command, args []string) error { - readOnly, _ := cmd.Flags().GetBool(ReadOnlyFlag) - autoUpgrade, _ := cmd.Flags().GetBool(AutoUpgradeFlag) - ballastSize, _ := cmd.Flags().GetUint(BallastSizeInBytesFlag) - bind, _ := cmd.Flags().GetString(BindFlag) - - return service.New(cmd.OutOrStdout(), resolveOptions( - cmd, - ballast.Module(ballastSize), - api.Module(api.Config{ - Version: Version, - ReadOnly: readOnly, - Debug: service.IsDebug(cmd), - }), - fx.Invoke(func(lc fx.Lifecycle, driver *driver.Driver) { - if autoUpgrade { - lc.Append(fx.Hook{ - OnStart: driver.UpgradeAllBuckets, - }) - } - }), - fx.Invoke(func(lc fx.Lifecycle, h chi.Router, logger logging.Logger) { - - wrappedRouter := chi.NewRouter() - wrappedRouter.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(logging.ContextWithLogger(r.Context(), logger)) - handler.ServeHTTP(w, r) - }) - }) - wrappedRouter.Use(Log()) - wrappedRouter.Mount("/", h) - - lc.Append(httpserver.NewHook(wrappedRouter, httpserver.WithAddress(bind))) - }), - )...).Run(cmd) - }, - } - cmd.Flags().Uint(BallastSizeInBytesFlag, 0, "Ballast size in bytes, default to 0") - cmd.Flags().Int(NumscriptCacheMaxCountFlag, 1024, "Numscript cache max count") - cmd.Flags().Int(ledgerBatchSizeFlag, 50, "ledger batch size") - cmd.Flags().Bool(ReadOnlyFlag, false, "Read only mode") - cmd.Flags().Bool(AutoUpgradeFlag, false, "Automatically upgrade all schemas") - return cmd -} - -func Log() func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - h.ServeHTTP(w, r) - latency := time.Since(start) - logging.FromContext(r.Context()).WithFields(map[string]interface{}{ - "method": r.Method, - "path": r.URL.Path, - "latency": latency, - "user_agent": r.UserAgent(), - "params": r.URL.Query().Encode(), - }).Debug("Request") - }) - } -} diff --git a/components/ledger/cmd/version.go b/components/ledger/cmd/version.go deleted file mode 100644 index 5b9e9ff24d..0000000000 --- a/components/ledger/cmd/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func PrintVersion(cmd *cobra.Command, args []string) { - fmt.Printf("Version: %s \n", Version) - fmt.Printf("Date: %s \n", BuildDate) - fmt.Printf("Commit: %s \n", Commit) -} - -func NewVersion() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Get version", - Run: PrintVersion, - } -} diff --git a/components/ledger/docker-compose.release.yml b/components/ledger/docker-compose.release.yml deleted file mode 100644 index d450c76314..0000000000 --- a/components/ledger/docker-compose.release.yml +++ /dev/null @@ -1,39 +0,0 @@ -version: '3.8' -volumes: - postgres: -services: - postgres: - image: "postgres:13-alpine" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ledger"] - interval: 10s - timeout: 5s - retries: 5 - ports: - - "5432:5432" - command: - - -c - - max_connections=200 - environment: - POSTGRES_USER: "ledger" - POSTGRES_PASSWORD: "ledger" - POSTGRES_DB: "ledger" - PGDATA: /data/postgres - volumes: - - postgres:/data/postgres - - ledger: - image: "ghcr.io/formancehq/ledger:v1.10.4" - healthcheck: - test: ["CMD", "wget", "http://127.0.0.1:3068/_info", "-O", "-", "-q"] - interval: 10s - timeout: 5s - retries: 5 - depends_on: - postgres: - condition: service_healthy - ports: - - "3068:3068" - environment: - STORAGE_DRIVER: "postgres" - STORAGE_POSTGRES_CONN_STRING: "postgresql://ledger:ledger@postgres/ledger?sslmode=disable" diff --git a/components/ledger/docker-compose.yml b/components/ledger/docker-compose.yml deleted file mode 100644 index 5be7f5fc6f..0000000000 --- a/components/ledger/docker-compose.yml +++ /dev/null @@ -1,37 +0,0 @@ -version: '3.8' -volumes: - postgres: -services: - postgres: - image: "postgres:15-alpine" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ledger"] - interval: 10s - timeout: 5s - retries: 5 - command: - - -c - - max_connections=200 - environment: - POSTGRES_USER: "ledger" - POSTGRES_PASSWORD: "ledger" - POSTGRES_DB: "ledger" - PGDATA: /data/postgres - volumes: - - postgres:/data/postgres - - ledger: - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - .:/src - ports: - - 3068:3068 - working_dir: /src - depends_on: - postgres: - condition: service_healthy - environment: - STORAGE_DRIVER: "postgres" - STORAGE_POSTGRES_CONN_STRING: "postgresql://ledger:ledger@postgres/ledger?sslmode=disable" - DEBUG: "true" diff --git a/components/ledger/examples/basic-auth/docker-compose.yml b/components/ledger/examples/basic-auth/docker-compose.yml deleted file mode 100644 index 9767eea25f..0000000000 --- a/components/ledger/examples/basic-auth/docker-compose.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -volumes: - postgres: -services: - postgres: - extends: - file: ../../docker-compose.yml - service: postgres - ledger: - extends: - file: ../../docker-compose.yml - service: ledger - depends_on: - - postgres - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - ../..:/src - ports: - - 3068:3068 - working_dir: /src - environment: - CGO_ENABLED: 0 - DEBUG: "true" - AUTH_BASIC_ENABLED: "true" - AUTH_BASIC_CREDENTIALS: "user:password" diff --git a/components/ledger/examples/jaeger-exporter/docker-compose.yml b/components/ledger/examples/jaeger-exporter/docker-compose.yml deleted file mode 100644 index 746f1db657..0000000000 --- a/components/ledger/examples/jaeger-exporter/docker-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -volumes: - postgres: -services: - postgres: - extends: - file: ../../docker-compose.yml - service: postgres - jaeger: - image: jaegertracing/opentelemetry-all-in-one - ports: - - "16686:16686/tcp" - ledger: - extends: - file: ../../docker-compose.yml - service: ledger - depends_on: - - postgres - - jaeger - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - ../..:/src - working_dir: /src - ports: - - "3068:3068/tcp" - environment: - CGO_ENABLED: 0 - DEBUG: "true" - OTEL_TRACES: "true" - OTEL_TRACES_EXPORTER: jaeger - OTEL_TRACES_EXPORTER_JAEGER_ENDPOINT: http://jaeger:14268/api/traces - OTEL_SERVICE_NAME: ledger diff --git a/components/ledger/examples/otlp-exporter/docker-compose.yml b/components/ledger/examples/otlp-exporter/docker-compose.yml deleted file mode 100644 index 3a6ea3b5b1..0000000000 --- a/components/ledger/examples/otlp-exporter/docker-compose.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -volumes: - postgres: -services: - postgres: - extends: - file: ../../docker-compose.yml - service: postgres - prometheus: - image: prom/prometheus:latest - restart: always - volumes: - - ./prometheus.yaml:/etc/prometheus/prometheus.yml - ports: - - "9090:9090" - otel: - image: "otel/opentelemetry-collector-contrib:0.81.0" - command: [ "--config=/etc/otel-collector-config.yaml" ] - volumes: - - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml - ledger: - extends: - file: ../../docker-compose.yml - service: ledger - depends_on: - - postgres - - otel - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - ../..:/src - working_dir: /src - environment: - CGO_ENABLED: 0 - DEBUG: "true" - OTEL_TRACES: "true" - OTEL_TRACES_EXPORTER: otlp - OTEL_TRACES_EXPORTER_OTLP_ENDPOINT: otel:4317 - OTEL_TRACES_EXPORTER_OTLP_INSECURE: "true" - OTEL_METRICS: "true" - OTEL_METRICS_EXPORTER: otlp - OTEL_METRICS_EXPORTER_OTLP_ENDPOINT: otel:4317 - OTEL_METRICS_EXPORTER_OTLP_INSECURE: "true" - OTEL_SERVICE_NAME: ledger - OTEL_RESOURCE_ATTRIBUTES: version=develop - OTEL_METRICS_RUNTIME: "true" diff --git a/components/ledger/examples/otlp-exporter/otel-collector-config.yaml b/components/ledger/examples/otlp-exporter/otel-collector-config.yaml deleted file mode 100644 index 052d0b27a1..0000000000 --- a/components/ledger/examples/otlp-exporter/otel-collector-config.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# https://uptrace.dev/opentelemetry/prometheus-metrics.html#prometheus-exporter - -receivers: - otlp: - protocols: - grpc: - -exporters: - prometheus: - endpoint: "0.0.0.0:8889" -# namespace: test-space -# const_labels: -# label1: value1 -# 'another label': spaced value - send_timestamps: true -# enable_open_metrics: true -# add_metric_suffixes: false -# metric_expiration: 180m - resource_to_telemetry_conversion: - enabled: true - logging: - -processors: - batch: - -extensions: - health_check: - pprof: - endpoint: :1888 - zpages: - endpoint: :55679 - -connectors: - spanmetrics: - namespace: span.metrics - histogram: - explicit: - buckets: [ 100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms ] - dimensions: - - name: http.status_code - - name: http.method - -service: - telemetry: - logs: - level: "debug" - extensions: [pprof, zpages, health_check] - pipelines: - traces: - receivers: [otlp] -# processors: [batch] - exporters: [spanmetrics] - metrics: - receivers: [otlp, spanmetrics] -# processors: [batch] - exporters: [prometheus] diff --git a/components/ledger/examples/otlp-exporter/prometheus.yaml b/components/ledger/examples/otlp-exporter/prometheus.yaml deleted file mode 100644 index 47b15efada..0000000000 --- a/components/ledger/examples/otlp-exporter/prometheus.yaml +++ /dev/null @@ -1,5 +0,0 @@ -scrape_configs: - - job_name: 'otel' - scrape_interval: 1s - static_configs: - - targets: ['otel:8889'] diff --git a/components/ledger/examples/publisher-http/Caddyfile b/components/ledger/examples/publisher-http/Caddyfile deleted file mode 100644 index e689e9bc30..0000000000 --- a/components/ledger/examples/publisher-http/Caddyfile +++ /dev/null @@ -1,5 +0,0 @@ -:8080 { - log { - output stdout - } -} diff --git a/components/ledger/examples/publisher-http/docker-compose.yml b/components/ledger/examples/publisher-http/docker-compose.yml deleted file mode 100644 index 94517820d1..0000000000 --- a/components/ledger/examples/publisher-http/docker-compose.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -volumes: - postgres: -services: - postgres: - extends: - file: ../../docker-compose.yml - service: postgres - listener: - image: caddy - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile - ledger: - extends: - file: ../../docker-compose.yml - service: ledger - depends_on: - - postgres - - listener - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - ../..:/src - working_dir: /src - environment: - CGO_ENABLED: 0 - DEBUG: "true" - PUBLISHER_HTTP_ENABLED: "true" - PUBLISHER_TOPIC_MAPPING: "*:http://listener:8080" diff --git a/components/ledger/examples/publisher-kafka/docker-compose.yml b/components/ledger/examples/publisher-kafka/docker-compose.yml deleted file mode 100644 index 9bdff2276a..0000000000 --- a/components/ledger/examples/publisher-kafka/docker-compose.yml +++ /dev/null @@ -1,40 +0,0 @@ ---- -volumes: - postgres: -services: - zookeeper: - image: 'bitnami/zookeeper:latest' - environment: - ALLOW_ANONYMOUS_LOGIN: yes - kafka: - image: 'bitnami/kafka:latest' - environment: - KAFKA_BROKER_ID: 1 - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 - KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181 - ALLOW_PLAINTEXT_LISTENER: yes - depends_on: - - zookeeper - postgres: - extends: - file: ../../docker-compose.yml - service: postgres - ledger: - extends: - file: ../../docker-compose.yml - service: ledger - depends_on: - - postgres - - kafka - image: golang:1.19-alpine - entrypoint: go run main.go serve - volumes: - - ../..:/src - working_dir: /src - environment: - CGO_ENABLED: 0 - DEBUG: "true" - PUBLISHER_KAFKA_ENABLED: "true" - PUBLISHER_KAFKA_BROKER: "kafka:9092" - PUBLISHER_TOPIC_MAPPING: "*:default" # Send all to 'default' topic diff --git a/components/ledger/go.mod b/components/ledger/go.mod deleted file mode 100644 index eb84228d61..0000000000 --- a/components/ledger/go.mod +++ /dev/null @@ -1,184 +0,0 @@ -module github.com/formancehq/ledger - -go 1.22.0 - -toolchain go1.22.7 - -replace github.com/formancehq/stack/ledger/client => ./pkg/client - -require ( - github.com/ThreeDotsLabs/watermill v1.3.7 - github.com/alitto/pond v1.9.2 - github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 - github.com/bluele/gcache v0.0.2 - github.com/formancehq/go-libs v1.7.1 - github.com/formancehq/stack/ledger/client v0.0.0-00010101000000-000000000000 - github.com/go-chi/chi/v5 v5.1.0 - github.com/go-chi/cors v1.2.1 - github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.1 - github.com/lib/pq v1.10.9 - github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/onsi/ginkgo/v2 v2.20.2 - github.com/onsi/gomega v1.34.2 - github.com/pborman/uuid v1.2.1 - github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.9.0 - github.com/uptrace/bun v1.2.3 - github.com/uptrace/bun/dialect/pgdialect v1.2.3 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/metric v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/fx v1.22.2 - go.uber.org/mock v0.4.0 -) - -require ( - dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/IBM/sarama v1.43.3 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 // indirect - github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 // indirect - github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 // indirect - github.com/ajg/form v1.5.1 // indirect - github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/docker v27.3.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect - github.com/eapache/queue v1.1.0 // indirect - github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-chi/render v1.0.3 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/muhlemmer/gu v0.3.1 // indirect - github.com/muhlemmer/httpforwarded v0.1.0 // indirect - github.com/nats-io/nats.go v1.37.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/ory/dockertest/v3 v3.11.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/rs/cors v1.11.1 // indirect - github.com/shirou/gopsutil/v4 v4.24.8 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun/extra/bunotel v1.2.3 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/dburl v0.23.2 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zitadel/oidc/v2 v2.12.2 // indirect - go.opentelemetry.io/contrib/instrumentation/host v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/components/ledger/go.sum b/components/ledger/go.sum deleted file mode 100644 index 0da8b96810..0000000000 --- a/components/ledger/go.sum +++ /dev/null @@ -1,466 +0,0 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= -github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 h1:M0iYM5HsGcoxtiQqprRlYZNZnGk3w5LsE9RbC2R8myQ= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1/go.mod h1:RwGHEzGsEEXC/rQNLWQqR83+WPlABgOgnv2kTB56Y4Y= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 h1:ud+4txnRgtr3kZXfXZ5+C7kVQEvsLc5HSNUEa0g+X1Q= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5/go.mod h1:t4o+4A6GB+XC8WL3DandhzPwd265zQuyWMQC/I+WIOU= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 h1:afAkAFzeooBRQvxElR+6xoigXKCukcZXnE9ACxhwlPI= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1/go.mod h1:stjbT+s4u/s5ime5jdIyvPyjBGwGeJewIN7jxH8gp4k= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs= -github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= -github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= -github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 h1:S92OBrGuLLZsyM5ybUzgc/mPjIYk2AZqufieooe98uw= -github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= -github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= -github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= -github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= -github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= -github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw= -github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.20 h1:CXDTYNHeBiAKBTAIP2gjpgbWap2GhATnTLgP8etyvEI= -github.com/nats-io/nats-server/v2 v2.10.20/go.mod h1:hgcPnoUtMfxz1qVOvLZGurVypQ+Cg6GXVXjG53iHk+M= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v4 v4.24.8 h1:pVQjIenQkIhqO81mwTaXjTzOMT7d3TZkf43PlVFHENI= -github.com/shirou/gopsutil/v4 v4.24.8/go.mod h1:wE0OrJtj4dG+hYkxqDH3QiBICdKSf04/npcvLLc/oRg= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc= -github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zitadel/oidc/v2 v2.12.2 h1:3kpckg4rurgw7w7aLJrq7yvRxb2pkNOtD08RH42vPEs= -github.com/zitadel/oidc/v2 v2.12.2/go.mod h1:vhP26g1g4YVntcTi0amMYW3tJuid70nxqxf+kb6XKgg= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0 h1:V/Cy5A2ydwvyED4ewwXJ441R3QllG+U8tXXVOjPeX4Y= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0/go.mod h1:fsY+EfHPwa1bQcxOUPv1FWaQXAwY+RliLRs6B6qgJes= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 h1:GotCpbh7YkCHdFs+hYMdvAEyGsBZifFognqrOnBwyJM= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0/go.mod h1:6b0AS55EEPj7qP44khqF5dqTUq+RkakDMShFaW1EcA4= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 h1:VrMAbeJz4gnVDg2zEzjHG4dEH86j4jO6VYB+NgtGD8s= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0/go.mod h1:qqN/uFdpeitTvm+JDqqnjm517pmQRYxTORbETHq5tOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 h1:IyFlqNsi8VT/nwYlLJfdM0y1gavxGpEvnf6FtVfZ6X4= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0/go.mod h1:bxiX8eUeKoAEQmbq/ecUT8UqZwCjZW52yJrXJUSozsk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= -go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/components/ledger/internal/account.go b/components/ledger/internal/account.go deleted file mode 100644 index 59ff9844d7..0000000000 --- a/components/ledger/internal/account.go +++ /dev/null @@ -1,53 +0,0 @@ -package ledger - -import ( - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/time" - "github.com/uptrace/bun" -) - -const ( - WORLD = "world" -) - -type Account struct { - bun.BaseModel `bun:"table:accounts,alias:accounts"` - - Address string `json:"address"` - Metadata metadata.Metadata `json:"metadata"` - FirstUsage time.Time `json:"-" bun:"first_usage,type:timestamp without timezone"` -} - -func (a Account) copy() Account { - a.Metadata = a.Metadata.Copy() - return a -} - -func NewAccount(address string) Account { - return Account{ - Address: address, - Metadata: metadata.Metadata{}, - } -} - -type ExpandedAccount struct { - Account `bun:",extend"` - Volumes VolumesByAssets `json:"volumes,omitempty" bun:"volumes,type:jsonb"` - EffectiveVolumes VolumesByAssets `json:"effectiveVolumes,omitempty" bun:"effective_volumes,type:jsonb"` -} - -func NewExpandedAccount(address string) ExpandedAccount { - return ExpandedAccount{ - Account: Account{ - Address: address, - Metadata: metadata.Metadata{}, - }, - Volumes: map[string]*Volumes{}, - } -} - -func (v ExpandedAccount) Copy() ExpandedAccount { - v.Account = v.Account.copy() - v.Volumes = v.Volumes.copy() - return v -} diff --git a/components/ledger/internal/api/backend/backend.go b/components/ledger/internal/api/backend/backend.go deleted file mode 100644 index ba8b2b0ee9..0000000000 --- a/components/ledger/internal/api/backend/backend.go +++ /dev/null @@ -1,99 +0,0 @@ -package backend - -import ( - "context" - "math/big" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/migrations" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/formancehq/ledger/internal/storage/systemstore" -) - -//go:generate mockgen -source backend.go -destination backend_generated.go -package backend . Ledger - -type Ledger interface { - GetAccountWithVolumes(ctx context.Context, query ledgerstore.GetAccountQuery) (*ledger.ExpandedAccount, error) - GetAccountsWithVolumes(ctx context.Context, query ledgerstore.GetAccountsQuery) (*bunpaginate.Cursor[ledger.ExpandedAccount], error) - CountAccounts(ctx context.Context, query ledgerstore.GetAccountsQuery) (int, error) - GetAggregatedBalances(ctx context.Context, q ledgerstore.GetAggregatedBalanceQuery) (ledger.BalancesByAssets, error) - GetMigrationsInfo(ctx context.Context) ([]migrations.Info, error) - Stats(ctx context.Context) (engine.Stats, error) - GetLogs(ctx context.Context, query ledgerstore.GetLogsQuery) (*bunpaginate.Cursor[ledger.ChainedLog], error) - CountTransactions(ctx context.Context, query ledgerstore.GetTransactionsQuery) (int, error) - GetTransactions(ctx context.Context, query ledgerstore.GetTransactionsQuery) (*bunpaginate.Cursor[ledger.ExpandedTransaction], error) - GetTransactionWithVolumes(ctx context.Context, query ledgerstore.GetTransactionQuery) (*ledger.ExpandedTransaction, error) - - CreateTransaction(ctx context.Context, parameters command.Parameters, data ledger.RunScript) (*ledger.Transaction, error) - RevertTransaction(ctx context.Context, parameters command.Parameters, id *big.Int, force, atEffectiveDate bool) (*ledger.Transaction, error) - SaveMeta(ctx context.Context, parameters command.Parameters, targetType string, targetID any, m metadata.Metadata) error - DeleteMetadata(ctx context.Context, parameters command.Parameters, targetType string, targetID any, key string) error - Import(ctx context.Context, stream chan *ledger.ChainedLog) error - Export(ctx context.Context, w engine.ExportWriter) error - - IsDatabaseUpToDate(ctx context.Context) (bool, error) - - GetVolumesWithBalances(ctx context.Context, q ledgerstore.GetVolumesWithBalancesQuery) (*bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount], error) -} - -type Backend interface { - GetLedgerEngine(ctx context.Context, name string) (Ledger, error) - GetLedger(ctx context.Context, name string) (*systemstore.Ledger, error) - ListLedgers(ctx context.Context, query systemstore.ListLedgersQuery) (*bunpaginate.Cursor[systemstore.Ledger], error) - CreateLedger(ctx context.Context, name string, configuration driver.LedgerConfiguration) error - UpdateLedgerMetadata(ctx context.Context, name string, m map[string]string) error - GetVersion() string - DeleteLedgerMetadata(ctx context.Context, param string, key string) error -} - -type DefaultBackend struct { - storageDriver *driver.Driver - resolver *engine.Resolver - version string -} - -func (d DefaultBackend) DeleteLedgerMetadata(ctx context.Context, name string, key string) error { - return d.storageDriver.GetSystemStore().DeleteLedgerMetadata(ctx, name, key) -} - -func (d DefaultBackend) UpdateLedgerMetadata(ctx context.Context, name string, m map[string]string) error { - return d.storageDriver.GetSystemStore().UpdateLedgerMetadata(ctx, name, m) -} - -func (d DefaultBackend) GetLedger(ctx context.Context, name string) (*systemstore.Ledger, error) { - return d.storageDriver.GetSystemStore().GetLedger(ctx, name) -} - -func (d DefaultBackend) CreateLedger(ctx context.Context, name string, configuration driver.LedgerConfiguration) error { - _, err := d.resolver.CreateLedger(ctx, name, configuration) - - return err -} - -func (d DefaultBackend) GetLedgerEngine(ctx context.Context, name string) (Ledger, error) { - return d.resolver.GetLedger(ctx, name) -} - -func (d DefaultBackend) ListLedgers(ctx context.Context, query systemstore.ListLedgersQuery) (*bunpaginate.Cursor[systemstore.Ledger], error) { - return d.storageDriver.GetSystemStore().ListLedgers(ctx, query) -} - -func (d DefaultBackend) GetVersion() string { - return d.version -} - -var _ Backend = (*DefaultBackend)(nil) - -func NewDefaultBackend(driver *driver.Driver, version string, resolver *engine.Resolver) *DefaultBackend { - return &DefaultBackend{ - storageDriver: driver, - resolver: resolver, - version: version, - } -} diff --git a/components/ledger/internal/api/backend/backend_generated.go b/components/ledger/internal/api/backend/backend_generated.go deleted file mode 100644 index b0135f68b0..0000000000 --- a/components/ledger/internal/api/backend/backend_generated.go +++ /dev/null @@ -1,440 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: backend.go -// -// Generated by this command: -// -// mockgen -source backend.go -destination backend_generated.go -package backend . Ledger -// - -// Package backend is a generated GoMock package. -package backend - -import ( - context "context" - big "math/big" - reflect "reflect" - - ledger "github.com/formancehq/ledger/internal" - engine "github.com/formancehq/ledger/internal/engine" - command "github.com/formancehq/ledger/internal/engine/command" - driver "github.com/formancehq/ledger/internal/storage/driver" - ledgerstore "github.com/formancehq/ledger/internal/storage/ledgerstore" - systemstore "github.com/formancehq/ledger/internal/storage/systemstore" - bunpaginate "github.com/formancehq/go-libs/bun/bunpaginate" - metadata "github.com/formancehq/go-libs/metadata" - migrations "github.com/formancehq/go-libs/migrations" - gomock "go.uber.org/mock/gomock" -) - -// MockLedger is a mock of Ledger interface. -type MockLedger struct { - ctrl *gomock.Controller - recorder *MockLedgerMockRecorder -} - -// MockLedgerMockRecorder is the mock recorder for MockLedger. -type MockLedgerMockRecorder struct { - mock *MockLedger -} - -// NewMockLedger creates a new mock instance. -func NewMockLedger(ctrl *gomock.Controller) *MockLedger { - mock := &MockLedger{ctrl: ctrl} - mock.recorder = &MockLedgerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockLedger) EXPECT() *MockLedgerMockRecorder { - return m.recorder -} - -// CountAccounts mocks base method. -func (m *MockLedger) CountAccounts(ctx context.Context, query ledgerstore.GetAccountsQuery) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CountAccounts", ctx, query) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CountAccounts indicates an expected call of CountAccounts. -func (mr *MockLedgerMockRecorder) CountAccounts(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountAccounts", reflect.TypeOf((*MockLedger)(nil).CountAccounts), ctx, query) -} - -// CountTransactions mocks base method. -func (m *MockLedger) CountTransactions(ctx context.Context, query ledgerstore.GetTransactionsQuery) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CountTransactions", ctx, query) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CountTransactions indicates an expected call of CountTransactions. -func (mr *MockLedgerMockRecorder) CountTransactions(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountTransactions", reflect.TypeOf((*MockLedger)(nil).CountTransactions), ctx, query) -} - -// CreateTransaction mocks base method. -func (m *MockLedger) CreateTransaction(ctx context.Context, parameters command.Parameters, data ledger.RunScript) (*ledger.Transaction, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTransaction", ctx, parameters, data) - ret0, _ := ret[0].(*ledger.Transaction) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateTransaction indicates an expected call of CreateTransaction. -func (mr *MockLedgerMockRecorder) CreateTransaction(ctx, parameters, data any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTransaction", reflect.TypeOf((*MockLedger)(nil).CreateTransaction), ctx, parameters, data) -} - -// DeleteMetadata mocks base method. -func (m *MockLedger) DeleteMetadata(ctx context.Context, parameters command.Parameters, targetType string, targetID any, key string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteMetadata", ctx, parameters, targetType, targetID, key) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteMetadata indicates an expected call of DeleteMetadata. -func (mr *MockLedgerMockRecorder) DeleteMetadata(ctx, parameters, targetType, targetID, key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMetadata", reflect.TypeOf((*MockLedger)(nil).DeleteMetadata), ctx, parameters, targetType, targetID, key) -} - -// Export mocks base method. -func (m *MockLedger) Export(ctx context.Context, w engine.ExportWriter) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Export", ctx, w) - ret0, _ := ret[0].(error) - return ret0 -} - -// Export indicates an expected call of Export. -func (mr *MockLedgerMockRecorder) Export(ctx, w any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Export", reflect.TypeOf((*MockLedger)(nil).Export), ctx, w) -} - -// GetAccountWithVolumes mocks base method. -func (m *MockLedger) GetAccountWithVolumes(ctx context.Context, query ledgerstore.GetAccountQuery) (*ledger.ExpandedAccount, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccountWithVolumes", ctx, query) - ret0, _ := ret[0].(*ledger.ExpandedAccount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAccountWithVolumes indicates an expected call of GetAccountWithVolumes. -func (mr *MockLedgerMockRecorder) GetAccountWithVolumes(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountWithVolumes", reflect.TypeOf((*MockLedger)(nil).GetAccountWithVolumes), ctx, query) -} - -// GetAccountsWithVolumes mocks base method. -func (m *MockLedger) GetAccountsWithVolumes(ctx context.Context, query ledgerstore.GetAccountsQuery) (*bunpaginate.Cursor[ledger.ExpandedAccount], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccountsWithVolumes", ctx, query) - ret0, _ := ret[0].(*bunpaginate.Cursor[ledger.ExpandedAccount]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAccountsWithVolumes indicates an expected call of GetAccountsWithVolumes. -func (mr *MockLedgerMockRecorder) GetAccountsWithVolumes(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountsWithVolumes", reflect.TypeOf((*MockLedger)(nil).GetAccountsWithVolumes), ctx, query) -} - -// GetAggregatedBalances mocks base method. -func (m *MockLedger) GetAggregatedBalances(ctx context.Context, q ledgerstore.GetAggregatedBalanceQuery) (ledger.BalancesByAssets, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAggregatedBalances", ctx, q) - ret0, _ := ret[0].(ledger.BalancesByAssets) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAggregatedBalances indicates an expected call of GetAggregatedBalances. -func (mr *MockLedgerMockRecorder) GetAggregatedBalances(ctx, q any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAggregatedBalances", reflect.TypeOf((*MockLedger)(nil).GetAggregatedBalances), ctx, q) -} - -// GetLogs mocks base method. -func (m *MockLedger) GetLogs(ctx context.Context, query ledgerstore.GetLogsQuery) (*bunpaginate.Cursor[ledger.ChainedLog], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLogs", ctx, query) - ret0, _ := ret[0].(*bunpaginate.Cursor[ledger.ChainedLog]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLogs indicates an expected call of GetLogs. -func (mr *MockLedgerMockRecorder) GetLogs(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockLedger)(nil).GetLogs), ctx, query) -} - -// GetMigrationsInfo mocks base method. -func (m *MockLedger) GetMigrationsInfo(ctx context.Context) ([]migrations.Info, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetMigrationsInfo", ctx) - ret0, _ := ret[0].([]migrations.Info) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetMigrationsInfo indicates an expected call of GetMigrationsInfo. -func (mr *MockLedgerMockRecorder) GetMigrationsInfo(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMigrationsInfo", reflect.TypeOf((*MockLedger)(nil).GetMigrationsInfo), ctx) -} - -// GetTransactionWithVolumes mocks base method. -func (m *MockLedger) GetTransactionWithVolumes(ctx context.Context, query ledgerstore.GetTransactionQuery) (*ledger.ExpandedTransaction, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTransactionWithVolumes", ctx, query) - ret0, _ := ret[0].(*ledger.ExpandedTransaction) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTransactionWithVolumes indicates an expected call of GetTransactionWithVolumes. -func (mr *MockLedgerMockRecorder) GetTransactionWithVolumes(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactionWithVolumes", reflect.TypeOf((*MockLedger)(nil).GetTransactionWithVolumes), ctx, query) -} - -// GetTransactions mocks base method. -func (m *MockLedger) GetTransactions(ctx context.Context, query ledgerstore.GetTransactionsQuery) (*bunpaginate.Cursor[ledger.ExpandedTransaction], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTransactions", ctx, query) - ret0, _ := ret[0].(*bunpaginate.Cursor[ledger.ExpandedTransaction]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTransactions indicates an expected call of GetTransactions. -func (mr *MockLedgerMockRecorder) GetTransactions(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTransactions", reflect.TypeOf((*MockLedger)(nil).GetTransactions), ctx, query) -} - -// GetVolumesWithBalances mocks base method. -func (m *MockLedger) GetVolumesWithBalances(ctx context.Context, q ledgerstore.GetVolumesWithBalancesQuery) (*bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVolumesWithBalances", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetVolumesWithBalances indicates an expected call of GetVolumesWithBalances. -func (mr *MockLedgerMockRecorder) GetVolumesWithBalances(ctx, q any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumesWithBalances", reflect.TypeOf((*MockLedger)(nil).GetVolumesWithBalances), ctx, q) -} - -// Import mocks base method. -func (m *MockLedger) Import(ctx context.Context, stream chan *ledger.ChainedLog) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Import", ctx, stream) - ret0, _ := ret[0].(error) - return ret0 -} - -// Import indicates an expected call of Import. -func (mr *MockLedgerMockRecorder) Import(ctx, stream any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockLedger)(nil).Import), ctx, stream) -} - -// IsDatabaseUpToDate mocks base method. -func (m *MockLedger) IsDatabaseUpToDate(ctx context.Context) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsDatabaseUpToDate", ctx) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IsDatabaseUpToDate indicates an expected call of IsDatabaseUpToDate. -func (mr *MockLedgerMockRecorder) IsDatabaseUpToDate(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDatabaseUpToDate", reflect.TypeOf((*MockLedger)(nil).IsDatabaseUpToDate), ctx) -} - -// RevertTransaction mocks base method. -func (m *MockLedger) RevertTransaction(ctx context.Context, parameters command.Parameters, id *big.Int, force, atEffectiveDate bool) (*ledger.Transaction, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RevertTransaction", ctx, parameters, id, force, atEffectiveDate) - ret0, _ := ret[0].(*ledger.Transaction) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// RevertTransaction indicates an expected call of RevertTransaction. -func (mr *MockLedgerMockRecorder) RevertTransaction(ctx, parameters, id, force, atEffectiveDate any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevertTransaction", reflect.TypeOf((*MockLedger)(nil).RevertTransaction), ctx, parameters, id, force, atEffectiveDate) -} - -// SaveMeta mocks base method. -func (m_2 *MockLedger) SaveMeta(ctx context.Context, parameters command.Parameters, targetType string, targetID any, m metadata.Metadata) error { - m_2.ctrl.T.Helper() - ret := m_2.ctrl.Call(m_2, "SaveMeta", ctx, parameters, targetType, targetID, m) - ret0, _ := ret[0].(error) - return ret0 -} - -// SaveMeta indicates an expected call of SaveMeta. -func (mr *MockLedgerMockRecorder) SaveMeta(ctx, parameters, targetType, targetID, m any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveMeta", reflect.TypeOf((*MockLedger)(nil).SaveMeta), ctx, parameters, targetType, targetID, m) -} - -// Stats mocks base method. -func (m *MockLedger) Stats(ctx context.Context) (engine.Stats, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Stats", ctx) - ret0, _ := ret[0].(engine.Stats) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Stats indicates an expected call of Stats. -func (mr *MockLedgerMockRecorder) Stats(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockLedger)(nil).Stats), ctx) -} - -// MockBackend is a mock of Backend interface. -type MockBackend struct { - ctrl *gomock.Controller - recorder *MockBackendMockRecorder -} - -// MockBackendMockRecorder is the mock recorder for MockBackend. -type MockBackendMockRecorder struct { - mock *MockBackend -} - -// NewMockBackend creates a new mock instance. -func NewMockBackend(ctrl *gomock.Controller) *MockBackend { - mock := &MockBackend{ctrl: ctrl} - mock.recorder = &MockBackendMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBackend) EXPECT() *MockBackendMockRecorder { - return m.recorder -} - -// CreateLedger mocks base method. -func (m *MockBackend) CreateLedger(ctx context.Context, name string, configuration driver.LedgerConfiguration) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateLedger", ctx, name, configuration) - ret0, _ := ret[0].(error) - return ret0 -} - -// CreateLedger indicates an expected call of CreateLedger. -func (mr *MockBackendMockRecorder) CreateLedger(ctx, name, configuration any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLedger", reflect.TypeOf((*MockBackend)(nil).CreateLedger), ctx, name, configuration) -} - -// DeleteLedgerMetadata mocks base method. -func (m *MockBackend) DeleteLedgerMetadata(ctx context.Context, param, key string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteLedgerMetadata", ctx, param, key) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteLedgerMetadata indicates an expected call of DeleteLedgerMetadata. -func (mr *MockBackendMockRecorder) DeleteLedgerMetadata(ctx, param, key any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLedgerMetadata", reflect.TypeOf((*MockBackend)(nil).DeleteLedgerMetadata), ctx, param, key) -} - -// GetLedger mocks base method. -func (m *MockBackend) GetLedger(ctx context.Context, name string) (*systemstore.Ledger, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLedger", ctx, name) - ret0, _ := ret[0].(*systemstore.Ledger) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLedger indicates an expected call of GetLedger. -func (mr *MockBackendMockRecorder) GetLedger(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLedger", reflect.TypeOf((*MockBackend)(nil).GetLedger), ctx, name) -} - -// GetLedgerEngine mocks base method. -func (m *MockBackend) GetLedgerEngine(ctx context.Context, name string) (Ledger, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLedgerEngine", ctx, name) - ret0, _ := ret[0].(Ledger) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLedgerEngine indicates an expected call of GetLedgerEngine. -func (mr *MockBackendMockRecorder) GetLedgerEngine(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLedgerEngine", reflect.TypeOf((*MockBackend)(nil).GetLedgerEngine), ctx, name) -} - -// GetVersion mocks base method. -func (m *MockBackend) GetVersion() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVersion") - ret0, _ := ret[0].(string) - return ret0 -} - -// GetVersion indicates an expected call of GetVersion. -func (mr *MockBackendMockRecorder) GetVersion() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVersion", reflect.TypeOf((*MockBackend)(nil).GetVersion)) -} - -// ListLedgers mocks base method. -func (m *MockBackend) ListLedgers(ctx context.Context, query systemstore.ListLedgersQuery) (*bunpaginate.Cursor[systemstore.Ledger], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListLedgers", ctx, query) - ret0, _ := ret[0].(*bunpaginate.Cursor[systemstore.Ledger]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListLedgers indicates an expected call of ListLedgers. -func (mr *MockBackendMockRecorder) ListLedgers(ctx, query any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLedgers", reflect.TypeOf((*MockBackend)(nil).ListLedgers), ctx, query) -} - -// UpdateLedgerMetadata mocks base method. -func (m_2 *MockBackend) UpdateLedgerMetadata(ctx context.Context, name string, m map[string]string) error { - m_2.ctrl.T.Helper() - ret := m_2.ctrl.Call(m_2, "UpdateLedgerMetadata", ctx, name, m) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateLedgerMetadata indicates an expected call of UpdateLedgerMetadata. -func (mr *MockBackendMockRecorder) UpdateLedgerMetadata(ctx, name, m any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLedgerMetadata", reflect.TypeOf((*MockBackend)(nil).UpdateLedgerMetadata), ctx, name, m) -} diff --git a/components/ledger/internal/api/backend/context.go b/components/ledger/internal/api/backend/context.go deleted file mode 100644 index cc11ed024f..0000000000 --- a/components/ledger/internal/api/backend/context.go +++ /dev/null @@ -1,17 +0,0 @@ -package backend - -import ( - "context" -) - -type ledgerKey struct{} - -var _ledgerKey = ledgerKey{} - -func ContextWithLedger(ctx context.Context, ledger Ledger) context.Context { - return context.WithValue(ctx, _ledgerKey, ledger) -} - -func LedgerFromContext(ctx context.Context) Ledger { - return ctx.Value(_ledgerKey).(Ledger) -} diff --git a/components/ledger/internal/api/backend/playnumscript.go b/components/ledger/internal/api/backend/playnumscript.go deleted file mode 100644 index 716200408c..0000000000 --- a/components/ledger/internal/api/backend/playnumscript.go +++ /dev/null @@ -1,24 +0,0 @@ -package backend - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "strings" -) - -func EncodeLink(errStr string) string { - if errStr == "" { - return "" - } - - errStr = strings.ReplaceAll(errStr, "\n", "\r\n") - payload, err := json.Marshal(map[string]any{ - "error": errStr, - }) - if err != nil { - panic(err) - } - payloadB64 := base64.StdEncoding.EncodeToString(payload) - return fmt.Sprintf("https://play.numscript.org/?payload=%v", payloadB64) -} diff --git a/components/ledger/internal/api/backend/resolver.go b/components/ledger/internal/api/backend/resolver.go deleted file mode 100644 index 132bbb74ff..0000000000 --- a/components/ledger/internal/api/backend/resolver.go +++ /dev/null @@ -1,118 +0,0 @@ -package backend - -import ( - "math/rand" - "net/http" - "strings" - "sync" - "time" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - sharedapi "github.com/formancehq/go-libs/api" - - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/ledger/internal/opentelemetry/tracer" -) - -var ( - r *rand.Rand - mu sync.Mutex -) - -const ( - ErrOutdatedSchema = "OUTDATED_SCHEMA" -) - -func init() { - r = rand.New(rand.NewSource(time.Now().UnixNano())) -} - -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - -func randomTraceID(n int) string { - mu.Lock() - defer mu.Unlock() - - b := make([]rune, n) - for i := range b { - b[i] = letterRunes[r.Intn(len(letterRunes))] - } - return string(b) -} - -func LedgerMiddleware( - resolver Backend, - excludePathFromSchemaCheck []string, -) func(handler http.Handler) http.Handler { - return func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "ledger") - if name == "" { - w.WriteHeader(http.StatusNotFound) - return - } - - ctx, span := tracer.Start(r.Context(), name) - defer span.End() - - r = r.WithContext(ctx) - - loggerFields := map[string]any{ - "ledger": name, - } - if span.SpanContext().TraceID().IsValid() { - loggerFields["trace-id"] = span.SpanContext().TraceID().String() - } else { - loggerFields["trace-id"] = randomTraceID(10) - } - - r = r.WithContext(logging.ContextWithFields(r.Context(), loggerFields)) - - l, err := resolver.GetLedgerEngine(r.Context(), name) - if err != nil { - switch { - case sqlutils.IsNotFoundError(err): - sharedapi.WriteErrorResponse(w, http.StatusNotFound, "LEDGER_NOT_FOUND", err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - pathWithoutLedger := r.URL.Path[1:] - nextSlash := strings.Index(pathWithoutLedger, "/") - if nextSlash >= 0 { - pathWithoutLedger = pathWithoutLedger[nextSlash:] - } else { - pathWithoutLedger = "" - } - - excluded := false - for _, path := range excludePathFromSchemaCheck { - if pathWithoutLedger == path { - excluded = true - break - } - } - - if !excluded { - isUpToDate, err := l.IsDatabaseUpToDate(ctx) - if err != nil { - sharedapi.BadRequest(w, sharedapi.ErrorInternal, err) - return - } - if !isUpToDate { - sharedapi.BadRequest(w, ErrOutdatedSchema, errors.New("You need to upgrade your ledger schema to the last version")) - return - } - } - - handler.ServeHTTP(w, r.WithContext(ContextWithLedger(r.Context(), l))) - }) - } -} diff --git a/components/ledger/internal/api/module.go b/components/ledger/internal/api/module.go deleted file mode 100644 index f0226ac596..0000000000 --- a/components/ledger/internal/api/module.go +++ /dev/null @@ -1,44 +0,0 @@ -package api - -import ( - _ "embed" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/driver" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - "go.uber.org/fx" -) - -type Config struct { - Version string - ReadOnly bool - Debug bool -} - -func Module(cfg Config) fx.Option { - return fx.Options( - fx.Provide(func( - backend backend.Backend, - healthController *health.HealthController, - globalMetricsRegistry metrics.GlobalRegistry, - a auth.Authenticator, - ) chi.Router { - return NewRouter(backend, healthController, globalMetricsRegistry, a, cfg.ReadOnly, cfg.Debug) - }), - fx.Provide(func(storageDriver *driver.Driver, resolver *engine.Resolver) backend.Backend { - return backend.NewDefaultBackend(storageDriver, cfg.Version, resolver) - }), - fx.Provide(fx.Annotate(noop.NewMeterProvider, fx.As(new(metric.MeterProvider)))), - fx.Decorate(fx.Annotate(func(meterProvider metric.MeterProvider) (metrics.GlobalRegistry, error) { - return metrics.RegisterGlobalRegistry(meterProvider) - }, fx.As(new(metrics.GlobalRegistry)))), - health.Module(), - ) -} diff --git a/components/ledger/internal/api/read_only.go b/components/ledger/internal/api/read_only.go deleted file mode 100644 index 0a788ef76e..0000000000 --- a/components/ledger/internal/api/read_only.go +++ /dev/null @@ -1,18 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/pkg/errors" -) - -func ReadOnly(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet && r.Method != http.MethodOptions && r.Method != http.MethodHead { - api.BadRequest(w, "READ_ONLY", errors.New("Read only mode")) - return - } - h.ServeHTTP(w, r) - }) -} diff --git a/components/ledger/internal/api/router.go b/components/ledger/internal/api/router.go deleted file mode 100644 index aad48ee77e..0000000000 --- a/components/ledger/internal/api/router.go +++ /dev/null @@ -1,42 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/ledger/internal/api/backend" - v1 "github.com/formancehq/ledger/internal/api/v1" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" -) - -func NewRouter( - backend backend.Backend, - healthController *health.HealthController, - globalMetricsRegistry metrics.GlobalRegistry, - a auth.Authenticator, - readOnly bool, - debug bool, -) chi.Router { - mux := chi.NewRouter() - mux.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - handler.ServeHTTP(w, r) - }) - }) - if readOnly { - mux.Use(ReadOnly) - } - v2Router := v2.NewRouter(backend, healthController, globalMetricsRegistry, a, debug) - mux.Handle("/v2*", http.StripPrefix("/v2", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - chi.RouteContext(r.Context()).Reset() - v2Router.ServeHTTP(w, r) - }))) - mux.Handle("/*", v1.NewRouter(backend, healthController, globalMetricsRegistry, a, debug)) - - return mux -} diff --git a/components/ledger/internal/api/v1/api_utils_test.go b/components/ledger/internal/api/v1/api_utils_test.go deleted file mode 100644 index f6358295e6..0000000000 --- a/components/ledger/internal/api/v1/api_utils_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package v1_test - -import ( - "testing" - - "github.com/formancehq/ledger/internal/storage/systemstore" - - "github.com/formancehq/ledger/internal/api/backend" - "go.uber.org/mock/gomock" -) - -func newTestingBackend(t *testing.T, expectedSchemaCheck bool) (*backend.MockBackend, *backend.MockLedger) { - ctrl := gomock.NewController(t) - mockLedger := backend.NewMockLedger(ctrl) - backend := backend.NewMockBackend(ctrl) - backend. - EXPECT(). - GetLedger(gomock.Any(), gomock.Any()). - MinTimes(0). - Return(&systemstore.Ledger{}, nil) - t.Cleanup(func() { - ctrl.Finish() - }) - backend. - EXPECT(). - GetLedgerEngine(gomock.Any(), gomock.Any()). - MinTimes(0). - Return(mockLedger, nil) - t.Cleanup(func() { - ctrl.Finish() - }) - if expectedSchemaCheck { - mockLedger.EXPECT(). - IsDatabaseUpToDate(gomock.Any()). - Return(true, nil) - } - return backend, mockLedger -} diff --git a/components/ledger/internal/api/v1/controllers_accounts.go b/components/ledger/internal/api/v1/controllers_accounts.go deleted file mode 100644 index 1a0514a308..0000000000 --- a/components/ledger/internal/api/v1/controllers_accounts.go +++ /dev/null @@ -1,217 +0,0 @@ -package v1 - -import ( - "encoding/json" - "fmt" - "math/big" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/formancehq/ledger/pkg/core/accounts" - - "github.com/go-chi/chi/v5" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/pkg/errors" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -type accountWithVolumesAndBalances ledger.ExpandedAccount - -func (a accountWithVolumesAndBalances) MarshalJSON() ([]byte, error) { - type aux struct { - ledger.ExpandedAccount - Balances map[string]*big.Int `json:"balances"` - } - return json.Marshal(aux{ - ExpandedAccount: ledger.ExpandedAccount(a), - Balances: a.Volumes.Balances(), - }) -} - -func buildAccountsFilterQuery(r *http.Request) (query.Builder, error) { - clauses := make([]query.Builder, 0) - - if balance := r.URL.Query().Get("balance"); balance != "" { - if _, err := strconv.ParseInt(balance, 10, 64); err != nil { - return nil, err - } - - balanceOperator, err := getBalanceOperator(r) - if err != nil { - return nil, err - } - - switch balanceOperator { - case "e": - clauses = append(clauses, query.Match("balance", balance)) - case "ne": - clauses = append(clauses, query.Not(query.Match("balance", balance))) - case "lt": - clauses = append(clauses, query.Lt("balance", balance)) - case "lte": - clauses = append(clauses, query.Lte("balance", balance)) - case "gt": - clauses = append(clauses, query.Gt("balance", balance)) - case "gte": - clauses = append(clauses, query.Gte("balance", balance)) - default: - return nil, errors.New("invalid balance operator") - } - } - - if address := r.URL.Query().Get("address"); address != "" { - clauses = append(clauses, query.Match("address", address)) - } - - for elem, value := range r.URL.Query() { - if strings.HasPrefix(elem, "metadata") { - clauses = append(clauses, query.Match(elem, value[0])) - } - } - - if len(clauses) == 0 { - return nil, nil - } - - return query.And(clauses...), nil -} - -func countAccounts(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - count, err := l.CountAccounts(r.Context(), ledgerstore.NewGetAccountsQuery(*options)) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - w.Header().Set("Count", fmt.Sprint(count)) - sharedapi.NoContent(w) -} - -func getAccounts(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query, err := bunpaginate.Extract[ledgerstore.GetAccountsQuery](r, func() (*ledgerstore.GetAccountsQuery, error) { - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - options.QueryBuilder, err = buildAccountsFilterQuery(r) - return pointer.For(ledgerstore.NewGetAccountsQuery(*options)), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetAccountsWithVolumes(r.Context(), *query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.RenderCursor(w, *cursor) -} - -func getAccount(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - query := ledgerstore.NewGetAccountQuery(param) - query = query.WithExpandVolumes() - - acc, err := l.GetAccountWithVolumes(r.Context(), query) - if err != nil { - switch { - case storageerrors.IsNotFoundError(err): - acc = &ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: param, - Metadata: map[string]string{}, - }, - Volumes: map[string]*ledger.Volumes{}, - EffectiveVolumes: map[string]*ledger.Volumes{}, - } - default: - sharedapi.InternalServerError(w, r, err) - return - } - } - - sharedapi.Ok(w, accountWithVolumesAndBalances(*acc)) -} - -func postAccountMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - if !accounts.ValidateAddress(param) { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid account address format")) - return - } - - var m metadata.Metadata - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid metadata format")) - return - } - - err = l.SaveMeta(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeAccount, param, m) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) -} - -func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) { - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - if err := backend.LedgerFromContext(r.Context()). - DeleteMetadata( - r.Context(), - getCommandParameters(r), - ledger.MetaTargetTypeAccount, - param, - chi.URLParam(r, "key"), - ); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) -} diff --git a/components/ledger/internal/api/v1/controllers_accounts_test.go b/components/ledger/internal/api/v1/controllers_accounts_test.go deleted file mode 100644 index 1b391c3905..0000000000 --- a/components/ledger/internal/api/v1/controllers_accounts_test.go +++ /dev/null @@ -1,282 +0,0 @@ -package v1_test - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v1 "github.com/formancehq/ledger/internal/api/v1" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes] - expectStatusCode int - expectedErrorCode string - } - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithPageSize(v1.DefaultPageSize), - }, - { - name: "using metadata", - queryParams: url.Values{ - "metadata[roles]": []string{"admin"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.And(query.Match("metadata[roles]", "admin"))). - WithPageSize(v1.DefaultPageSize), - }, - { - name: "using address", - queryParams: url.Values{ - "address": []string{"foo"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.And(query.Match("address", "foo"))). - WithPageSize(v1.DefaultPageSize), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetAccountsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{})))}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"XXX"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "invalid page size", - queryParams: url.Values{ - "pageSize": []string{"nan"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "page size over maximum", - queryParams: url.Values{ - "pageSize": []string{"1000000"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithPageSize(v1.MaxPageSize), - }, - { - name: "using balance filter", - queryParams: url.Values{ - "balance": []string{"100"}, - "balanceOperator": []string{"e"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.And(query.Match("balance", "100"))). - WithPageSize(v1.DefaultPageSize), - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ExpandedAccount]{ - Data: []ledger.ExpandedAccount{ - { - Account: ledger.Account{ - Address: "world", - Metadata: metadata.Metadata{}, - }, - }, - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetAccountsWithVolumes(gomock.Any(), ledgerstore.NewGetAccountsQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, expectedCursor, *cursor) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetAccount(t *testing.T) { - t.Parallel() - - account := ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "foo", - Metadata: metadata.Metadata{}, - }, - } - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAccountWithVolumes(gomock.Any(), ledgerstore.NewGetAccountQuery("foo").WithExpandVolumes()). - Return(&account, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts/foo", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, account, response) -} - -func TestGetAccountWithEncoded(t *testing.T) { - t.Parallel() - - account := ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "foo:bar", - Metadata: metadata.Metadata{}, - }, - } - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAccountWithVolumes(gomock.Any(), ledgerstore.NewGetAccountQuery("foo:bar").WithExpandVolumes()). - Return(&account, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts/foo%3Abar", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, account, response) -} - -func TestPostAccountMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectStatusCode int - expectedErrorCode string - account string - body any - } - - testCases := []testCase{ - { - name: "nominal", - account: "world", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "nominal dash 1", - account: "-test", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "nominal dash 2", - account: "-", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "nominal dash 2", - account: "-tes--t--t--t-----", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "invalid body", - account: "world", - body: "invalid - not an object", - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mock := newTestingBackend(t, true) - if testCase.expectStatusCode == http.StatusNoContent { - mock.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, testCase.account, testCase.body). - Return(nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/accounts/"+testCase.account+"/metadata", sharedapi.Buffer(t, testCase.body)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode >= 300 || testCase.expectStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/ledger/internal/api/v1/controllers_balances.go b/components/ledger/internal/api/v1/controllers_balances.go deleted file mode 100644 index 23025a7d11..0000000000 --- a/components/ledger/internal/api/v1/controllers_balances.go +++ /dev/null @@ -1,92 +0,0 @@ -package v1 - -import ( - "math/big" - "net/http" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func buildAggregatedBalancesQuery(r *http.Request) (query.Builder, error) { - if address := r.URL.Query().Get("address"); address != "" { - return query.Match("address", address), nil - } - - return nil, nil -} - -func getBalancesAggregated(w http.ResponseWriter, r *http.Request) { - - pitFilter, err := getPITFilter(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - queryBuilder, err := buildAggregatedBalancesQuery(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - query := ledgerstore.NewGetAggregatedBalancesQuery(*pitFilter, queryBuilder, - // notes(gfyrag): if pit is not specified, always use insertion date to be backward compatible - r.URL.Query().Get("pit") == "" || sharedapi.QueryParamBool(r, "useInsertionDate") || sharedapi.QueryParamBool(r, "use_insertion_date")) - - balances, err := backend.LedgerFromContext(r.Context()).GetAggregatedBalances(r.Context(), query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, balances) -} - -func getBalances(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - q, err := bunpaginate.Extract[ledgerstore.GetAccountsQuery](r, func() (*ledgerstore.GetAccountsQuery, error) { - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - options.QueryBuilder, err = buildAccountsFilterQuery(r) - return pointer.For(ledgerstore.NewGetAccountsQuery(*options)), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetAccountsWithVolumes(r.Context(), q.WithExpandVolumes()) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - ret := make([]map[string]map[string]*big.Int, 0) - for _, item := range cursor.Data { - e := map[string]map[string]*big.Int{ - item.Address: {}, - } - for asset, volumes := range item.Volumes { - e[item.Address][asset] = volumes.Balance() - } - ret = append(ret, e) - } - - sharedapi.RenderCursor(w, bunpaginate.Cursor[map[string]map[string]*big.Int]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: ret, - }) -} diff --git a/components/ledger/internal/api/v1/controllers_balances_test.go b/components/ledger/internal/api/v1/controllers_balances_test.go deleted file mode 100644 index 8482dc97cc..0000000000 --- a/components/ledger/internal/api/v1/controllers_balances_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package v1_test - -import ( - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v1 "github.com/formancehq/ledger/internal/api/v1" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetBalancesAggregated(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectQuery ledgerstore.GetAggregatedBalanceQuery - } - - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - UseInsertionDate: true, - }, - }, - { - name: "using address", - queryParams: url.Values{ - "address": []string{"foo"}, - }, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - QueryBuilder: query.Match("address", "foo"), - UseInsertionDate: true, - }, - }, - { - name: "using pit", - queryParams: url.Values{ - "pit": []string{now.Format(time.RFC3339Nano)}, - }, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }, - }, - { - name: "using pit + insertion date", - queryParams: url.Values{ - "pit": []string{now.Format(time.RFC3339Nano)}, - "useInsertionDate": []string{"true"}, - }, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - UseInsertionDate: true, - }, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - expectedBalances := ledger.BalancesByAssets{ - "world": big.NewInt(-100), - } - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAggregatedBalances(gomock.Any(), testCase.expectQuery). - Return(expectedBalances, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/aggregate/balances", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - balances, ok := sharedapi.DecodeSingleResponse[ledger.BalancesByAssets](t, rec.Body) - require.True(t, ok) - require.Equal(t, expectedBalances, balances) - }) - } -} diff --git a/components/ledger/internal/api/v1/controllers_config.go b/components/ledger/internal/api/v1/controllers_config.go deleted file mode 100644 index ede2286ae3..0000000000 --- a/components/ledger/internal/api/v1/controllers_config.go +++ /dev/null @@ -1,62 +0,0 @@ -package v1 - -import ( - "context" - _ "embed" - "net/http" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/ledger/internal/storage/systemstore" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" -) - -type ConfigInfo struct { - Server string `json:"server"` - Version string `json:"version"` - Config *LedgerConfig `json:"config"` -} - -type LedgerConfig struct { - LedgerStorage *LedgerStorage `json:"storage"` -} - -type LedgerStorage struct { - Driver string `json:"driver"` - Ledgers []string `json:"ledgers"` -} - -func getInfo(backend backend.Backend) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - - ledgerNames := make([]string, 0) - if err := bunpaginate.Iterate(r.Context(), systemstore.NewListLedgersQuery(100), - func(ctx context.Context, q systemstore.ListLedgersQuery) (*bunpaginate.Cursor[systemstore.Ledger], error) { - return backend.ListLedgers(ctx, q) - }, - func(cursor *bunpaginate.Cursor[systemstore.Ledger]) error { - ledgerNames = append(ledgerNames, collectionutils.Map(cursor.Data, func(from systemstore.Ledger) string { - return from.Name - })...) - return nil - }, - ); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, ConfigInfo{ - Server: "ledger", - Version: backend.GetVersion(), - Config: &LedgerConfig{ - LedgerStorage: &LedgerStorage{ - Driver: "postgres", - Ledgers: ledgerNames, - }, - }, - }) - } -} diff --git a/components/ledger/internal/api/v1/controllers_config_test.go b/components/ledger/internal/api/v1/controllers_config_test.go deleted file mode 100644 index 4871a015a8..0000000000 --- a/components/ledger/internal/api/v1/controllers_config_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package v1_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - v1 "github.com/formancehq/ledger/internal/api/v1" - - "github.com/formancehq/ledger/internal/storage/systemstore" - - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetInfo(t *testing.T) { - t.Parallel() - - backend, _ := newTestingBackend(t, false) - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - backend. - EXPECT(). - ListLedgers(gomock.Any(), gomock.Any()). - Return(&bunpaginate.Cursor[systemstore.Ledger]{ - Data: []systemstore.Ledger{ - { - Name: "a", - }, - { - Name: "b", - }, - }, - }, nil) - - backend. - EXPECT(). - GetVersion(). - Return("latest") - - req := httptest.NewRequest(http.MethodGet, "/_info", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - info, _ := sharedapi.DecodeSingleResponse[v1.ConfigInfo](t, rec.Body) - - require.EqualValues(t, v1.ConfigInfo{ - Server: "ledger", - Version: "latest", - Config: &v1.LedgerConfig{ - LedgerStorage: &v1.LedgerStorage{ - Driver: "postgres", - Ledgers: []string{"a", "b"}, - }, - }, - }, info) -} diff --git a/components/ledger/internal/api/v1/controllers_info.go b/components/ledger/internal/api/v1/controllers_info.go deleted file mode 100644 index c8ec8538fe..0000000000 --- a/components/ledger/internal/api/v1/controllers_info.go +++ /dev/null @@ -1,126 +0,0 @@ -package v1 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine" - "github.com/pkg/errors" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/migrations" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -type Info struct { - Name string `json:"name"` - Storage StorageInfo `json:"storage"` -} - -type StorageInfo struct { - Migrations []migrations.Info `json:"migrations"` -} - -func getLedgerInfo(w http.ResponseWriter, r *http.Request) { - ledger := backend.LedgerFromContext(r.Context()) - - var err error - res := Info{ - Name: chi.URLParam(r, "ledger"), - Storage: StorageInfo{}, - } - res.Storage.Migrations, err = ledger.GetMigrationsInfo(r.Context()) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, res) -} - -func getStats(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - stats, err := l.Stats(r.Context()) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, stats) -} - -func buildGetLogsQuery(r *http.Request) (query.Builder, error) { - clauses := make([]query.Builder, 0) - if after := r.URL.Query().Get("after"); after != "" { - clauses = append(clauses, query.Lt("id", after)) - } - - if startTime := r.URL.Query().Get("start_time"); startTime != "" { - clauses = append(clauses, query.Gte("date", startTime)) - } - if endTime := r.URL.Query().Get("end_time"); endTime != "" { - clauses = append(clauses, query.Lt("date", endTime)) - } - - if len(clauses) == 0 { - return nil, nil - } - if len(clauses) == 1 { - return clauses[0], nil - } - - return query.And(clauses...), nil -} - -func getLogs(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query := ledgerstore.GetLogsQuery{} - - if r.URL.Query().Get(QueryKeyCursor) != "" { - err := bunpaginate.UnmarshalCursor(r.URL.Query().Get(QueryKeyCursor), &query) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.Errorf("invalid '%s' query param", QueryKeyCursor)) - return - } - } else { - var err error - - pageSize, err := bunpaginate.GetPageSize(r, - bunpaginate.WithDefaultPageSize(DefaultPageSize), - bunpaginate.WithMaxPageSize(MaxPageSize)) - if err != nil { - switch { - case engine.IsStorageError(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - qb, err := buildGetLogsQuery(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - query = ledgerstore.NewGetLogsQuery(ledgerstore.PaginatedQueryOptions[any]{ - QueryBuilder: qb, - PageSize: uint64(pageSize), - }) - } - - cursor, err := l.GetLogs(r.Context(), query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.RenderCursor(w, *cursor) -} diff --git a/components/ledger/internal/api/v1/controllers_info_test.go b/components/ledger/internal/api/v1/controllers_info_test.go deleted file mode 100644 index fa83e8c0ac..0000000000 --- a/components/ledger/internal/api/v1/controllers_info_test.go +++ /dev/null @@ -1,201 +0,0 @@ -package v1_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/migrations" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v1 "github.com/formancehq/ledger/internal/api/v1" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetLedgerInfo(t *testing.T) { - t.Parallel() - - backend, mock := newTestingBackend(t, false) - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - migrationInfo := []migrations.Info{ - { - Version: "1", - Name: "init", - State: "ready", - Date: time.Now().Add(-2 * time.Minute).Round(time.Second).UTC(), - }, - { - Version: "2", - Name: "fix", - State: "ready", - Date: time.Now().Add(-time.Minute).Round(time.Second).UTC(), - }, - } - - mock.EXPECT(). - GetMigrationsInfo(gomock.Any()). - Return(migrationInfo, nil) - - req := httptest.NewRequest(http.MethodGet, "/xxx/_info", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - info, ok := sharedapi.DecodeSingleResponse[v1.Info](t, rec.Body) - require.True(t, ok) - - require.EqualValues(t, v1.Info{ - Name: "xxx", - Storage: v1.StorageInfo{ - Migrations: migrationInfo, - }, - }, info) -} - -func TestGetStats(t *testing.T) { - t.Parallel() - - backend, mock := newTestingBackend(t, true) - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - expectedStats := engine.Stats{ - Transactions: 10, - Accounts: 5, - } - - mock.EXPECT(). - Stats(gomock.Any()). - Return(expectedStats, nil) - - req := httptest.NewRequest(http.MethodGet, "/xxx/stats", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - stats, ok := sharedapi.DecodeSingleResponse[engine.Stats](t, rec.Body) - require.True(t, ok) - - require.EqualValues(t, expectedStats, stats) -} - -func TestGetLogs(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectQuery ledgerstore.PaginatedQueryOptions[any] - expectStatusCode int - expectedErrorCode string - } - - now := time.Now() - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil), - }, - { - name: "using start time", - queryParams: url.Values{ - "start_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil).WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using end time", - queryParams: url.Values{ - "end_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil). - WithQueryBuilder(query.Lt("date", now.Format(time.DateFormat))), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetLogsQuery(ledgerstore.NewPaginatedQueryOptions[any](nil)))}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"xxx"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ChainedLog]{ - Data: []ledger.ChainedLog{ - *ledger.NewTransactionLog(ledger.NewTransaction(), map[string]metadata.Metadata{}). - ChainLog(nil), - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetLogs(gomock.Any(), ledgerstore.NewGetLogsQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/logs", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ChainedLog](t, rec.Body) - - cursorData, err := json.Marshal(cursor) - require.NoError(t, err) - - cursorAsMap := make(map[string]any) - require.NoError(t, json.Unmarshal(cursorData, &cursorAsMap)) - - expectedCursorData, err := json.Marshal(expectedCursor) - require.NoError(t, err) - - expectedCursorAsMap := make(map[string]any) - require.NoError(t, json.Unmarshal(expectedCursorData, &expectedCursorAsMap)) - - require.Equal(t, expectedCursorAsMap, cursorAsMap) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/ledger/internal/api/v1/controllers_transactions.go b/components/ledger/internal/api/v1/controllers_transactions.go deleted file mode 100644 index d92cd96881..0000000000 --- a/components/ledger/internal/api/v1/controllers_transactions.go +++ /dev/null @@ -1,404 +0,0 @@ -package v1 - -import ( - "encoding/json" - "fmt" - "math/big" - "net/http" - "strconv" - "strings" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/machine" - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - "github.com/pkg/errors" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func mapTransactionToV1(tx ledger.Transaction) any { - return struct { - ledger.Transaction - TxID *big.Int `json:"txid"` - ID *big.Int `json:"-"` - }{ - Transaction: tx, - TxID: tx.ID, - } -} - -func mapExpandedTransactionToV1(tx ledger.ExpandedTransaction) any { - return struct { - ledger.ExpandedTransaction - TxID *big.Int `json:"txid"` - ID *big.Int `json:"-"` - }{ - ExpandedTransaction: tx, - TxID: tx.ID, - } -} - -func buildGetTransactionsQuery(r *http.Request) (query.Builder, error) { - clauses := make([]query.Builder, 0) - if after := r.URL.Query().Get("after"); after != "" { - clauses = append(clauses, query.Lt("id", after)) - } - - if startTime := r.URL.Query().Get("start_time"); startTime != "" { - clauses = append(clauses, query.Gte("date", startTime)) - } - if endTime := r.URL.Query().Get("end_time"); endTime != "" { - clauses = append(clauses, query.Lt("date", endTime)) - } - - if reference := r.URL.Query().Get("reference"); reference != "" { - clauses = append(clauses, query.Match("reference", reference)) - } - if source := r.URL.Query().Get("source"); source != "" { - clauses = append(clauses, query.Match("source", source)) - } - if destination := r.URL.Query().Get("destination"); destination != "" { - clauses = append(clauses, query.Match("destination", destination)) - } - if address := r.URL.Query().Get("account"); address != "" { - clauses = append(clauses, query.Match("account", address)) - } - for elem, value := range r.URL.Query() { - if strings.HasPrefix(elem, "metadata") { - clauses = append(clauses, query.Match(elem, value[0])) - } - } - - if len(clauses) == 0 { - return nil, nil - } - if len(clauses) == 1 { - return clauses[0], nil - } - - return query.And(clauses...), nil -} - -func countTransactions(w http.ResponseWriter, r *http.Request) { - - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - options.QueryBuilder, err = buildGetTransactionsQuery(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - count, err := backend.LedgerFromContext(r.Context()). - CountTransactions(r.Context(), ledgerstore.NewGetTransactionsQuery(*options)) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - w.Header().Set("Count", fmt.Sprint(count)) - sharedapi.NoContent(w) -} - -func getTransactions(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query, err := bunpaginate.Extract[ledgerstore.GetTransactionsQuery](r, func() (*ledgerstore.GetTransactionsQuery, error) { - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - options.QueryBuilder, err = buildGetTransactionsQuery(r) - if err != nil { - return nil, err - } - return pointer.For(ledgerstore.NewGetTransactionsQuery(*options)), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetTransactions(r.Context(), *query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.RenderCursor(w, *bunpaginate.MapCursor(cursor, mapExpandedTransactionToV1)) -} - -type Script struct { - ledger.Script - Vars map[string]json.RawMessage `json:"vars"` -} - -func (s Script) ToCore() (*ledger.Script, error) { - s.Script.Vars = map[string]string{} - for k, v := range s.Vars { - - m := make(map[string]json.RawMessage) - if err := json.Unmarshal(v, &m); err != nil { - var rawValue string - if err := json.Unmarshal(v, &rawValue); err != nil { - panic(err) - } - s.Script.Vars[k] = rawValue - continue - } - - // Is a monetary - var asset string - if err := json.Unmarshal(m["asset"], &asset); err != nil { - return nil, errors.Wrap(err, "unmarshalling asset") - } - amount := &big.Int{} - if err := json.Unmarshal(m["amount"], amount); err != nil { - return nil, errors.Wrap(err, "unmarshalling amount") - } - - s.Script.Vars[k] = fmt.Sprintf("%s %s", asset, amount) - } - return &s.Script, nil -} - -type PostTransactionRequest struct { - Postings ledger.Postings `json:"postings"` - Script Script `json:"script"` - Timestamp time.Time `json:"timestamp"` - Reference string `json:"reference"` - Metadata metadata.Metadata `json:"metadata" swaggertype:"object"` -} - -func postTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - payload := PostTransactionRequest{} - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction format")) - return - } - - if len(payload.Postings) > 0 && payload.Script.Plain != "" || - len(payload.Postings) == 0 && payload.Script.Plain == "" { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid payload: should contain either postings or script")) - return - } else if len(payload.Postings) > 0 { - if _, err := payload.Postings.Validate(); err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - txData := ledger.TransactionData{ - Postings: payload.Postings, - Timestamp: payload.Timestamp, - Reference: payload.Reference, - Metadata: payload.Metadata, - } - - res, err := l.CreateTransaction(r.Context(), getCommandParameters(r), ledger.TxToScriptData(txData, false)) - if err != nil { - switch { - case engine.IsCommandError(err): - switch { - case command.IsErrMachine(err): - switch { - case machine.IsInsufficientFundError(err): - sharedapi.BadRequest(w, ErrInsufficientFund, err) - return - case machine.IsMetadataOverride(err): - sharedapi.BadRequest(w, ErrScriptMetadataOverride, err) - return - } - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeConflict): - sharedapi.BadRequest(w, ErrConflict, err) - return - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeCompilationFailed): - sharedapi.BadRequestWithDetails(w, ErrScriptCompilationFailed, err, backend.EncodeLink(err.Error())) - return - } - sharedapi.BadRequest(w, ErrValidation, err) - return - } - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, []any{mapTransactionToV1(*res)}) - return - } - - script, err := payload.Script.ToCore() - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - runScript := ledger.RunScript{ - Script: *script, - Timestamp: payload.Timestamp, - Reference: payload.Reference, - Metadata: payload.Metadata, - } - - res, err := l.CreateTransaction(r.Context(), getCommandParameters(r), runScript) - if err != nil { - switch { - case engine.IsCommandError(err): - switch { - case command.IsErrMachine(err): - switch { - case machine.IsInsufficientFundError(err): - sharedapi.BadRequest(w, ErrInsufficientFund, err) - return - } - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeConflict): - sharedapi.BadRequest(w, ErrConflict, err) - return - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeCompilationFailed): - sharedapi.BadRequestWithDetails(w, ErrScriptCompilationFailed, err, backend.EncodeLink(err.Error())) - return - } - sharedapi.BadRequest(w, ErrValidation, err) - return - } - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, []any{mapTransactionToV1(*res)}) -} - -func getTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - txId, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction ID")) - return - } - - query := ledgerstore.NewGetTransactionQuery(txId) - if collectionutils.Contains(r.URL.Query()["expand"], "volumes") { - query = query.WithExpandVolumes() - } - if collectionutils.Contains(r.URL.Query()["expand"], "effectiveVolumes") { - query = query.WithExpandEffectiveVolumes() - } - - tx, err := l.GetTransactionWithVolumes(r.Context(), query) - if err != nil { - switch { - case storageerrors.IsNotFoundError(err): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.Ok(w, mapExpandedTransactionToV1(*tx)) -} - -func revertTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - transactionID, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.NotFound(w, errors.New("invalid transaction ID")) - return - } - - tx, err := l.RevertTransaction(r.Context(), getCommandParameters(r), transactionID, - sharedapi.QueryParamBool(r, "disableChecks"), false) - if err != nil { - switch { - case engine.IsCommandError(err): - switch { - case command.IsErrMachine(err): - switch { - case machine.IsInsufficientFundError(err): - sharedapi.BadRequest(w, ErrInsufficientFund, err) - return - } - case command.IsRevertError(err, command.ErrRevertTransactionCodeNotFound): - sharedapi.NotFound(w, err) - return - } - sharedapi.BadRequest(w, ErrValidation, err) - return - } - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Created(w, mapTransactionToV1(*tx)) -} - -func postTransactionMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - var m metadata.Metadata - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid metadata format")) - return - } - - txID, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.NotFound(w, errors.New("invalid transaction ID")) - return - } - - if err := l.SaveMeta(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeTransaction, txID, m); err != nil { - switch { - case command.IsSaveMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.NoContent(w) -} - -func deleteTransactionMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - transactionID, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction ID")) - return - } - - metadataKey := chi.URLParam(r, "key") - - if err := l.DeleteMetadata(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeTransaction, transactionID, metadataKey); err != nil { - switch { - case command.IsSaveMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.NoContent(w) -} diff --git a/components/ledger/internal/api/v1/controllers_transactions_test.go b/components/ledger/internal/api/v1/controllers_transactions_test.go deleted file mode 100644 index 62a0b4b7a6..0000000000 --- a/components/ledger/internal/api/v1/controllers_transactions_test.go +++ /dev/null @@ -1,655 +0,0 @@ -package v1_test - -import ( - "encoding/json" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v1 "github.com/formancehq/ledger/internal/api/v1" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestPostTransactions(t *testing.T) { - type testCase struct { - name string - expectedPreview bool - expectedRunScript ledger.RunScript - payload any - expectedStatusCode int - expectedErrorCode string - queryParams url.Values - } - - testCases := []testCase{ - { - name: "using plain numscript", - payload: v1.PostTransactionRequest{ - Script: v1.Script{ - Script: ledger.Script{ - Plain: `XXX`, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `XXX`, - Vars: map[string]string{}, - }, - }, - }, - { - name: "using plain numscript with variables", - payload: v1.PostTransactionRequest{ - Script: v1.Script{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - }, - Vars: map[string]json.RawMessage{ - "val": json.RawMessage(`"USD/2 100"`), - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - Vars: map[string]string{ - "val": "USD/2 100", - }, - }, - }, - }, - { - name: "using plain numscript with variables (legacy format)", - payload: v1.PostTransactionRequest{ - Script: v1.Script{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - }, - Vars: map[string]json.RawMessage{ - "val": json.RawMessage(`{ - "asset": "USD/2", - "amount": 100 - }`), - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - Vars: map[string]string{ - "val": "USD/2 100", - }, - }, - }, - }, - { - name: "using plain numscript and dry run", - payload: v1.PostTransactionRequest{ - Script: v1.Script{ - Script: ledger.Script{ - Plain: `send ( - source = @world - destination = @bank - )`, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `send ( - source = @world - destination = @bank - )`, - Vars: map[string]string{}, - }, - }, - expectedPreview: true, - queryParams: url.Values{ - "preview": []string{"true"}, - }, - }, - { - name: "using JSON postings", - payload: v1.PostTransactionRequest{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - }, - }, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), false), - }, - { - name: "using JSON postings and dry run", - queryParams: url.Values{ - "preview": []string{"true"}, - }, - payload: v1.PostTransactionRequest{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - }, - }, - expectedPreview: true, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), false), - }, - { - name: "no postings or script", - payload: v1.PostTransactionRequest{}, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "postings and script", - payload: v1.PostTransactionRequest{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - Script: v1.Script{ - Script: ledger.Script{ - Plain: ` - send [COIN 100] ( - source = @world - destination = @bob - )`, - }, - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "using invalid body", - payload: "not a valid payload", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - } - - for _, testCase := range testCases { - tc := testCase - t.Run(tc.name, func(t *testing.T) { - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - expectedTx := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockLedger.EXPECT(). - CreateTransaction(gomock.Any(), command.Parameters{ - DryRun: tc.expectedPreview, - }, testCase.expectedRunScript). - Return(expectedTx, nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions", sharedapi.Buffer(t, testCase.payload)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - tx, ok := sharedapi.DecodeSingleResponse[[]ledger.Transaction](t, rec.Body) - require.True(t, ok) - require.Equal(t, *expectedTx, tx[0]) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestPostTransactionMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectStatusCode int - expectedErrorCode string - body any - } - - testCases := []testCase{ - { - name: "nominal", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "invalid body", - body: "invalid - not an object", - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mock := newTestingBackend(t, true) - if testCase.expectStatusCode == http.StatusNoContent { - mock.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeTransaction, big.NewInt(0), testCase.body). - Return(nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions/0/metadata", sharedapi.Buffer(t, testCase.body)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode >= 300 || testCase.expectStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetTransaction(t *testing.T) { - t.Parallel() - - tx := ledger.ExpandTransaction( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - nil, - ) - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetTransactionWithVolumes(gomock.Any(), ledgerstore.NewGetTransactionQuery(big.NewInt(0))). - Return(&tx, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/transactions/0", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedTransaction](t, rec.Body) - require.Equal(t, tx, response) -} - -func TestGetTransactions(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes] - expectStatusCode int - expectedErrorCode string - } - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}), - }, - { - name: "using metadata", - queryParams: url.Values{ - "metadata[roles]": []string{"admin"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("metadata[roles]", "admin")), - }, - { - name: "using startTime", - queryParams: url.Values{ - "start_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using endTime", - queryParams: url.Values{ - "end_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Lt("date", now.Format(time.DateFormat))), - }, - { - name: "using account", - queryParams: url.Values{ - "account": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("account", "xxx")), - }, - { - name: "using reference", - queryParams: url.Values{ - "reference": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("reference", "xxx")), - }, - { - name: "using destination", - queryParams: url.Values{ - "destination": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("destination", "xxx")), - }, - { - name: "using source", - queryParams: url.Values{ - "source": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("source", "xxx")), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{})))}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"XXX"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "invalid page size", - queryParams: url.Values{ - "pageSize": []string{"nan"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v1.ErrValidation, - }, - { - name: "page size over maximum", - queryParams: url.Values{ - "pageSize": []string{"1000000"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithPageSize(v1.MaxPageSize), - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ExpandedTransaction]{ - Data: []ledger.ExpandedTransaction{ - ledger.ExpandTransaction( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - nil, - ), - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetTransactions(gomock.Any(), ledgerstore.NewGetTransactionsQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/transactions", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ExpandedTransaction](t, rec.Body) - require.Equal(t, expectedCursor, *cursor) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestCountTransactions(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes] - expectStatusCode int - expectedErrorCode string - } - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}), - }, - { - name: "using metadata", - queryParams: url.Values{ - "metadata[roles]": []string{"admin"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("metadata[roles]", "admin")), - }, - { - name: "using startTime", - queryParams: url.Values{ - "start_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using endTime", - queryParams: url.Values{ - "end_time": []string{now.Format(time.DateFormat)}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Lt("date", now.Format(time.DateFormat))), - }, - { - name: "using account", - queryParams: url.Values{ - "account": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("account", "xxx")), - }, - { - name: "using reference", - queryParams: url.Values{ - "reference": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("reference", "xxx")), - }, - { - name: "using destination", - queryParams: url.Values{ - "destination": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("destination", "xxx")), - }, - { - name: "using source", - queryParams: url.Values{ - "source": []string{"xxx"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("source", "xxx")), - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - CountTransactions(gomock.Any(), ledgerstore.NewGetTransactionsQuery(testCase.expectQuery)). - Return(10, nil) - } - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodHead, "/xxx/transactions", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - require.Equal(t, "10", rec.Header().Get("Count")) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestRevertTransaction(t *testing.T) { - - expectedTx := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - - backend, mockLedger := newTestingBackend(t, true) - mockLedger. - EXPECT(). - RevertTransaction(gomock.Any(), command.Parameters{}, big.NewInt(0), false, false). - Return(expectedTx, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions/0/revert", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusCreated, rec.Code) - tx, ok := sharedapi.DecodeSingleResponse[ledger.Transaction](t, rec.Body) - require.True(t, ok) - require.Equal(t, *expectedTx, tx) -} - -func TestForceRevertTransaction(t *testing.T) { - - expectedTx := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - - backend, mockLedger := newTestingBackend(t, true) - mockLedger. - EXPECT(). - RevertTransaction(gomock.Any(), command.Parameters{}, big.NewInt(0), true, false). - Return(expectedTx, nil) - - router := v1.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions/0/revert?disableChecks=true", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusCreated, rec.Code) - tx, ok := sharedapi.DecodeSingleResponse[ledger.Transaction](t, rec.Body) - require.True(t, ok) - require.Equal(t, *expectedTx, tx) -} diff --git a/components/ledger/internal/api/v1/errors.go b/components/ledger/internal/api/v1/errors.go deleted file mode 100644 index af23a06cb0..0000000000 --- a/components/ledger/internal/api/v1/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package v1 - -const ( - ErrConflict = "CONFLICT" - ErrInsufficientFund = "INSUFFICIENT_FUND" - ErrValidation = "VALIDATION" - - ErrScriptCompilationFailed = "COMPILATION_FAILED" - ErrScriptMetadataOverride = "METADATA_OVERRIDE" -) diff --git a/components/ledger/internal/api/v1/middleware_auto_create_ledger.go b/components/ledger/internal/api/v1/middleware_auto_create_ledger.go deleted file mode 100644 index dbc812a716..0000000000 --- a/components/ledger/internal/api/v1/middleware_auto_create_ledger.go +++ /dev/null @@ -1,36 +0,0 @@ -package v1 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/formancehq/ledger/internal/storage/sqlutils" -) - -func autoCreateMiddleware(backend backend.Backend) func(handler http.Handler) http.Handler { - return func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - ledgerName := chi.URLParam(r, "ledger") - if _, err := backend.GetLedger(r.Context(), ledgerName); err != nil { - if !sqlutils.IsNotFoundError(err) { - sharedapi.InternalServerError(w, r, err) - return - } - - if err := backend.CreateLedger(r.Context(), ledgerName, driver.LedgerConfiguration{ - Bucket: ledgerName, - }); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - } - - handler.ServeHTTP(w, r) - }) - } -} diff --git a/components/ledger/internal/api/v1/middlewares_metrics.go b/components/ledger/internal/api/v1/middlewares_metrics.go deleted file mode 100644 index dac79cb4ad..0000000000 --- a/components/ledger/internal/api/v1/middlewares_metrics.go +++ /dev/null @@ -1,55 +0,0 @@ -package v1 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -type statusRecorder struct { - http.ResponseWriter - Status int -} - -func newStatusRecorder(w http.ResponseWriter) *statusRecorder { - return &statusRecorder{ResponseWriter: w} -} - -func (r *statusRecorder) WriteHeader(status int) { - r.Status = status - r.ResponseWriter.WriteHeader(status) -} - -func MetricsMiddleware(globalMetricsRegistry metrics.GlobalRegistry) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - attrs := []attribute.KeyValue{} - - ctx := r.Context() - name := chi.URLParam(r, "ledger") - if name != "" { - attrs = append(attrs, attribute.String("ledger", name)) - } - - recorder := newStatusRecorder(w) - - start := time.Now() - h.ServeHTTP(recorder, r) - latency := time.Since(start) - - attrs = append(attrs, - attribute.String("route", chi.RouteContext(r.Context()).RoutePattern())) - - globalMetricsRegistry.APILatencies().Record(ctx, latency.Milliseconds(), metric.WithAttributes(attrs...)) - - attrs = append(attrs, attribute.Int("status", recorder.Status)) - globalMetricsRegistry.StatusCodes().Add(ctx, 1, metric.WithAttributes(attrs...)) - }) - } -} diff --git a/components/ledger/internal/api/v1/query.go b/components/ledger/internal/api/v1/query.go deleted file mode 100644 index d8d83736b4..0000000000 --- a/components/ledger/internal/api/v1/query.go +++ /dev/null @@ -1,23 +0,0 @@ -package v1 - -import ( - "net/http" -) - -const ( - MaxPageSize = 1000 - DefaultPageSize = 15 - - QueryKeyCursor = "cursor" - QueryKeyBalanceOperator = "balanceOperator" -) - -func getBalanceOperator(c *http.Request) (string, error) { - balanceOperator := "eq" - balanceOperatorStr := c.URL.Query().Get(QueryKeyBalanceOperator) - if balanceOperatorStr != "" { - return balanceOperatorStr, nil - } - - return balanceOperator, nil -} diff --git a/components/ledger/internal/api/v1/routes.go b/components/ledger/internal/api/v1/routes.go deleted file mode 100644 index 0bfdf6abab..0000000000 --- a/components/ledger/internal/api/v1/routes.go +++ /dev/null @@ -1,86 +0,0 @@ -package v1 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" -) - -func NewRouter( - b backend.Backend, - healthController *health.HealthController, - globalMetricsRegistry metrics.GlobalRegistry, - authenticator auth.Authenticator, - debug bool, -) chi.Router { - router := chi.NewMux() - - router.Use( - cors.New(cors.Options{ - AllowOriginFunc: func(r *http.Request, origin string) bool { - return true - }, - AllowCredentials: true, - }).Handler, - MetricsMiddleware(globalMetricsRegistry), - middleware.Recoverer, - ) - - router.Get("/_healthcheck", healthController.Check) - router.Get("/_info", getInfo(b)) - - router.Group(func(router chi.Router) { - router.Use(auth.Middleware(authenticator)) - router.Use(service.OTLPMiddleware("ledger", debug)) - - router.Route("/{ledger}", func(router chi.Router) { - router.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r) - }) - }) - router.Use(autoCreateMiddleware(b)) - router.Use(backend.LedgerMiddleware(b, []string{"/_info"})) - - // LedgerController - router.Get("/_info", getLedgerInfo) - router.Get("/stats", getStats) - router.Get("/logs", getLogs) - - // AccountController - router.Get("/accounts", getAccounts) - router.Head("/accounts", countAccounts) - router.Get("/accounts/{address}", getAccount) - router.Post("/accounts/{address}/metadata", postAccountMetadata) - router.Delete("/accounts/{address}/metadata/{key}", deleteAccountMetadata) - - // TransactionController - router.Get("/transactions", getTransactions) - router.Head("/transactions", countTransactions) - - router.Post("/transactions", postTransaction) - router.Post("/transactions/batch", func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "not supported", http.StatusBadRequest) - }) - - router.Get("/transactions/{id}", getTransaction) - router.Post("/transactions/{id}/revert", revertTransaction) - router.Post("/transactions/{id}/metadata", postTransactionMetadata) - router.Delete("/transactions/{id}/metadata/{key}", deleteTransactionMetadata) - - router.Get("/balances", getBalances) - router.Get("/aggregate/balances", getBalancesAggregated) - }) - }) - - return router -} diff --git a/components/ledger/internal/api/v1/utils.go b/components/ledger/internal/api/v1/utils.go deleted file mode 100644 index b141142466..0000000000 --- a/components/ledger/internal/api/v1/utils.go +++ /dev/null @@ -1,79 +0,0 @@ -package v1 - -import ( - "net/http" - "strings" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func getPITFilter(r *http.Request) (*ledgerstore.PITFilter, error) { - pitString := r.URL.Query().Get("pit") - if pitString == "" { - return &ledgerstore.PITFilter{}, nil - } - pit, err := time.ParseTime(pitString) - if err != nil { - return nil, err - } - return &ledgerstore.PITFilter{ - PIT: &pit, - }, nil -} - -func getPITFilterWithVolumes(r *http.Request) (*ledgerstore.PITFilterWithVolumes, error) { - pit, err := getPITFilter(r) - if err != nil { - return nil, err - } - return &ledgerstore.PITFilterWithVolumes{ - PITFilter: *pit, - ExpandVolumes: collectionutils.Contains(r.URL.Query()["expand"], "volumes"), - ExpandEffectiveVolumes: collectionutils.Contains(r.URL.Query()["expand"], "effectiveVolumes"), - }, nil -} - -func getQueryBuilder(r *http.Request) (query.Builder, error) { - return query.ParseJSON(r.URL.Query().Get("query")) -} - -func getPaginatedQueryOptionsOfPITFilterWithVolumes(r *http.Request) (*ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes], error) { - qb, err := getQueryBuilder(r) - if err != nil { - return nil, err - } - - pitFilter, err := getPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - - pageSize, err := bunpaginate.GetPageSize(r, bunpaginate.WithMaxPageSize(MaxPageSize), bunpaginate.WithDefaultPageSize(DefaultPageSize)) - if err != nil { - return nil, err - } - - return pointer.For(ledgerstore.NewPaginatedQueryOptions(*pitFilter). - WithQueryBuilder(qb). - WithPageSize(pageSize)), nil -} - -func getCommandParameters(r *http.Request) command.Parameters { - dryRunAsString := r.URL.Query().Get("preview") - dryRun := strings.ToUpper(dryRunAsString) == "YES" || strings.ToUpper(dryRunAsString) == "TRUE" || dryRunAsString == "1" - - idempotencyKey := r.Header.Get("Idempotency-Key") - - return command.Parameters{ - DryRun: dryRun, - IdempotencyKey: idempotencyKey, - } -} diff --git a/components/ledger/internal/api/v2/api_utils_test.go b/components/ledger/internal/api/v2/api_utils_test.go deleted file mode 100644 index ecc4aa0fc3..0000000000 --- a/components/ledger/internal/api/v2/api_utils_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package v2_test - -import ( - "testing" - - "go.uber.org/mock/gomock" - - "github.com/formancehq/ledger/internal/api/backend" -) - -func newTestingBackend(t *testing.T, expectedSchemaCheck bool) (*backend.MockBackend, *backend.MockLedger) { - ctrl := gomock.NewController(t) - mockLedger := backend.NewMockLedger(ctrl) - backend := backend.NewMockBackend(ctrl) - backend. - EXPECT(). - GetLedgerEngine(gomock.Any(), gomock.Any()). - MinTimes(0). - Return(mockLedger, nil) - t.Cleanup(func() { - ctrl.Finish() - }) - if expectedSchemaCheck { - mockLedger.EXPECT(). - IsDatabaseUpToDate(gomock.Any()). - Return(true, nil) - } - return backend, mockLedger -} diff --git a/components/ledger/internal/api/v2/bulk.go b/components/ledger/internal/api/v2/bulk.go deleted file mode 100644 index c7c6370609..0000000000 --- a/components/ledger/internal/api/v2/bulk.go +++ /dev/null @@ -1,209 +0,0 @@ -package v2 - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - - "github.com/formancehq/ledger/internal/opentelemetry/tracer" - - sharedapi "github.com/formancehq/go-libs/api" - - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine/command" -) - -const ( - ActionCreateTransaction = "CREATE_TRANSACTION" - ActionAddMetadata = "ADD_METADATA" - ActionRevertTransaction = "REVERT_TRANSACTION" - ActionDeleteMetadata = "DELETE_METADATA" -) - -type Bulk []Element - -type Element struct { - Action string `json:"action"` - IdempotencyKey string `json:"ik"` - Data json.RawMessage `json:"data"` -} - -type Result struct { - ErrorCode string `json:"errorCode,omitempty"` - ErrorDescription string `json:"errorDescription,omitempty"` - ErrorDetails string `json:"errorDetails,omitempty"` - Data any `json:"data,omitempty"` - ResponseType string `json:"responseType"` // Added for sdk generation (discriminator in oneOf) -} - -func ProcessBulk(ctx context.Context, l backend.Ledger, bulk Bulk, continueOnFailure bool) ([]Result, bool, error) { - - ctx, span := tracer.Start(ctx, "Bulk") - defer span.End() - - ret := make([]Result, 0, len(bulk)) - - errorsInBulk := false - var bulkError = func(action, code string, err error) { - ret = append(ret, Result{ - ErrorCode: code, - ErrorDescription: err.Error(), - ResponseType: "ERROR", - }) - errorsInBulk = true - } - - for i, element := range bulk { - parameters := command.Parameters{ - DryRun: false, - IdempotencyKey: element.IdempotencyKey, - } - - switch element.Action { - case ActionCreateTransaction: - req := &ledger.TransactionRequest{} - if err := json.Unmarshal(element.Data, req); err != nil { - return nil, errorsInBulk, fmt.Errorf("error parsing element %d: %s", i, err) - } - rs := req.ToRunScript() - - tx, err := l.CreateTransaction(ctx, parameters, *rs) - if err != nil { - var code string - switch { - case machine.IsInsufficientFundError(err): - code = ErrInsufficientFund - case engine.IsCommandError(err): - code = ErrValidation - default: - code = sharedapi.ErrorInternal - } - bulkError(element.Action, code, err) - if !continueOnFailure { - return ret, errorsInBulk, nil - } - } else { - ret = append(ret, Result{ - Data: tx, - ResponseType: element.Action, - }) - } - case ActionAddMetadata: - type addMetadataRequest struct { - TargetType string `json:"targetType"` - TargetID json.RawMessage `json:"targetId"` - Metadata metadata.Metadata `json:"metadata"` - } - req := &addMetadataRequest{} - if err := json.Unmarshal(element.Data, req); err != nil { - return nil, errorsInBulk, fmt.Errorf("error parsing element %d: %s", i, err) - } - - var targetID any - switch req.TargetType { - case ledger.MetaTargetTypeAccount: - targetID = "" - case ledger.MetaTargetTypeTransaction: - targetID = big.NewInt(0) - } - if err := json.Unmarshal(req.TargetID, &targetID); err != nil { - return nil, errorsInBulk, err - } - - if err := l.SaveMeta(ctx, parameters, req.TargetType, targetID, req.Metadata); err != nil { - var code string - switch { - case command.IsSaveMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - code = sharedapi.ErrorCodeNotFound - default: - code = sharedapi.ErrorInternal - } - bulkError(element.Action, code, err) - if !continueOnFailure { - return ret, errorsInBulk, nil - } - } else { - ret = append(ret, Result{ - ResponseType: element.Action, - }) - } - case ActionRevertTransaction: - type revertTransactionRequest struct { - ID *big.Int `json:"id"` - Force bool `json:"force"` - AtEffectiveDate bool `json:"atEffectiveDate"` - } - req := &revertTransactionRequest{} - if err := json.Unmarshal(element.Data, req); err != nil { - return nil, errorsInBulk, fmt.Errorf("error parsing element %d: %s", i, err) - } - - tx, err := l.RevertTransaction(ctx, parameters, req.ID, req.Force, req.AtEffectiveDate) - if err != nil { - var code string - switch { - case engine.IsCommandError(err): - code = ErrValidation - default: - code = sharedapi.ErrorInternal - } - bulkError(element.Action, code, err) - if !continueOnFailure { - return ret, errorsInBulk, nil - } - } else { - ret = append(ret, Result{ - Data: tx, - ResponseType: element.Action, - }) - } - case ActionDeleteMetadata: - type deleteMetadataRequest struct { - TargetType string `json:"targetType"` - TargetID json.RawMessage `json:"targetId"` - Key string `json:"key"` - } - req := &deleteMetadataRequest{} - if err := json.Unmarshal(element.Data, req); err != nil { - return nil, errorsInBulk, fmt.Errorf("error parsing element %d: %s", i, err) - } - - var targetID any - switch req.TargetType { - case ledger.MetaTargetTypeAccount: - targetID = "" - case ledger.MetaTargetTypeTransaction: - targetID = big.NewInt(0) - } - if err := json.Unmarshal(req.TargetID, &targetID); err != nil { - return nil, errorsInBulk, err - } - - err := l.DeleteMetadata(ctx, parameters, req.TargetType, targetID, req.Key) - if err != nil { - var code string - switch { - case command.IsDeleteMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - code = sharedapi.ErrorCodeNotFound - default: - code = sharedapi.ErrorInternal - } - bulkError(element.Action, code, err) - if !continueOnFailure { - return ret, errorsInBulk, nil - } - } else { - ret = append(ret, Result{ - ResponseType: element.Action, - }) - } - } - } - return ret, errorsInBulk, nil -} diff --git a/components/ledger/internal/api/v2/controller_export_logs.go b/components/ledger/internal/api/v2/controller_export_logs.go deleted file mode 100644 index 3c00e62e05..0000000000 --- a/components/ledger/internal/api/v2/controller_export_logs.go +++ /dev/null @@ -1,23 +0,0 @@ -package v2 - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine" -) - -func exportLogs(w http.ResponseWriter, r *http.Request) { - enc := json.NewEncoder(w) - w.Header().Set("Content-Type", "application/octet-stream") - if err := backend.LedgerFromContext(r.Context()).Export(r.Context(), engine.ExportWriterFn(func(ctx context.Context, log *ledger.ChainedLog) error { - return enc.Encode(log) - })); err != nil { - api.InternalServerError(w, r, err) - return - } -} diff --git a/components/ledger/internal/api/v2/controller_import_logs.go b/components/ledger/internal/api/v2/controller_import_logs.go deleted file mode 100644 index 32b50f8ed4..0000000000 --- a/components/ledger/internal/api/v2/controller_import_logs.go +++ /dev/null @@ -1,65 +0,0 @@ -package v2 - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/formancehq/ledger/internal/engine" - - "github.com/formancehq/go-libs/api" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/pkg/errors" -) - -func importLogs(w http.ResponseWriter, r *http.Request) { - - stream := make(chan *ledger.ChainedLog) - errChan := make(chan error, 1) - go func() { - errChan <- backend.LedgerFromContext(r.Context()).Import(r.Context(), stream) - }() - dec := json.NewDecoder(r.Body) - handleError := func(err error) { - switch { - case errors.Is(err, engine.ImportError{}): - api.WriteErrorResponse(w, http.StatusBadRequest, "IMPORT", err) - default: - api.InternalServerError(w, r, err) - } - } - for { - l := &ledger.ChainedLog{} - if err := dec.Decode(l); err != nil { - if errors.Is(err, io.EOF) { - close(stream) - break - } else { - api.InternalServerError(w, r, err) - return - } - } - select { - case stream <- l: - case <-r.Context().Done(): - api.InternalServerError(w, r, r.Context().Err()) - return - case err := <-errChan: - handleError(err) - return - } - } - select { - case err := <-errChan: - if err != nil { - handleError(err) - return - } - case <-r.Context().Done(): - api.InternalServerError(w, r, r.Context().Err()) - return - } - - api.NoContent(w) -} diff --git a/components/ledger/internal/api/v2/controllers_accounts.go b/components/ledger/internal/api/v2/controllers_accounts.go deleted file mode 100644 index 0a363d6b4d..0000000000 --- a/components/ledger/internal/api/v2/controllers_accounts.go +++ /dev/null @@ -1,166 +0,0 @@ -package v2 - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/formancehq/ledger/pkg/core/accounts" - - "github.com/go-chi/chi/v5" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/pointer" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/pkg/errors" -) - -func countAccounts(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - count, err := l.CountAccounts(r.Context(), ledgerstore.NewGetAccountsQuery(*options)) - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - w.Header().Set("Count", fmt.Sprint(count)) - sharedapi.NoContent(w) -} - -func getAccounts(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query, err := bunpaginate.Extract[ledgerstore.GetAccountsQuery](r, func() (*ledgerstore.GetAccountsQuery, error) { - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - return pointer.For(ledgerstore.NewGetAccountsQuery(*options)), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetAccountsWithVolumes(r.Context(), *query) - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.RenderCursor(w, *cursor) -} - -func getAccount(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - query := ledgerstore.NewGetAccountQuery(param) - if collectionutils.Contains(r.URL.Query()["expand"], "volumes") { - query = query.WithExpandVolumes() - } - if collectionutils.Contains(r.URL.Query()["expand"], "effectiveVolumes") { - query = query.WithExpandEffectiveVolumes() - } - pitFilter, err := getPITFilter(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - query.PITFilter = *pitFilter - - acc, err := l.GetAccountWithVolumes(r.Context(), query) - if err != nil { - switch { - case storageerrors.IsNotFoundError(err): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.Ok(w, acc) -} - -func postAccountMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - if !accounts.ValidateAddress(param) { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid account address format")) - return - } - - var m metadata.Metadata - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid metadata format")) - return - } - - err = l.SaveMeta(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeAccount, chi.URLParam(r, "address"), m) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) -} - -func deleteAccountMetadata(w http.ResponseWriter, r *http.Request) { - param, err := url.PathUnescape(chi.URLParam(r, "address")) - if err != nil { - sharedapi.BadRequestWithDetails(w, ErrValidation, err, err.Error()) - return - } - - if err := backend.LedgerFromContext(r.Context()). - DeleteMetadata( - r.Context(), - getCommandParameters(r), - ledger.MetaTargetTypeAccount, - param, - chi.URLParam(r, "key"), - ); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) -} diff --git a/components/ledger/internal/api/v2/controllers_accounts_test.go b/components/ledger/internal/api/v2/controllers_accounts_test.go deleted file mode 100644 index 5b40539905..0000000000 --- a/components/ledger/internal/api/v2/controllers_accounts_test.go +++ /dev/null @@ -1,323 +0,0 @@ -package v2_test - -import ( - "bytes" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes] - expectStatusCode int - expectedErrorCode string - } - before := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using metadata", - body: `{"$match": { "metadata[roles]": "admin" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("metadata[roles]", "admin")). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using address", - body: `{"$match": { "address": "foo" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("address", "foo")). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetAccountsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{})))}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"XXX"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "invalid page size", - queryParams: url.Values{ - "pageSize": []string{"nan"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "page size over maximum", - queryParams: url.Values{ - "pageSize": []string{"1000000"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithPageSize(v2.MaxPageSize), - }, - { - name: "using balance filter", - body: `{"$lt": { "balance[USD/2]": 100 }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Lt("balance[USD/2]", float64(100))). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using exists filter", - body: `{"$exists": { "metadata": "foo" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Exists("metadata", "foo")). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using invalid query payload", - body: `[]`, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ExpandedAccount]{ - Data: []ledger.ExpandedAccount{ - { - Account: ledger.Account{ - Address: "world", - Metadata: metadata.Metadata{}, - }, - }, - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetAccountsWithVolumes(gomock.Any(), ledgerstore.NewGetAccountsQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts?pit="+before.Format(time.RFC3339Nano), bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - params := url.Values{} - if testCase.queryParams != nil { - params = testCase.queryParams - } - params.Set("pit", before.Format(time.RFC3339Nano)) - req.URL.RawQuery = params.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, expectedCursor, *cursor) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetAccount(t *testing.T) { - t.Parallel() - - account := ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "foo", - Metadata: metadata.Metadata{}, - }, - } - - now := time.Now() - query := ledgerstore.NewGetAccountQuery("foo") - query.PIT = &now - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAccountWithVolumes(gomock.Any(), query). - Return(&account, nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts/foo?pit="+now.Format(time.RFC3339Nano), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, account, response) -} - -func TestGetAccountWithEncoded(t *testing.T) { - t.Parallel() - - account := ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "foo:bar", - Metadata: metadata.Metadata{}, - }, - } - - now := time.Now() - query := ledgerstore.NewGetAccountQuery("foo:bar") - query.PIT = &now - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAccountWithVolumes(gomock.Any(), query). - Return(&account, nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/accounts/foo%3Abar?pit="+now.Format(time.RFC3339Nano), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedAccount](t, rec.Body) - require.Equal(t, account, response) -} - -func TestPostAccountMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectStatusCode int - expectedErrorCode string - account string - body any - } - - testCases := []testCase{ - { - name: "nominal", - account: "world", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "nominal", - account: "test-", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "nominal", - account: "-t--e-st-", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "invalid body", - account: "world", - body: "invalid - not an object", - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mock := newTestingBackend(t, true) - if testCase.expectStatusCode == http.StatusNoContent { - mock.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, testCase.account, testCase.body). - Return(nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/accounts/"+testCase.account+"/metadata", sharedapi.Buffer(t, testCase.body)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode >= 300 || testCase.expectStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_balances.go b/components/ledger/internal/api/v2/controllers_balances.go deleted file mode 100644 index 3b16c0d087..0000000000 --- a/components/ledger/internal/api/v2/controllers_balances.go +++ /dev/null @@ -1,39 +0,0 @@ -package v2 - -import ( - "net/http" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func getBalancesAggregated(w http.ResponseWriter, r *http.Request) { - - pitFilter, err := getPITFilter(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - queryBuilder, err := getQueryBuilder(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - balances, err := backend.LedgerFromContext(r.Context()). - GetAggregatedBalances(r.Context(), ledgerstore.NewGetAggregatedBalancesQuery( - *pitFilter, queryBuilder, sharedapi.QueryParamBool(r, "use_insertion_date") || sharedapi.QueryParamBool(r, "useInsertionDate"))) - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.Ok(w, balances) -} diff --git a/components/ledger/internal/api/v2/controllers_balances_test.go b/components/ledger/internal/api/v2/controllers_balances_test.go deleted file mode 100644 index 1d018e702d..0000000000 --- a/components/ledger/internal/api/v2/controllers_balances_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package v2_test - -import ( - "bytes" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetBalancesAggregated(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.GetAggregatedBalanceQuery - } - - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }, - }, - { - name: "using address", - body: `{"$match": {"address": "foo"}}`, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - QueryBuilder: query.Match("address", "foo"), - }, - }, - { - name: "using exists metadata filter", - body: `{"$exists": {"metadata": "foo"}}`, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - QueryBuilder: query.Exists("metadata", "foo"), - }, - }, - { - name: "using pit", - queryParams: url.Values{ - "pit": []string{now.Format(time.RFC3339Nano)}, - }, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }, - }, - { - name: "using pit + insertion date", - queryParams: url.Values{ - "pit": []string{now.Format(time.RFC3339Nano)}, - "useInsertionDate": []string{"true"}, - }, - expectQuery: ledgerstore.GetAggregatedBalanceQuery{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - UseInsertionDate: true, - }, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - expectedBalances := ledger.BalancesByAssets{ - "world": big.NewInt(-100), - } - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetAggregatedBalances(gomock.Any(), testCase.expectQuery). - Return(expectedBalances, nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/aggregate/balances?pit="+now.Format(time.RFC3339Nano), bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - if testCase.queryParams != nil { - req.URL.RawQuery = testCase.queryParams.Encode() - } - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - balances, ok := sharedapi.DecodeSingleResponse[ledger.BalancesByAssets](t, rec.Body) - require.True(t, ok) - require.Equal(t, expectedBalances, balances) - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_bulk.go b/components/ledger/internal/api/v2/controllers_bulk.go deleted file mode 100644 index f7afc3eeca..0000000000 --- a/components/ledger/internal/api/v2/controllers_bulk.go +++ /dev/null @@ -1,33 +0,0 @@ -package v2 - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/contextutil" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" -) - -func bulkHandler(w http.ResponseWriter, r *http.Request) { - b := Bulk{} - if err := json.NewDecoder(r.Body).Decode(&b); err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - w.Header().Set("Content-Type", "application/json") - - ctx, _ := contextutil.Detached(r.Context()) - ret, errorsInBulk, err := ProcessBulk(ctx, backend.LedgerFromContext(r.Context()), b, sharedapi.QueryParamBool(r, "continueOnFailure")) - if err != nil || errorsInBulk { - w.WriteHeader(http.StatusBadRequest) - } - - if err := json.NewEncoder(w).Encode(sharedapi.BaseResponse[[]Result]{ - Data: &ret, - }); err != nil { - panic(err) - } -} diff --git a/components/ledger/internal/api/v2/controllers_bulk_test.go b/components/ledger/internal/api/v2/controllers_bulk_test.go deleted file mode 100644 index 4a3e5692d9..0000000000 --- a/components/ledger/internal/api/v2/controllers_bulk_test.go +++ /dev/null @@ -1,332 +0,0 @@ -package v2_test - -import ( - "bytes" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/api/backend" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestBulk(t *testing.T) { - t.Parallel() - - now := time.Now() - - type bulkTestCase struct { - name string - queryParams url.Values - body string - expectations func(mockLedger *backend.MockLedger) - expectError bool - expectResults []v2.Result - } - - testCases := []bulkTestCase{ - { - name: "create transaction", - body: fmt.Sprintf(`[{ - "action": "CREATE_TRANSACTION", - "data": { - "postings": [{ - "source": "world", - "destination": "bank", - "amount": 100, - "asset": "USD/2" - }], - "timestamp": "%s" - } - }]`, now.Format(time.RFC3339Nano)), - expectations: func(mockLedger *backend.MockLedger) { - postings := []ledger.Posting{{ - Source: "world", - Destination: "bank", - Amount: big.NewInt(100), - Asset: "USD/2", - }} - mockLedger.EXPECT(). - CreateTransaction(gomock.Any(), command.Parameters{}, ledger.TxToScriptData(ledger.TransactionData{ - Postings: postings, - Timestamp: now, - }, false)). - Return(&ledger.Transaction{ - TransactionData: ledger.TransactionData{ - Postings: postings, - Metadata: metadata.Metadata{}, - Timestamp: now, - }, - ID: big.NewInt(0), - }, nil) - }, - expectResults: []v2.Result{{ - Data: map[string]any{ - "postings": []any{ - map[string]any{ - "source": "world", - "destination": "bank", - "amount": float64(100), - "asset": "USD/2", - }, - }, - "timestamp": now.Format(time.RFC3339Nano), - "metadata": map[string]any{}, - "reverted": false, - "id": float64(0), - }, - ResponseType: v2.ActionCreateTransaction, - }}, - }, - { - name: "add metadata on transaction", - body: `[{ - "action": "ADD_METADATA", - "data": { - "targetId": 1, - "targetType": "TRANSACTION", - "metadata": { - "foo": "bar" - } - } - }]`, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeTransaction, big.NewInt(1), metadata.Metadata{ - "foo": "bar", - }). - Return(nil) - }, - expectResults: []v2.Result{{ - ResponseType: v2.ActionAddMetadata, - }}, - }, - { - name: "add metadata on account", - body: `[{ - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo": "bar" - } - } - }]`, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo": "bar", - }). - Return(nil) - }, - expectResults: []v2.Result{{ - ResponseType: v2.ActionAddMetadata, - }}, - }, - { - name: "revert transaction", - body: `[{ - "action": "REVERT_TRANSACTION", - "data": { - "id": 1 - } - }]`, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - RevertTransaction(gomock.Any(), command.Parameters{}, big.NewInt(1), false, false). - Return(&ledger.Transaction{}, nil) - }, - expectResults: []v2.Result{{ - Data: map[string]any{ - "id": nil, - "metadata": nil, - "postings": nil, - "reverted": false, - "timestamp": "0001-01-01T00:00:00Z", - }, - ResponseType: v2.ActionRevertTransaction, - }}, - }, - { - name: "delete metadata", - body: `[{ - "action": "DELETE_METADATA", - "data": { - "targetType": "TRANSACTION", - "targetId": 1, - "key": "foo" - } - }]`, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - DeleteMetadata(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeTransaction, big.NewInt(1), "foo"). - Return(nil) - }, - expectResults: []v2.Result{{ - ResponseType: v2.ActionDeleteMetadata, - }}, - }, - { - name: "error in the middle", - body: `[ - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo": "bar" - } - } - }, - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo2": "bar2" - } - } - }, - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo3": "bar3" - } - } - } - ]`, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo": "bar", - }). - Return(nil) - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo2": "bar2", - }). - Return(errors.New("unexpected error")) - }, - expectResults: []v2.Result{{ - ResponseType: v2.ActionAddMetadata, - }, { - ErrorCode: "INTERNAL", - ErrorDescription: "unexpected error", - ResponseType: "ERROR", - }}, - expectError: true, - }, - { - name: "error in the middle with continue on failure", - body: `[ - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo": "bar" - } - } - }, - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo2": "bar2" - } - } - }, - { - "action": "ADD_METADATA", - "data": { - "targetId": "world", - "targetType": "ACCOUNT", - "metadata": { - "foo3": "bar3" - } - } - } - ]`, - queryParams: map[string][]string{ - "continueOnFailure": {"true"}, - }, - expectations: func(mockLedger *backend.MockLedger) { - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo": "bar", - }). - Return(nil) - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo2": "bar2", - }). - Return(errors.New("unexpected error")) - mockLedger.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeAccount, "world", metadata.Metadata{ - "foo3": "bar3", - }). - Return(nil) - }, - expectResults: []v2.Result{{ - ResponseType: v2.ActionAddMetadata, - }, { - ResponseType: "ERROR", - ErrorCode: "INTERNAL", - ErrorDescription: "unexpected error", - }, { - ResponseType: v2.ActionAddMetadata, - }}, - expectError: true, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - backend, mock := newTestingBackend(t, true) - testCase.expectations(mock) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/_bulk", bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - if testCase.queryParams != nil { - req.URL.RawQuery = testCase.queryParams.Encode() - } - - router.ServeHTTP(rec, req) - - if testCase.expectError { - require.Equal(t, http.StatusBadRequest, rec.Code) - } else { - require.Equal(t, http.StatusOK, rec.Code) - } - - ret, _ := sharedapi.DecodeSingleResponse[[]v2.Result](t, rec.Body) - require.Equal(t, testCase.expectResults, ret) - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_config.go b/components/ledger/internal/api/v2/controllers_config.go deleted file mode 100644 index a9b610e17f..0000000000 --- a/components/ledger/internal/api/v2/controllers_config.go +++ /dev/null @@ -1,23 +0,0 @@ -package v2 - -import ( - _ "embed" - "net/http" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" -) - -type ConfigInfo struct { - Server string `json:"server"` - Version string `json:"version"` -} - -func getInfo(backend backend.Backend) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - sharedapi.RawOk(w, ConfigInfo{ - Server: "ledger", - Version: backend.GetVersion(), - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_config_test.go b/components/ledger/internal/api/v2/controllers_config_test.go deleted file mode 100644 index 5c10297e23..0000000000 --- a/components/ledger/internal/api/v2/controllers_config_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package v2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/formancehq/go-libs/auth" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/stretchr/testify/require" -) - -func TestGetInfo(t *testing.T) { - t.Parallel() - - backend, _ := newTestingBackend(t, false) - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - backend. - EXPECT(). - GetVersion(). - Return("latest") - - req := httptest.NewRequest(http.MethodGet, "/_info", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - info := v2.ConfigInfo{} - require.NoError(t, json.NewDecoder(rec.Body).Decode(&info)) - - require.EqualValues(t, v2.ConfigInfo{ - Server: "ledger", - Version: "latest", - }, info) -} diff --git a/components/ledger/internal/api/v2/controllers_create_ledger.go b/components/ledger/internal/api/v2/controllers_create_ledger.go deleted file mode 100644 index 84a05af6e3..0000000000 --- a/components/ledger/internal/api/v2/controllers_create_ledger.go +++ /dev/null @@ -1,45 +0,0 @@ -package v2 - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/ledger/internal/storage/driver" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/pkg/errors" -) - -func createLedger(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - configuration := driver.LedgerConfiguration{} - - data, err := io.ReadAll(r.Body) - if err != nil && !errors.Is(err, io.EOF) { - sharedapi.InternalServerError(w, r, err) - return - } - - if len(data) > 0 { - if err := json.Unmarshal(data, &configuration); err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - } - - if err := b.CreateLedger(r.Context(), chi.URLParam(r, "ledger"), configuration); err != nil { - switch { - case errors.Is(err, driver.ErrLedgerAlreadyExists): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - sharedapi.NoContent(w) - } -} diff --git a/components/ledger/internal/api/v2/controllers_create_ledger_test.go b/components/ledger/internal/api/v2/controllers_create_ledger_test.go deleted file mode 100644 index 286143dd36..0000000000 --- a/components/ledger/internal/api/v2/controllers_create_ledger_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package v2_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - - "github.com/formancehq/ledger/internal/storage/driver" -) - -func TestConfigureLedger(t *testing.T) { - t.Parallel() - - type testCase struct { - configuration driver.LedgerConfiguration - name string - } - - testCases := []testCase{ - { - name: "nominal", - configuration: driver.LedgerConfiguration{}, - }, - { - name: "with alternative bucket", - configuration: driver.LedgerConfiguration{ - Bucket: "bucket0", - }, - }, - { - name: "with metadata", - configuration: driver.LedgerConfiguration{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - b, _ := newTestingBackend(t, false) - router := v2.NewRouter(b, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - name := uuid.NewString() - b. - EXPECT(). - CreateLedger(gomock.Any(), name, testCase.configuration). - Return(nil) - - req := httptest.NewRequest(http.MethodPost, "/"+name, api.Buffer(t, testCase.configuration)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusNoContent, rec.Code) - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_get_ledger.go b/components/ledger/internal/api/v2/controllers_get_ledger.go deleted file mode 100644 index 23c01fe326..0000000000 --- a/components/ledger/internal/api/v2/controllers_get_ledger.go +++ /dev/null @@ -1,46 +0,0 @@ -package v2 - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/go-chi/chi/v5" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/formancehq/ledger/internal/storage/sqlutils" - "github.com/pkg/errors" -) - -func getLedger(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - configuration := driver.LedgerState{} - - data, err := io.ReadAll(r.Body) - if err != nil && !errors.Is(err, io.EOF) { - sharedapi.InternalServerError(w, r, err) - return - } - - if len(data) > 0 { - if err := json.Unmarshal(data, &configuration); err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - } - - ledger, err := b.GetLedger(r.Context(), chi.URLParam(r, "ledger")) - if err != nil { - switch { - case sqlutils.IsNotFoundError(err): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - sharedapi.Ok(w, ledger) - } -} diff --git a/components/ledger/internal/api/v2/controllers_get_ledger_test.go b/components/ledger/internal/api/v2/controllers_get_ledger_test.go deleted file mode 100644 index f0d998cbfb..0000000000 --- a/components/ledger/internal/api/v2/controllers_get_ledger_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package v2_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/ledger/internal/storage/systemstore" - - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetLedger(t *testing.T) { - t.Parallel() - - b, _ := newTestingBackend(t, false) - router := v2.NewRouter(b, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - name := uuid.NewString() - now := time.Now() - ledger := systemstore.Ledger{ - Name: name, - AddedAt: now, - Bucket: "bucket0", - } - b. - EXPECT(). - GetLedger(gomock.Any(), name). - Return(&ledger, nil) - - req := httptest.NewRequest(http.MethodGet, "/"+name, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - ledgerFromAPI, _ := api.DecodeSingleResponse[systemstore.Ledger](t, rec.Body) - require.Equal(t, ledger, ledgerFromAPI) -} diff --git a/components/ledger/internal/api/v2/controllers_get_logs.go b/components/ledger/internal/api/v2/controllers_get_logs.go deleted file mode 100644 index 3ba86b32b4..0000000000 --- a/components/ledger/internal/api/v2/controllers_get_logs.go +++ /dev/null @@ -1,52 +0,0 @@ -package v2 - -import ( - "fmt" - "net/http" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func getLogs(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query := ledgerstore.GetLogsQuery{} - - if r.URL.Query().Get(QueryKeyCursor) != "" { - err := bunpaginate.UnmarshalCursor(r.URL.Query().Get(QueryKeyCursor), &query) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, fmt.Errorf("invalid '%s' query param", QueryKeyCursor)) - return - } - } else { - var err error - - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - qb, err := getQueryBuilder(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - query = ledgerstore.NewGetLogsQuery(ledgerstore.PaginatedQueryOptions[any]{ - QueryBuilder: qb, - PageSize: pageSize, - }) - } - - cursor, err := l.GetLogs(r.Context(), query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.RenderCursor(w, *cursor) -} diff --git a/components/ledger/internal/api/v2/controllers_info.go b/components/ledger/internal/api/v2/controllers_info.go deleted file mode 100644 index 74bf4f779f..0000000000 --- a/components/ledger/internal/api/v2/controllers_info.go +++ /dev/null @@ -1,49 +0,0 @@ -package v2 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/migrations" - "github.com/formancehq/ledger/internal/api/backend" -) - -type Info struct { - Name string `json:"name"` - Storage StorageInfo `json:"storage"` -} - -type StorageInfo struct { - Migrations []migrations.Info `json:"migrations"` -} - -func getLedgerInfo(w http.ResponseWriter, r *http.Request) { - ledger := backend.LedgerFromContext(r.Context()) - - var err error - res := Info{ - Name: chi.URLParam(r, "ledger"), - Storage: StorageInfo{}, - } - res.Storage.Migrations, err = ledger.GetMigrationsInfo(r.Context()) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, res) -} - -func getStats(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - stats, err := l.Stats(r.Context()) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, stats) -} diff --git a/components/ledger/internal/api/v2/controllers_info_test.go b/components/ledger/internal/api/v2/controllers_info_test.go deleted file mode 100644 index 075f266449..0000000000 --- a/components/ledger/internal/api/v2/controllers_info_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package v2_test - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/migrations" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetLedgerInfo(t *testing.T) { - t.Parallel() - - backend, mock := newTestingBackend(t, false) - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - migrationInfo := []migrations.Info{ - { - Version: "1", - Name: "init", - State: "ready", - Date: time.Now().Add(-2 * time.Minute).Round(time.Second).UTC(), - }, - { - Version: "2", - Name: "fix", - State: "ready", - Date: time.Now().Add(-time.Minute).Round(time.Second).UTC(), - }, - } - - mock.EXPECT(). - GetMigrationsInfo(gomock.Any()). - Return(migrationInfo, nil) - - req := httptest.NewRequest(http.MethodGet, "/xxx/_info", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - info, ok := sharedapi.DecodeSingleResponse[v2.Info](t, rec.Body) - require.True(t, ok) - - require.EqualValues(t, v2.Info{ - Name: "xxx", - Storage: v2.StorageInfo{ - Migrations: migrationInfo, - }, - }, info) -} - -func TestGetStats(t *testing.T) { - t.Parallel() - - backend, mock := newTestingBackend(t, true) - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - expectedStats := engine.Stats{ - Transactions: 10, - Accounts: 5, - } - - mock.EXPECT(). - Stats(gomock.Any()). - Return(expectedStats, nil) - - req := httptest.NewRequest(http.MethodGet, "/xxx/stats", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - - stats, ok := sharedapi.DecodeSingleResponse[engine.Stats](t, rec.Body) - require.True(t, ok) - - require.EqualValues(t, expectedStats, stats) -} - -func TestGetLogs(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.PaginatedQueryOptions[any] - expectStatusCode int - expectedErrorCode string - } - - now := time.Now() - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil), - }, - { - name: "using start time", - body: fmt.Sprintf(`{"$gte": {"date": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil).WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using end time", - body: fmt.Sprintf(`{"$lt": {"date": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil). - WithQueryBuilder(query.Lt("date", now.Format(time.DateFormat))), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetLogsQuery(ledgerstore.NewPaginatedQueryOptions[any](nil)))}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions[any](nil), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"xxx"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ChainedLog]{ - Data: []ledger.ChainedLog{ - *ledger.NewTransactionLog(ledger.NewTransaction(), map[string]metadata.Metadata{}). - ChainLog(nil), - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetLogs(gomock.Any(), ledgerstore.NewGetLogsQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/logs", bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - if testCase.queryParams != nil { - req.URL.RawQuery = testCase.queryParams.Encode() - } - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ChainedLog](t, rec.Body) - - cursorData, err := json.Marshal(cursor) - require.NoError(t, err) - - cursorAsMap := make(map[string]any) - require.NoError(t, json.Unmarshal(cursorData, &cursorAsMap)) - - expectedCursorData, err := json.Marshal(expectedCursor) - require.NoError(t, err) - - expectedCursorAsMap := make(map[string]any) - require.NoError(t, json.Unmarshal(expectedCursorData, &expectedCursorAsMap)) - - require.Equal(t, expectedCursorAsMap, cursorAsMap) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_ledgers.go b/components/ledger/internal/api/v2/controllers_ledgers.go deleted file mode 100644 index 67e7d0212e..0000000000 --- a/components/ledger/internal/api/v2/controllers_ledgers.go +++ /dev/null @@ -1,75 +0,0 @@ -package v2 - -import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/metadata" - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/storage/systemstore" - - "github.com/formancehq/ledger/internal/api/backend" -) - -func listLedgers(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - query, err := bunpaginate.Extract[systemstore.ListLedgersQuery](r, func() (*systemstore.ListLedgersQuery, error) { - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - return nil, err - } - - return pointer.For(systemstore.NewListLedgersQuery(pageSize)), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - ledgers, err := b.ListLedgers(r.Context(), *query) - if err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.RenderCursor(w, *ledgers) - } -} - -func updateLedgerMetadata(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - m := metadata.Metadata{} - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - sharedapi.BadRequest(w, "VALIDATION", errors.New("invalid format")) - return - } - - if err := b.UpdateLedgerMetadata(r.Context(), chi.URLParam(r, "ledger"), m); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) - } -} - -func deleteLedgerMetadata(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := b.DeleteLedgerMetadata(r.Context(), chi.URLParam(r, "ledger"), chi.URLParam(r, "key")); err != nil { - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.NoContent(w) - } -} diff --git a/components/ledger/internal/api/v2/controllers_ledgers_test.go b/components/ledger/internal/api/v2/controllers_ledgers_test.go deleted file mode 100644 index 8dfedef4d4..0000000000 --- a/components/ledger/internal/api/v2/controllers_ledgers_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package v2_test - -import ( - "net/http" - "net/http/httptest" - "testing" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestUpdateLedgerMetadata(t *testing.T) { - ctx := logging.TestingContext() - - name := uuid.NewString() - metadata := map[string]string{ - "foo": "bar", - } - backend, _ := newTestingBackend(t, false) - backend.EXPECT(). - UpdateLedgerMetadata(gomock.Any(), name, metadata). - Return(nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPut, "/"+name+"/metadata", sharedapi.Buffer(t, metadata)) - req = req.WithContext(ctx) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusNoContent, rec.Code) -} - -func TestDeleteLedgerMetadata(t *testing.T) { - ctx := logging.TestingContext() - - name := uuid.NewString() - backend, _ := newTestingBackend(t, false) - backend.EXPECT(). - DeleteLedgerMetadata(gomock.Any(), name, "foo"). - Return(nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodDelete, "/"+name+"/metadata/foo", nil) - req = req.WithContext(ctx) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusNoContent, rec.Code) -} diff --git a/components/ledger/internal/api/v2/controllers_transactions.go b/components/ledger/internal/api/v2/controllers_transactions.go deleted file mode 100644 index c33dc5f59d..0000000000 --- a/components/ledger/internal/api/v2/controllers_transactions.go +++ /dev/null @@ -1,271 +0,0 @@ -package v2 - -import ( - "encoding/json" - "fmt" - "math/big" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/engine" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/machine" - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - "github.com/pkg/errors" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func countTransactions(w http.ResponseWriter, r *http.Request) { - - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - count, err := backend.LedgerFromContext(r.Context()). - CountTransactions(r.Context(), ledgerstore.NewGetTransactionsQuery(*options)) - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - w.Header().Set("Count", fmt.Sprint(count)) - sharedapi.NoContent(w) -} - -func getTransactions(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - query, err := bunpaginate.Extract[ledgerstore.GetTransactionsQuery](r, func() (*ledgerstore.GetTransactionsQuery, error) { - options, err := getPaginatedQueryOptionsOfPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - q := ledgerstore.NewGetTransactionsQuery(*options) - - if r.URL.Query().Get("order") == "effective" { - q.Column = "timestamp" - } - if r.URL.Query().Get("reverse") == "true" { - q.Order = bunpaginate.OrderAsc - } - - return pointer.For(q), nil - }) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetTransactions(r.Context(), *query) - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.RenderCursor(w, *cursor) -} - -func postTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - payload := ledger.TransactionRequest{} - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction format")) - return - } - - if len(payload.Postings) > 0 && payload.Script.Plain != "" { - sharedapi.BadRequest(w, ErrValidation, errors.New("cannot pass postings and numscript in the same request")) - return - } - - ctx, _ := contextutil.Detached(r.Context()) - - res, err := l.CreateTransaction(ctx, getCommandParameters(r), *payload.ToRunScript()) - if err != nil { - switch { - case engine.IsCommandError(err): - switch { - case command.IsErrMachine(err): - switch { - case machine.IsInsufficientFundError(err): - sharedapi.BadRequest(w, ErrInsufficientFund, err) - return - case machine.IsMetadataOverride(err): - sharedapi.BadRequest(w, ErrMetadataOverride, err) - return - } - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeConflict): - sharedapi.BadRequest(w, ErrConflict, err) - return - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeNoPostings): - sharedapi.BadRequest(w, ErrNoPostings, err) - return - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeNoScript): - sharedapi.BadRequest(w, ErrNoScript, err) - return - case command.IsInvalidTransactionError(err, command.ErrInvalidTransactionCodeCompilationFailed): - sharedapi.BadRequestWithDetails(w, ErrCompilationFailed, err, backend.EncodeLink(errors.Cause(err).Error())) - return - } - } - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Ok(w, res) -} - -func getTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - txId, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction id")) - return - } - - query := ledgerstore.NewGetTransactionQuery(txId) - if collectionutils.Contains(r.URL.Query()["expand"], "volumes") { - query = query.WithExpandVolumes() - } - if collectionutils.Contains(r.URL.Query()["expand"], "effectiveVolumes") { - query = query.WithExpandEffectiveVolumes() - } - - pitFilter, err := getPITFilter(r) - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - query.PITFilter = *pitFilter - - tx, err := l.GetTransactionWithVolumes(r.Context(), query) - if err != nil { - switch { - case storageerrors.IsNotFoundError(err): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.Ok(w, tx) -} - -func revertTransaction(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - transactionID, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.NotFound(w, errors.New("invalid transaction ID")) - return - } - - tx, err := l.RevertTransaction(r.Context(), getCommandParameters(r), transactionID, - sharedapi.QueryParamBool(r, "force"), - sharedapi.QueryParamBool(r, "atEffectiveDate"), - ) - if err != nil { - switch { - case engine.IsCommandError(err): - switch { - case command.IsErrMachine(err): - switch { - case machine.IsInsufficientFundError(err): - sharedapi.BadRequest(w, ErrInsufficientFund, err) - return - } - case command.IsRevertError(err, command.ErrRevertTransactionCodeNotFound): - sharedapi.NotFound(w, err) - return - case command.IsRevertError(err, command.ErrRevertTransactionCodeOccurring): - sharedapi.BadRequest(w, ErrRevertOccurring, err) - return - case command.IsRevertError(err, command.ErrRevertTransactionCodeAlreadyReverted): - sharedapi.BadRequest(w, ErrAlreadyRevert, err) - return - } - } - sharedapi.InternalServerError(w, r, err) - return - } - - sharedapi.Created(w, tx) -} - -func postTransactionMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - var m metadata.Metadata - if err := json.NewDecoder(r.Body).Decode(&m); err != nil { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid metadata format")) - return - } - - txID, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.NotFound(w, errors.New("invalid transaction ID")) - return - } - - if err := l.SaveMeta(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeTransaction, txID, m); err != nil { - switch { - case command.IsSaveMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.NoContent(w) -} - -func deleteTransactionMetadata(w http.ResponseWriter, r *http.Request) { - l := backend.LedgerFromContext(r.Context()) - - transactionID, ok := big.NewInt(0).SetString(chi.URLParam(r, "id"), 10) - if !ok { - sharedapi.BadRequest(w, ErrValidation, errors.New("invalid transaction ID")) - return - } - - metadataKey := chi.URLParam(r, "key") - - if err := l.DeleteMetadata(r.Context(), getCommandParameters(r), ledger.MetaTargetTypeTransaction, transactionID, metadataKey); err != nil { - switch { - case command.IsSaveMetaError(err, command.ErrSaveMetaCodeTransactionNotFound): - sharedapi.NotFound(w, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.NoContent(w) -} diff --git a/components/ledger/internal/api/v2/controllers_transactions_test.go b/components/ledger/internal/api/v2/controllers_transactions_test.go deleted file mode 100644 index cbc1469945..0000000000 --- a/components/ledger/internal/api/v2/controllers_transactions_test.go +++ /dev/null @@ -1,1002 +0,0 @@ -package v2_test - -import ( - "bytes" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/ledger/internal/api/backend" - "github.com/pkg/errors" - - "github.com/formancehq/ledger/internal/engine" - - "github.com/formancehq/ledger/internal/machine" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestPostTransactions(t *testing.T) { - type testCase struct { - name string - expectedDryRun bool - expectedRunScript ledger.RunScript - returnError error - payload any - expectedStatusCode int - expectedErrorCode string - expectedErrorDetails string - queryParams url.Values - expectEngineCall bool - } - - testCases := []testCase{ - { - name: "using plain numscript", - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `XXX`, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `XXX`, - Vars: map[string]string{}, - }, - }, - expectEngineCall: true, - }, - { - name: "using plain numscript with variables", - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - }, - Vars: map[string]any{ - "val": "USD/2 100", - }, - }, - }, - expectEngineCall: true, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - Vars: map[string]string{ - "val": "USD/2 100", - }, - }, - }, - }, - { - name: "using plain numscript with variables (legacy format)", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - }, - Vars: map[string]any{ - "val": map[string]any{ - "asset": "USD/2", - "amount": 100, - }, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars { - monetary $val - } - - send $val ( - source = @world - destination = @bank - )`, - Vars: map[string]string{ - "val": "USD/2 100", - }, - }, - }, - }, - { - name: "using plain numscript and dry run", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `send ( - source = @world - destination = @bank - )`, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `send ( - source = @world - destination = @bank - )`, - Vars: map[string]string{}, - }, - }, - expectedDryRun: true, - queryParams: url.Values{ - "dryRun": []string{"true"}, - }, - }, - { - name: "using JSON postings", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - }, - }, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), false), - }, - { - name: "using JSON postings and dry run", - expectEngineCall: true, - queryParams: url.Values{ - "dryRun": []string{"true"}, - }, - payload: ledger.TransactionRequest{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - }, - }, - expectedDryRun: true, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), false), - }, - { - name: "no postings or script", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.TxToScriptData(ledger.NewTransactionData(), false).Script, - }, - Metadata: map[string]string{}, - }, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData(), false), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrNoPostings, - returnError: engine.NewCommandError(command.NewErrNoPostings()), - }, - { - name: "postings and script", - payload: ledger.TransactionRequest{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: ` - send [COIN 100] ( - source = @world - destination = @bob - )`, - }, - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "using invalid body", - payload: "not a valid payload", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "with insufficient funds", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `XXX`, - }, - }, - }, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `XXX`, - Vars: map[string]string{}, - }, - }, - returnError: engine.NewCommandError(command.NewErrMachine(&machine.ErrInsufficientFund{})), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrInsufficientFund, - }, - { - name: "using JSON postings and negative amount", - payload: ledger.TransactionRequest{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "bank", "USD", big.NewInt(-100)), - }, - }, - expectEngineCall: true, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrCompilationFailed, - expectedRunScript: ledger.TxToScriptData(ledger.NewTransactionData().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(-100)), - ), false), - expectedErrorDetails: backend.EncodeLink(`compilation failed`), - returnError: engine.NewCommandError( - command.NewErrInvalidTransaction(command.ErrInvalidTransactionCodeCompilationFailed, errors.New("compilation failed")), - ), - }, - { - expectEngineCall: true, - name: "numscript and negative amount", - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `send [COIN -100] ( - source = @world - destination = @bob - )`, - }, - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrCompilationFailed, - expectedErrorDetails: backend.EncodeLink("compilation failed"), - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `send [COIN -100] ( - source = @world - destination = @bob - )`, - Vars: map[string]string{}, - }, - }, - returnError: engine.NewCommandError( - command.NewErrInvalidTransaction(command.ErrInvalidTransactionCodeCompilationFailed, errors.New("compilation failed")), - ), - }, - { - name: "numscript and compilation failed", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `send [COIN XXX] ( - source = @world - destination = @bob - )`, - }, - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrCompilationFailed, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `send [COIN XXX] ( - source = @world - destination = @bob - )`, - Vars: map[string]string{}, - }, - }, - expectedErrorDetails: backend.EncodeLink("compilation failed"), - returnError: engine.NewCommandError( - command.NewErrCompilationFailed(fmt.Errorf("compilation failed")), - ), - }, - { - name: "numscript and no postings", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `vars {}`, - }, - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrNoPostings, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars {}`, - Vars: map[string]string{}, - }, - }, - returnError: engine.NewCommandError( - command.NewErrNoPostings(), - ), - }, - { - name: "numscript and conflict", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `vars {}`, - }, - }, - Reference: "xxx", - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrConflict, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `vars {}`, - Vars: map[string]string{}, - }, - Reference: "xxx", - }, - returnError: engine.NewCommandError( - command.NewErrConflict(), - ), - }, - { - name: "numscript and metadata override", - expectEngineCall: true, - payload: ledger.TransactionRequest{ - Script: ledger.ScriptV1{ - Script: ledger.Script{ - Plain: `send [COIN 100] ( - source = @world - destination = @bob - ) - set_tx_meta("foo", "bar")`, - }, - }, - Reference: "xxx", - Metadata: map[string]string{ - "foo": "baz", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrMetadataOverride, - expectedRunScript: ledger.RunScript{ - Script: ledger.Script{ - Plain: `send [COIN 100] ( - source = @world - destination = @bob - ) - set_tx_meta("foo", "bar")`, - Vars: map[string]string{}, - }, - Reference: "xxx", - Metadata: map[string]string{ - "foo": "baz", - }, - }, - returnError: engine.NewCommandError( - command.NewErrMachine(&machine.ErrMetadataOverride{}), - ), - }, - } - - for _, testCase := range testCases { - tc := testCase - t.Run(tc.name, func(t *testing.T) { - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - expectedTx := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectEngineCall { - expect := mockLedger.EXPECT(). - CreateTransaction(gomock.Any(), command.Parameters{ - DryRun: tc.expectedDryRun, - }, testCase.expectedRunScript) - - if tc.returnError == nil { - expect.Return(expectedTx, nil) - } else { - expect.Return(nil, tc.returnError) - } - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions", sharedapi.Buffer(t, testCase.payload)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - tx, ok := sharedapi.DecodeSingleResponse[ledger.Transaction](t, rec.Body) - require.True(t, ok) - require.Equal(t, *expectedTx, tx) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - require.EqualValues(t, testCase.expectedErrorDetails, err.Details) - - } - }) - } -} - -func TestPostTransactionMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - expectStatusCode int - expectedErrorCode string - body any - } - - testCases := []testCase{ - { - name: "nominal", - body: metadata.Metadata{ - "foo": "bar", - }, - }, - { - name: "invalid body", - body: "invalid - not an object", - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mock := newTestingBackend(t, true) - if testCase.expectStatusCode == http.StatusNoContent { - mock.EXPECT(). - SaveMeta(gomock.Any(), command.Parameters{}, ledger.MetaTargetTypeTransaction, big.NewInt(0), testCase.body). - Return(nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions/0/metadata", sharedapi.Buffer(t, testCase.body)) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode >= 300 || testCase.expectStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetTransaction(t *testing.T) { - t.Parallel() - - now := time.Now() - - tx := ledger.ExpandTransaction( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - nil, - ) - - query := ledgerstore.NewGetTransactionQuery(big.NewInt(0)) - query.PIT = &now - - backend, mock := newTestingBackend(t, true) - mock.EXPECT(). - GetTransactionWithVolumes(gomock.Any(), query). - Return(&tx, nil) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/transactions/0?pit="+now.Format(time.RFC3339Nano), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, http.StatusOK, rec.Code) - response, _ := sharedapi.DecodeSingleResponse[ledger.ExpandedTransaction](t, rec.Body) - require.Equal(t, tx, response) -} - -func TestGetTransactions(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.GetTransactionsQuery - expectStatusCode int - expectedErrorCode string - } - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - })), - }, - { - name: "using metadata", - body: `{"$match": {"metadata[roles]": "admin"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Match("metadata[roles]", "admin"))), - }, - { - name: "using startTime", - body: fmt.Sprintf(`{"$gte": {"start_time": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Gte("start_time", now.Format(time.DateFormat)))), - }, - { - name: "using endTime", - body: fmt.Sprintf(`{"$lte": {"end_time": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Lte("end_time", now.Format(time.DateFormat)))), - }, - { - name: "using account", - body: `{"$match": {"account": "xxx"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Match("account", "xxx"))), - }, - { - name: "using reference", - body: `{"$match": {"reference": "xxx"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Match("reference", "xxx"))), - }, - { - name: "using destination", - body: `{"$match": {"destination": "xxx"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Match("destination", "xxx"))), - }, - { - name: "using source", - body: `{"$match": {"source": "xxx"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Match("source", "xxx"))), - }, - { - name: "using empty cursor", - queryParams: url.Values{ - "cursor": []string{bunpaginate.EncodeCursor(ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{})))}, - }, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{}, - })), - }, - { - name: "using invalid cursor", - queryParams: url.Values{ - "cursor": []string{"XXX"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "invalid page size", - queryParams: url.Values{ - "pageSize": []string{"nan"}, - }, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "page size over maximum", - queryParams: url.Values{ - "pageSize": []string{"1000000"}, - }, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithPageSize(v2.MaxPageSize)), - }, - { - name: "using cursor", - queryParams: url.Values{ - "cursor": []string{"eyJwYWdlU2l6ZSI6MTUsImJvdHRvbSI6bnVsbCwiY29sdW1uIjoiaWQiLCJwYWdpbmF0aW9uSUQiOm51bGwsIm9yZGVyIjoxLCJmaWx0ZXJzIjp7InFiIjp7fSwicGFnZVNpemUiOjE1LCJvcHRpb25zIjp7InBpdCI6bnVsbCwidm9sdW1lcyI6ZmFsc2UsImVmZmVjdGl2ZVZvbHVtZXMiOmZhbHNlfX0sInJldmVyc2UiOmZhbHNlfQ"}, - }, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{})), - }, - { - name: "using $exists metadata filter", - body: `{"$exists": {"metadata": "foo"}}`, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - }). - WithQueryBuilder(query.Exists("metadata", "foo"))), - }, - { - name: "paginate using effective order", - queryParams: map[string][]string{"order": {"effective"}}, - expectQuery: ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &now, - }, - })). - WithColumn("timestamp"), - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.ExpandedTransaction]{ - Data: []ledger.ExpandedTransaction{ - ledger.ExpandTransaction( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - nil, - ), - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetTransactions(gomock.Any(), testCase.expectQuery). - Return(&expectedCursor, nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/transactions", bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - params := url.Values{} - if testCase.queryParams != nil { - params = testCase.queryParams - } - params.Set("pit", now.Format(time.RFC3339Nano)) - req.URL.RawQuery = params.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.ExpandedTransaction](t, rec.Body) - require.Equal(t, expectedCursor, *cursor) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - - } - }) - } -} - -func TestCountTransactions(t *testing.T) { - t.Parallel() - - before := time.Now() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes] - expectStatusCode int - expectedErrorCode string - } - now := time.Now() - - testCases := []testCase{ - { - name: "nominal", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }), - }, - { - name: "using metadata", - body: `{"$match": {"metadata[roles]": "admin"}}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("metadata[roles]", "admin")), - }, - { - name: "using startTime", - body: fmt.Sprintf(`{"$gte": {"date": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using endTime", - body: fmt.Sprintf(`{"$gte": {"date": "%s"}}`, now.Format(time.DateFormat)), - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Gte("date", now.Format(time.DateFormat))), - }, - { - name: "using account", - body: `{"$match": {"account": "xxx"}}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("account", "xxx")), - }, - { - name: "using reference", - body: `{"$match": {"reference": "xxx"}}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("reference", "xxx")), - }, - { - name: "using destination", - body: `{"$match": {"destination": "xxx"}}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("destination", "xxx")), - }, - { - name: "using source", - body: `{"$match": {"source": "xxx"}}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - }, - }). - WithQueryBuilder(query.Match("source", "xxx")), - }, - } - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusNoContent - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - CountTransactions(gomock.Any(), ledgerstore.NewGetTransactionsQuery(testCase.expectQuery)). - Return(10, nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodHead, "/xxx/transactions?pit="+before.Format(time.RFC3339Nano), bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - if testCase.queryParams != nil { - req.URL.RawQuery = testCase.queryParams.Encode() - } - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - require.Equal(t, "10", rec.Header().Get("Count")) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - - } - }) - } -} - -func TestRevert(t *testing.T) { - t.Parallel() - type testCase struct { - name string - queryParams url.Values - returnTx *ledger.Transaction - returnErr error - expectForce bool - expectStatusCode int - expectErrorCode string - } - - testCases := []testCase{ - { - name: "nominal", - returnTx: ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - }, - { - name: "force revert", - returnTx: ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - expectForce: true, - queryParams: map[string][]string{"force": {"true"}}, - }, - { - name: "with insufficient fund", - returnErr: engine.NewCommandError( - command.NewErrMachine(&machine.ErrInsufficientFund{}), - ), - expectStatusCode: http.StatusBadRequest, - expectErrorCode: v2.ErrInsufficientFund, - }, - { - name: "with revert already occurring", - returnErr: engine.NewCommandError( - command.NewErrRevertTransactionOccurring(), - ), - expectStatusCode: http.StatusBadRequest, - expectErrorCode: v2.ErrRevertOccurring, - }, - { - name: "with already revert", - returnErr: engine.NewCommandError( - command.NewErrRevertTransactionAlreadyReverted(), - ), - expectStatusCode: http.StatusBadRequest, - expectErrorCode: v2.ErrAlreadyRevert, - }, - { - name: "with transaction not found", - returnErr: engine.NewCommandError( - command.NewErrRevertTransactionNotFound(), - ), - expectStatusCode: http.StatusNotFound, - expectErrorCode: sharedapi.ErrorCodeNotFound, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - backend, mockLedger := newTestingBackend(t, true) - mockLedger. - EXPECT(). - RevertTransaction(gomock.Any(), command.Parameters{}, big.NewInt(0), tc.expectForce, false). - Return(tc.returnTx, tc.returnErr) - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodPost, "/xxx/transactions/0/revert", nil) - if tc.queryParams != nil { - req.URL.RawQuery = tc.queryParams.Encode() - } - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - if tc.expectStatusCode == 0 { - require.Equal(t, http.StatusCreated, rec.Code) - tx, ok := sharedapi.DecodeSingleResponse[ledger.Transaction](t, rec.Body) - require.True(t, ok) - require.Equal(t, *tc.returnTx, tx) - } else { - require.Equal(t, tc.expectStatusCode, rec.Code) - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, tc.expectErrorCode, err.ErrorCode) - - } - }) - } -} diff --git a/components/ledger/internal/api/v2/controllers_volumes.go b/components/ledger/internal/api/v2/controllers_volumes.go deleted file mode 100644 index 4316c44917..0000000000 --- a/components/ledger/internal/api/v2/controllers_volumes.go +++ /dev/null @@ -1,49 +0,0 @@ -package v2 - -import ( - "net/http" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/bun/bunpaginate" -) - -func getVolumesWithBalances(w http.ResponseWriter, r *http.Request) { - - l := backend.LedgerFromContext(r.Context()) - - query, err := bunpaginate.Extract[ledgerstore.GetVolumesWithBalancesQuery](r, func() (*ledgerstore.GetVolumesWithBalancesQuery, error) { - options, err := getPaginatedQueryOptionsOfFiltersForVolumes(r) - if err != nil { - return nil, err - } - - getVolumesWithBalancesQuery := ledgerstore.NewGetVolumesWithBalancesQuery(*options) - return pointer.For(getVolumesWithBalancesQuery), nil - - }) - - if err != nil { - sharedapi.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := l.GetVolumesWithBalances(r.Context(), *query) - - if err != nil { - switch { - case ledgerstore.IsErrInvalidQuery(err): - sharedapi.BadRequest(w, ErrValidation, err) - default: - sharedapi.InternalServerError(w, r, err) - } - return - } - - sharedapi.RenderCursor(w, *cursor) - -} diff --git a/components/ledger/internal/api/v2/controllers_volumes_test.go b/components/ledger/internal/api/v2/controllers_volumes_test.go deleted file mode 100644 index 8ff837c5e9..0000000000 --- a/components/ledger/internal/api/v2/controllers_volumes_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package v2_test - -import ( - "bytes" - - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/time" - - sharedapi "github.com/formancehq/go-libs/api" - ledger "github.com/formancehq/ledger/internal" - v2 "github.com/formancehq/ledger/internal/api/v2" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - - "github.com/formancehq/go-libs/query" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetVolumes(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - body string - expectQuery ledgerstore.PaginatedQueryOptions[ledgerstore.FiltersForVolumes] - expectStatusCode int - expectedErrorCode string - } - before := time.Now() - zero := time.Time{} - - testCases := []testCase{ - { - name: "basic", - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - - UseInsertionDate: false, - }). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using metadata", - body: `{"$match": { "metadata[roles]": "admin" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - }). - WithQueryBuilder(query.Match("metadata[roles]", "admin")). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using account", - body: `{"$match": { "account": "foo" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - }). - WithQueryBuilder(query.Match("account", "foo")). - WithPageSize(v2.DefaultPageSize), - }, - { - name: "using invalid query payload", - body: `[]`, - expectStatusCode: http.StatusBadRequest, - expectedErrorCode: v2.ErrValidation, - }, - { - name: "using pit", - queryParams: url.Values{ - "pit": []string{before.Format(time.RFC3339Nano)}, - "groupBy": []string{"3"}, - }, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - GroupLvl: 3, - }).WithPageSize(v2.DefaultPageSize), - }, - { - name: "using Exists metadata filter", - body: `{"$exists": { "metadata": "foo" }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - }).WithPageSize(v2.DefaultPageSize).WithQueryBuilder(query.Exists("metadata", "foo")), - }, - { - name: "using balance filter", - body: `{"$gte": { "balance[EUR]": 50 }}`, - expectQuery: ledgerstore.NewPaginatedQueryOptions(ledgerstore.FiltersForVolumes{ - PITFilter: ledgerstore.PITFilter{ - PIT: &before, - OOT: &zero, - }, - }).WithQueryBuilder(query.Gte("balance[EUR]", float64(50))). - WithPageSize(v2.DefaultPageSize), - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectStatusCode == 0 { - testCase.expectStatusCode = http.StatusOK - } - - expectedCursor := bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount]{ - Data: []ledger.VolumesWithBalanceByAssetByAccount{ - { - Account: "user:1", - Asset: "eur", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(1), - Output: big.NewInt(1), - Balance: big.NewInt(0), - }, - }, - }, - } - - backend, mockLedger := newTestingBackend(t, true) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - mockLedger.EXPECT(). - GetVolumesWithBalances(gomock.Any(), ledgerstore.NewGetVolumesWithBalancesQuery(testCase.expectQuery)). - Return(&expectedCursor, nil) - } - - router := v2.NewRouter(backend, nil, metrics.NewNoOpRegistry(), auth.NewNoAuth(), testing.Verbose()) - - req := httptest.NewRequest(http.MethodGet, "/xxx/volumes?endTime="+before.Format(time.RFC3339Nano), bytes.NewBufferString(testCase.body)) - rec := httptest.NewRecorder() - params := url.Values{} - if testCase.queryParams != nil { - params = testCase.queryParams - } - - params.Set("endTime", before.Format(time.RFC3339Nano)) - req.URL.RawQuery = params.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectStatusCode, rec.Code) - if testCase.expectStatusCode < 300 && testCase.expectStatusCode >= 200 { - cursor := sharedapi.DecodeCursorResponse[ledger.VolumesWithBalanceByAssetByAccount](t, rec.Body) - require.Equal(t, expectedCursor, *cursor) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/ledger/internal/api/v2/errors.go b/components/ledger/internal/api/v2/errors.go deleted file mode 100644 index dbfae245a3..0000000000 --- a/components/ledger/internal/api/v2/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -package v2 - -const ( - ErrConflict = "CONFLICT" - ErrInsufficientFund = "INSUFFICIENT_FUND" - ErrValidation = "VALIDATION" - ErrRevertOccurring = "REVERT_OCCURRING" - ErrAlreadyRevert = "ALREADY_REVERT" - ErrNoPostings = "NO_POSTINGS" - ErrCompilationFailed = "COMPILATION_FAILED" - ErrMetadataOverride = "METADATA_OVERRIDE" - ErrNoScript = "NO_SCRIPT" -) diff --git a/components/ledger/internal/api/v2/middlewares_metrics.go b/components/ledger/internal/api/v2/middlewares_metrics.go deleted file mode 100644 index 23cfa6ac44..0000000000 --- a/components/ledger/internal/api/v2/middlewares_metrics.go +++ /dev/null @@ -1,55 +0,0 @@ -package v2 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -type statusRecorder struct { - http.ResponseWriter - Status int -} - -func newStatusRecorder(w http.ResponseWriter) *statusRecorder { - return &statusRecorder{ResponseWriter: w} -} - -func (r *statusRecorder) WriteHeader(status int) { - r.Status = status - r.ResponseWriter.WriteHeader(status) -} - -func MetricsMiddleware(globalMetricsRegistry metrics.GlobalRegistry) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - attrs := []attribute.KeyValue{} - - ctx := r.Context() - name := chi.URLParam(r, "ledger") - if name != "" { - attrs = append(attrs, attribute.String("ledger", name)) - } - - recorder := newStatusRecorder(w) - - start := time.Now() - h.ServeHTTP(recorder, r) - latency := time.Since(start) - - attrs = append(attrs, - attribute.String("route", chi.RouteContext(r.Context()).RoutePattern())) - - globalMetricsRegistry.APILatencies().Record(ctx, latency.Milliseconds(), metric.WithAttributes(attrs...)) - - attrs = append(attrs, attribute.Int("status", recorder.Status)) - globalMetricsRegistry.StatusCodes().Add(ctx, 1, metric.WithAttributes(attrs...)) - }) - } -} diff --git a/components/ledger/internal/api/v2/query.go b/components/ledger/internal/api/v2/query.go deleted file mode 100644 index addd057624..0000000000 --- a/components/ledger/internal/api/v2/query.go +++ /dev/null @@ -1,36 +0,0 @@ -package v2 - -import ( - "net/http" - "strings" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/ledger/internal/engine/command" - "github.com/pkg/errors" -) - -const ( - MaxPageSize = bunpaginate.MaxPageSize - DefaultPageSize = bunpaginate.QueryDefaultPageSize - - QueryKeyCursor = "cursor" -) - -var ( - ErrInvalidBalanceOperator = errors.New( - "invalid parameter 'balanceOperator', should be one of 'e, ne, gt, gte, lt, lte'") - ErrInvalidStartTime = errors.New("invalid 'startTime' query param") - ErrInvalidEndTime = errors.New("invalid 'endTime' query param") -) - -func getCommandParameters(r *http.Request) command.Parameters { - dryRunAsString := r.URL.Query().Get("dryRun") - dryRun := strings.ToUpper(dryRunAsString) == "YES" || strings.ToUpper(dryRunAsString) == "TRUE" || dryRunAsString == "1" - - return command.Parameters{ - DryRun: dryRun, - IdempotencyKey: api.IdempotencyKeyFromRequest(r), - } -} diff --git a/components/ledger/internal/api/v2/routes.go b/components/ledger/internal/api/v2/routes.go deleted file mode 100644 index 5c13204c93..0000000000 --- a/components/ledger/internal/api/v2/routes.go +++ /dev/null @@ -1,99 +0,0 @@ -package v2 - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/ledger/internal/api/backend" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" -) - -func NewRouter( - b backend.Backend, - healthController *health.HealthController, - globalMetricsRegistry metrics.GlobalRegistry, - authenticator auth.Authenticator, - debug bool, -) chi.Router { - router := chi.NewMux() - - router.Use( - cors.New(cors.Options{ - AllowOriginFunc: func(r *http.Request, origin string) bool { - return true - }, - AllowCredentials: true, - }).Handler, - MetricsMiddleware(globalMetricsRegistry), - middleware.Recoverer, - ) - - router.Get("/_healthcheck", healthController.Check) - router.Get("/_info", getInfo(b)) - - router.Group(func(router chi.Router) { - router.Use(auth.Middleware(authenticator)) - router.Use(service.OTLPMiddleware("ledger", debug)) - - router.Get("/", listLedgers(b)) - router.Route("/{ledger}", func(router chi.Router) { - router.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - trace. - SpanFromContext(r.Context()). - SetAttributes(attribute.String("ledger", chi.URLParam(r, "ledger"))) - handler.ServeHTTP(w, r) - }) - }) - router.Post("/", createLedger(b)) - router.Get("/", getLedger(b)) - router.Put("/metadata", updateLedgerMetadata(b)) - router.Delete("/metadata/{key}", deleteLedgerMetadata(b)) - - router.With(backend.LedgerMiddleware(b, []string{"/_info"})).Group(func(router chi.Router) { - router.Post("/_bulk", bulkHandler) - - // LedgerController - router.Get("/_info", getLedgerInfo) - router.Get("/stats", getStats) - router.Get("/logs", getLogs) - router.Post("/logs/import", importLogs) - router.Post("/logs/export", exportLogs) - - // AccountController - router.Get("/accounts", getAccounts) - router.Head("/accounts", countAccounts) - router.Get("/accounts/{address}", getAccount) - router.Post("/accounts/{address}/metadata", postAccountMetadata) - router.Delete("/accounts/{address}/metadata/{key}", deleteAccountMetadata) - - // TransactionController - router.Get("/transactions", getTransactions) - router.Head("/transactions", countTransactions) - - router.Post("/transactions", postTransaction) - - router.Get("/transactions/{id}", getTransaction) - router.Post("/transactions/{id}/revert", revertTransaction) - router.Post("/transactions/{id}/metadata", postTransactionMetadata) - router.Delete("/transactions/{id}/metadata/{key}", deleteTransactionMetadata) - - router.Get("/aggregate/balances", getBalancesAggregated) - - router.Get("/volumes", getVolumesWithBalances) - }) - }) - }) - - return router -} diff --git a/components/ledger/internal/api/v2/utils.go b/components/ledger/internal/api/v2/utils.go deleted file mode 100644 index 22facac1a2..0000000000 --- a/components/ledger/internal/api/v2/utils.go +++ /dev/null @@ -1,159 +0,0 @@ -package v2 - -import ( - "io" - "net/http" - "strconv" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -func getPITOOTFilter(r *http.Request) (*ledgerstore.PITFilter, error) { - pitString := r.URL.Query().Get("endTime") - ootString := r.URL.Query().Get("startTime") - - pit := time.Now() - oot := time.Time{} - - if pitString != "" { - var err error - pit, err = time.ParseTime(pitString) - if err != nil { - return nil, err - } - } - - if ootString != "" { - var err error - oot, err = time.ParseTime(ootString) - if err != nil { - return nil, err - } - } - - return &ledgerstore.PITFilter{ - PIT: &pit, - OOT: &oot, - }, nil -} - -func getPITFilter(r *http.Request) (*ledgerstore.PITFilter, error) { - pitString := r.URL.Query().Get("pit") - - pit := time.Now() - - if pitString != "" { - var err error - pit, err = time.ParseTime(pitString) - if err != nil { - return nil, err - } - } - - return &ledgerstore.PITFilter{ - PIT: &pit, - }, nil -} - -func getPITFilterWithVolumes(r *http.Request) (*ledgerstore.PITFilterWithVolumes, error) { - pit, err := getPITFilter(r) - if err != nil { - return nil, err - } - return &ledgerstore.PITFilterWithVolumes{ - PITFilter: *pit, - ExpandVolumes: collectionutils.Contains(r.URL.Query()["expand"], "volumes"), - ExpandEffectiveVolumes: collectionutils.Contains(r.URL.Query()["expand"], "effectiveVolumes"), - }, nil -} - -func getFiltersForVolumes(r *http.Request) (*ledgerstore.FiltersForVolumes, error) { - pit, err := getPITOOTFilter(r) - if err != nil { - return nil, err - } - - useInsertionDate := sharedapi.QueryParamBool(r, "insertionDate") - groupLvl := 0 - - groupLvlStr := r.URL.Query().Get("groupBy") - if groupLvlStr != "" { - groupLvlInt, err := strconv.Atoi(groupLvlStr) - if err != nil { - return nil, err - } - if groupLvlInt > 0 { - groupLvl = groupLvlInt - } - } - return &ledgerstore.FiltersForVolumes{ - PITFilter: *pit, - UseInsertionDate: useInsertionDate, - GroupLvl: uint(groupLvl), - }, nil -} - -func getQueryBuilder(r *http.Request) (query.Builder, error) { - q := r.URL.Query().Get("query") - if q == "" { - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, err - } - q = string(data) - } - - if len(q) > 0 { - return query.ParseJSON(q) - } - return nil, nil -} - -func getPaginatedQueryOptionsOfPITFilterWithVolumes(r *http.Request) (*ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes], error) { - qb, err := getQueryBuilder(r) - if err != nil { - return nil, err - } - - pitFilter, err := getPITFilterWithVolumes(r) - if err != nil { - return nil, err - } - - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - return nil, err - } - - return pointer.For(ledgerstore.NewPaginatedQueryOptions(*pitFilter). - WithQueryBuilder(qb). - WithPageSize(pageSize)), nil -} - -func getPaginatedQueryOptionsOfFiltersForVolumes(r *http.Request) (*ledgerstore.PaginatedQueryOptions[ledgerstore.FiltersForVolumes], error) { - qb, err := getQueryBuilder(r) - if err != nil { - return nil, err - } - - filtersForVolumes, err := getFiltersForVolumes(r) - if err != nil { - return nil, err - } - - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - return nil, err - } - - return pointer.For(ledgerstore.NewPaginatedQueryOptions(*filtersForVolumes). - WithPageSize(pageSize). - WithQueryBuilder(qb)), nil -} diff --git a/components/ledger/internal/bigint.go b/components/ledger/internal/bigint.go deleted file mode 100644 index d0bd3f2cf6..0000000000 --- a/components/ledger/internal/bigint.go +++ /dev/null @@ -1,7 +0,0 @@ -package ledger - -import ( - "math/big" -) - -var Zero = big.NewInt(0) diff --git a/components/ledger/internal/bus/message.go b/components/ledger/internal/bus/message.go deleted file mode 100644 index b76bfb7aa9..0000000000 --- a/components/ledger/internal/bus/message.go +++ /dev/null @@ -1,75 +0,0 @@ -package bus - -import ( - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/time" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/pkg/events" -) - -type CommittedTransactions struct { - Ledger string `json:"ledger"` - Transactions []ledger.Transaction `json:"transactions"` - AccountMetadata map[string]metadata.Metadata `json:"accountMetadata"` -} - -func newEventCommittedTransactions(txs CommittedTransactions) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().Time, - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeCommittedTransactions, - Payload: txs, - } -} - -type SavedMetadata struct { - Ledger string `json:"ledger"` - TargetType string `json:"targetType"` - TargetID string `json:"targetId"` - Metadata metadata.Metadata `json:"metadata"` -} - -func newEventSavedMetadata(metadata SavedMetadata) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().Time, - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedMetadata, - Payload: metadata, - } -} - -type RevertedTransaction struct { - Ledger string `json:"ledger"` - RevertedTransaction ledger.Transaction `json:"revertedTransaction"` - RevertTransaction ledger.Transaction `json:"revertTransaction"` -} - -func newEventRevertedTransaction(tx RevertedTransaction) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().Time, - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeRevertedTransaction, - Payload: tx, - } -} - -type DeletedMetadata struct { - Ledger string `json:"ledger"` - TargetType string `json:"targetType"` - TargetID any `json:"targetId"` - Key string `json:"key"` -} - -func newEventDeletedMetadata(tx DeletedMetadata) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().Time, - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeDeletedMetadata, - Payload: tx, - } -} diff --git a/components/ledger/internal/bus/monitor.go b/components/ledger/internal/bus/monitor.go deleted file mode 100644 index 7d3d4545f4..0000000000 --- a/components/ledger/internal/bus/monitor.go +++ /dev/null @@ -1,97 +0,0 @@ -package bus - -import ( - "context" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/publish" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/pkg/events" -) - -type Monitor interface { - CommittedTransactions(ctx context.Context, res ledger.Transaction, accountMetadata map[string]metadata.Metadata) - SavedMetadata(ctx context.Context, targetType, id string, metadata metadata.Metadata) - RevertedTransaction(ctx context.Context, reverted, revert *ledger.Transaction) - DeletedMetadata(ctx context.Context, targetType string, targetID any, key string) -} - -type noOpMonitor struct{} - -func (n noOpMonitor) DeletedMetadata(ctx context.Context, targetType string, targetID any, key string) { -} - -func (n noOpMonitor) CommittedTransactions(ctx context.Context, res ledger.Transaction, accountMetadata map[string]metadata.Metadata) { -} -func (n noOpMonitor) SavedMetadata(ctx context.Context, targetType string, id string, metadata metadata.Metadata) { -} -func (n noOpMonitor) RevertedTransaction(ctx context.Context, reverted, revert *ledger.Transaction) { -} - -var _ Monitor = &noOpMonitor{} - -func NewNoOpMonitor() *noOpMonitor { - return &noOpMonitor{} -} - -type ledgerMonitor struct { - publisher message.Publisher - ledgerName string -} - -var _ Monitor = &ledgerMonitor{} - -func NewLedgerMonitor(publisher message.Publisher, ledgerName string) *ledgerMonitor { - m := &ledgerMonitor{ - publisher: publisher, - ledgerName: ledgerName, - } - return m -} - -func (l *ledgerMonitor) CommittedTransactions(ctx context.Context, txs ledger.Transaction, accountMetadata map[string]metadata.Metadata) { - l.publish(ctx, events.EventTypeCommittedTransactions, - newEventCommittedTransactions(CommittedTransactions{ - Ledger: l.ledgerName, - Transactions: []ledger.Transaction{txs}, - AccountMetadata: accountMetadata, - })) -} - -func (l *ledgerMonitor) SavedMetadata(ctx context.Context, targetType, targetID string, metadata metadata.Metadata) { - l.publish(ctx, events.EventTypeSavedMetadata, - newEventSavedMetadata(SavedMetadata{ - Ledger: l.ledgerName, - TargetType: targetType, - TargetID: targetID, - Metadata: metadata, - })) -} - -func (l *ledgerMonitor) RevertedTransaction(ctx context.Context, reverted, revert *ledger.Transaction) { - l.publish(ctx, events.EventTypeRevertedTransaction, - newEventRevertedTransaction(RevertedTransaction{ - Ledger: l.ledgerName, - RevertedTransaction: *reverted, - RevertTransaction: *revert, - })) -} - -func (l *ledgerMonitor) DeletedMetadata(ctx context.Context, targetType string, targetID any, key string) { - l.publish(ctx, events.EventTypeDeletedMetadata, - newEventDeletedMetadata(DeletedMetadata{ - Ledger: l.ledgerName, - TargetType: targetType, - TargetID: targetID, - Key: key, - })) -} - -func (l *ledgerMonitor) publish(ctx context.Context, topic string, ev publish.EventMessage) { - if err := l.publisher.Publish(topic, publish.NewMessage(ctx, ev)); err != nil { - logging.FromContext(ctx).Errorf("publishing message: %s", err) - return - } -} diff --git a/components/ledger/internal/bus/monitor_test.go b/components/ledger/internal/bus/monitor_test.go deleted file mode 100644 index 6749cab987..0000000000 --- a/components/ledger/internal/bus/monitor_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package bus - -import ( - "context" - "testing" - "time" - - ledger "github.com/formancehq/ledger/internal" - - "github.com/ThreeDotsLabs/watermill" - "github.com/ThreeDotsLabs/watermill/pubsub/gochannel" - topicmapper "github.com/formancehq/go-libs/publish/topic_mapper" - "github.com/pborman/uuid" - "github.com/stretchr/testify/require" -) - -func TestMonitor(t *testing.T) { - - pubSub := gochannel.NewGoChannel( - gochannel.Config{ - BlockPublishUntilSubscriberAck: true, - }, - watermill.NewStdLogger(testing.Verbose(), testing.Verbose()), - ) - messages, err := pubSub.Subscribe(context.Background(), "testing") - require.NoError(t, err) - p := topicmapper.NewPublisherDecorator(pubSub, map[string]string{ - "*": "testing", - }) - m := NewLedgerMonitor(p, uuid.New()) - go m.CommittedTransactions(context.Background(), ledger.Transaction{}, nil) - - select { - case m := <-messages: - m.Ack() - case <-time.After(time.Second): - t.Fatal("should have a message") - } - -} diff --git a/components/ledger/internal/engine/chain/chain.go b/components/ledger/internal/engine/chain/chain.go deleted file mode 100644 index 9f0aaabd4d..0000000000 --- a/components/ledger/internal/engine/chain/chain.go +++ /dev/null @@ -1,79 +0,0 @@ -package chain - -import ( - "context" - "math/big" - "sync" - - ledger "github.com/formancehq/ledger/internal" - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" -) - -type Chain struct { - mu sync.Mutex - lastLog *ledger.ChainedLog - lastTXID *big.Int - store Store -} - -func (chain *Chain) ChainLog(log *ledger.Log) *ledger.ChainedLog { - chain.mu.Lock() - defer chain.mu.Unlock() - - chain.lastLog = log.ChainLog(chain.lastLog) - return chain.lastLog -} - -func (chain *Chain) Init(ctx context.Context) error { - lastTx, err := chain.store.GetLastTransaction(ctx) - if err != nil && !storageerrors.IsNotFoundError(err) { - return err - } - if lastTx != nil { - chain.lastTXID = lastTx.ID - } - - chain.lastLog, err = chain.store.GetLastLog(ctx) - if err != nil && !storageerrors.IsNotFoundError(err) { - return err - } - return nil -} - -func (chain *Chain) AllocateNewTxID() *big.Int { - chain.mu.Lock() - defer chain.mu.Unlock() - - chain.lastTXID = chain.predictNextTxID() - - return chain.lastTXID -} - -func (chain *Chain) PredictNextTxID() *big.Int { - chain.mu.Lock() - defer chain.mu.Unlock() - - return chain.predictNextTxID() -} - -func (chain *Chain) predictNextTxID() *big.Int { - return big.NewInt(0).Add(chain.lastTXID, big.NewInt(1)) -} - -func (chain *Chain) ReplaceLast(log *ledger.ChainedLog) { - if log.Type == ledger.NewTransactionLogType { - chain.lastTXID = log.Data.(ledger.NewTransactionLogPayload).Transaction.ID - } - chain.lastLog = log -} - -func (chain *Chain) GetLastLog() *ledger.ChainedLog { - return chain.lastLog -} - -func New(store Store) *Chain { - return &Chain{ - lastTXID: big.NewInt(-1), - store: store, - } -} diff --git a/components/ledger/internal/engine/chain/store.go b/components/ledger/internal/engine/chain/store.go deleted file mode 100644 index 9e5771b8fe..0000000000 --- a/components/ledger/internal/engine/chain/store.go +++ /dev/null @@ -1,12 +0,0 @@ -package chain - -import ( - "context" - - ledger "github.com/formancehq/ledger/internal" -) - -type Store interface { - GetLastLog(ctx context.Context) (*ledger.ChainedLog, error) - GetLastTransaction(ctx context.Context) (*ledger.ExpandedTransaction, error) -} diff --git a/components/ledger/internal/engine/command/commander.go b/components/ledger/internal/engine/command/commander.go deleted file mode 100644 index 4fdd0d86f2..0000000000 --- a/components/ledger/internal/engine/command/commander.go +++ /dev/null @@ -1,347 +0,0 @@ -package command - -import ( - "context" - "fmt" - "math/big" - "sync" - - "github.com/formancehq/ledger/internal/machine/vm/program" - "github.com/formancehq/ledger/internal/opentelemetry/tracer" - - "github.com/formancehq/go-libs/time" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/bus" - "github.com/formancehq/ledger/internal/engine/utils/batching" - "github.com/formancehq/ledger/internal/machine/vm" - "github.com/pkg/errors" -) - -type Parameters struct { - DryRun bool - IdempotencyKey string -} - -type Chainer interface { - ChainLog(log *ledger.Log) *ledger.ChainedLog - AllocateNewTxID() *big.Int - PredictNextTxID() *big.Int -} - -type Commander struct { - *batching.Batcher[*ledger.ChainedLog] - store Store - locker Locker - compiler *Compiler - running sync.WaitGroup - referencer *Referencer - - monitor bus.Monitor - chain Chainer -} - -func New( - store Store, - locker Locker, - compiler *Compiler, - referencer *Referencer, - monitor bus.Monitor, - chain Chainer, - batchSize int, -) *Commander { - return &Commander{ - store: store, - locker: locker, - compiler: compiler, - chain: chain, - referencer: referencer, - Batcher: batching.NewBatcher(store.InsertLogs, 1, batchSize), - monitor: monitor, - } -} - -func (commander *Commander) GetLedgerStore() Store { - return commander.store -} - -func (commander *Commander) exec(ctx context.Context, parameters Parameters, script ledger.RunScript, - logComputer func(tx *ledger.Transaction, accountMetadata map[string]metadata.Metadata) *ledger.Log) (*ledger.ChainedLog, error) { - - if script.Script.Plain == "" { - return nil, NewErrNoScript() - } - - if script.Timestamp.IsZero() { - script.Timestamp = time.Now() - } - - execContext := newExecutionContext(commander, parameters) - return execContext.run(ctx, func(executionContext *executionContext) (*ledger.ChainedLog, error) { - if script.Reference != "" { - if err := commander.referencer.take(referenceTxReference, script.Reference); err != nil { - return nil, NewErrConflict() - } - defer commander.referencer.release(referenceTxReference, script.Reference) - - err := func() error { - ctx, span := tracer.Start(ctx, "CheckReference") - defer span.End() - - _, err := commander.store.GetTransactionByReference(ctx, script.Reference) - if err == nil { - return NewErrConflict() - } - if err != nil && !storageerrors.IsNotFoundError(err) { - return err - } - return nil - }() - if err != nil { - return nil, err - } - } - - program, err := func() (*program.Program, error) { - _, span := tracer.Start(ctx, "CompileNumscript") - defer span.End() - - program, err := commander.compiler.Compile(script.Plain) - if err != nil { - return nil, NewErrCompilationFailed(err) - } - - return program, nil - }() - if err != nil { - return nil, err - } - - m := vm.NewMachine(*program) - if err := m.SetVarsFromJSON(script.Vars); err != nil { - return nil, NewErrCompilationFailed(err) - } - - readLockAccounts, writeLockAccounts, err := m.ResolveResources(ctx, commander.store) - if err != nil { - return nil, NewErrCompilationFailed(err) - } - lockAccounts := Accounts{ - Read: readLockAccounts, - Write: writeLockAccounts, - } - - unlock, err := func() (Unlock, error) { - _, span := tracer.Start(ctx, "Lock") - defer span.End() - - unlock, err := commander.locker.Lock(ctx, lockAccounts) - if err != nil { - return nil, errors.Wrap(err, "locking accounts for tx processing") - } - - return unlock, nil - }() - if err != nil { - return nil, err - } - defer unlock(ctx) - - err = func() error { - ctx, span := tracer.Start(ctx, "ResolveBalances") - defer span.End() - - err = m.ResolveBalances(ctx, commander.store) - if err != nil { - return errors.Wrap(err, "could not resolve balances") - } - - return nil - }() - if err != nil { - return nil, err - } - result, err := func() (*vm.Result, error) { - _, span := tracer.Start(ctx, "RunNumscript") - defer span.End() - - result, err := vm.Run(m, script) - if err != nil { - return nil, NewErrMachine(err) - } - - return result, nil - }() - if err != nil { - return nil, err - } - - if len(result.Postings) == 0 { - return nil, NewErrNoPostings() - } - - txID := commander.chain.PredictNextTxID() - if !parameters.DryRun { - txID = commander.chain.AllocateNewTxID() - } - - tx := ledger.NewTransaction(). - WithPostings(result.Postings...). - WithMetadata(result.Metadata). - WithDate(script.Timestamp). - WithID(txID). - WithReference(script.Reference) - - log := logComputer(tx, result.AccountMetadata) - if parameters.IdempotencyKey != "" { - log = log.WithIdempotencyKey(parameters.IdempotencyKey) - } - - return executionContext.AppendLog(ctx, log) - }) -} - -func (commander *Commander) CreateTransaction(ctx context.Context, parameters Parameters, script ledger.RunScript) (*ledger.Transaction, error) { - - ctx, span := tracer.Start(ctx, "CreateTransaction") - defer span.End() - - log, err := commander.exec(ctx, parameters, script, ledger.NewTransactionLog) - if err != nil { - - return nil, err - } - - commander.monitor.CommittedTransactions(ctx, *log.Data.(ledger.NewTransactionLogPayload).Transaction, log.Data.(ledger.NewTransactionLogPayload).AccountMetadata) - - return log.Data.(ledger.NewTransactionLogPayload).Transaction, nil -} - -func (commander *Commander) SaveMeta(ctx context.Context, parameters Parameters, targetType string, targetID interface{}, m metadata.Metadata) error { - execContext := newExecutionContext(commander, parameters) - _, err := execContext.run(ctx, func(executionContext *executionContext) (*ledger.ChainedLog, error) { - var ( - log *ledger.Log - at = time.Now() - ) - switch targetType { - case ledger.MetaTargetTypeTransaction: - _, err := commander.store.GetTransaction(ctx, targetID.(*big.Int)) - if err != nil { - if storageerrors.IsNotFoundError(err) { - return nil, newErrSaveMetadataTransactionNotFound() - } - } - log = ledger.NewSetMetadataLog(at, ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeTransaction, - TargetID: targetID.(*big.Int), - Metadata: m, - }) - case ledger.MetaTargetTypeAccount: - log = ledger.NewSetMetadataLog(at, ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: targetID.(string), - Metadata: m, - }) - default: - panic(errors.Errorf("unknown target type '%s'", targetType)) - } - - return executionContext.AppendLog(ctx, log) - }) - if err != nil { - return err - } - - commander.monitor.SavedMetadata(ctx, targetType, fmt.Sprint(targetID), m) - return nil -} - -func (commander *Commander) RevertTransaction(ctx context.Context, parameters Parameters, id *big.Int, force, atEffectiveDate bool) (*ledger.Transaction, error) { - - if err := commander.referencer.take(referenceReverts, id); err != nil { - return nil, NewErrRevertTransactionOccurring() - } - defer commander.referencer.release(referenceReverts, id) - - transactionToRevert, err := commander.store.GetTransaction(ctx, id) - if err != nil { - if storageerrors.IsNotFoundError(err) { - return nil, NewErrRevertTransactionNotFound() - } - return nil, err - } - if transactionToRevert.Reverted { - return nil, NewErrRevertTransactionAlreadyReverted() - } - - rt := transactionToRevert.Reverse() - rt.Metadata = ledger.MarkReverts(metadata.Metadata{}, transactionToRevert.ID) - - script := ledger.TxToScriptData(ledger.TransactionData{ - Postings: rt.Postings, - Metadata: rt.Metadata, - }, force) - if atEffectiveDate { - script.Timestamp = transactionToRevert.Timestamp - } - - log, err := commander.exec(ctx, parameters, script, - func(tx *ledger.Transaction, accountMetadata map[string]metadata.Metadata) *ledger.Log { - return ledger.NewRevertedTransactionLog(tx.Timestamp, transactionToRevert.ID, tx) - }) - if err != nil { - return nil, err - } - - commander.monitor.RevertedTransaction(ctx, log.Data.(ledger.RevertedTransactionLogPayload).RevertTransaction, transactionToRevert) - - return log.Data.(ledger.RevertedTransactionLogPayload).RevertTransaction, nil -} - -func (commander *Commander) Close() { - commander.Batcher.Close() - commander.running.Wait() -} - -func (commander *Commander) DeleteMetadata(ctx context.Context, parameters Parameters, targetType string, targetID any, key string) error { - execContext := newExecutionContext(commander, parameters) - _, err := execContext.run(ctx, func(executionContext *executionContext) (*ledger.ChainedLog, error) { - var ( - log *ledger.Log - at = time.Now() - ) - switch targetType { - case ledger.MetaTargetTypeTransaction: - _, err := commander.store.GetTransaction(ctx, targetID.(*big.Int)) - if err != nil { - return nil, newErrDeleteMetadataTransactionNotFound() - } - log = ledger.NewDeleteMetadataLog(at, ledger.DeleteMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeTransaction, - TargetID: targetID.(*big.Int), - Key: key, - }) - case ledger.MetaTargetTypeAccount: - log = ledger.NewDeleteMetadataLog(at, ledger.DeleteMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: targetID.(string), - Key: key, - }) - default: - panic(errors.Errorf("unknown target type '%s'", targetType)) - } - - return executionContext.AppendLog(ctx, log) - }) - if err != nil { - return err - } - - commander.monitor.DeletedMetadata(ctx, targetType, targetID, key) - - return nil -} diff --git a/components/ledger/internal/engine/command/commander_test.go b/components/ledger/internal/engine/command/commander_test.go deleted file mode 100644 index a3fac0682e..0000000000 --- a/components/ledger/internal/engine/command/commander_test.go +++ /dev/null @@ -1,419 +0,0 @@ -package command - -import ( - "context" - "math/big" - "sync" - "testing" - - "github.com/formancehq/go-libs/testing/docker" - - "github.com/formancehq/go-libs/bun/bundebug" - "github.com/uptrace/bun" - - "github.com/formancehq/ledger/internal/engine/chain" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/google/uuid" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/bus" - storageerrors "github.com/formancehq/ledger/internal/storage" - internaltesting "github.com/formancehq/ledger/internal/testing" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" -) - -var ( - now = time.Now() -) - -type testCase struct { - name string - setup func(t *testing.T, r Store) - script string - reference string - expectedErrorCode string - expectedTx *ledger.Transaction - expectedLogs []*ledger.Log - parameters Parameters -} - -var testCases = []testCase{ - { - name: "nominal", - script: ` - send [GEM 100] ( - source = @world - destination = @mint - )`, - expectedTx: ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ), - expectedLogs: []*ledger.Log{ - ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100))), - map[string]metadata.Metadata{}, - ), - }, - }, - { - name: "no script", - script: ``, - expectedErrorCode: ErrInvalidTransactionCodeNoScript, - }, - { - name: "invalid script", - script: `XXX`, - expectedErrorCode: ErrInvalidTransactionCodeCompilationFailed, - }, - { - name: "set reference conflict", - setup: func(t *testing.T, store Store) { - tx := ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "mint", "GEM", big.NewInt(100))). - WithReference("tx_ref") - log := ledger.NewTransactionLog(tx, nil) - err := store.InsertLogs(context.Background(), log.ChainLog(nil)) - require.NoError(t, err) - }, - script: ` - send [GEM 100] ( - source = @world - destination = @mint - )`, - reference: "tx_ref", - expectedErrorCode: ErrInvalidTransactionCodeConflict, - }, - { - name: "set reference", - script: ` - send [GEM 100] ( - source = @world - destination = @mint - )`, - reference: "tx_ref", - expectedTx: ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ). - WithReference("tx_ref"), - expectedLogs: []*ledger.Log{ - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ). - WithReference("tx_ref"), - map[string]metadata.Metadata{}, - ), - }, - }, - { - name: "using idempotency", - script: ` - send [GEM 100] ( - source = @world - destination = @mint - )`, - reference: "tx_ref", - expectedTx: ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ), - expectedLogs: []*ledger.Log{ - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ), - map[string]metadata.Metadata{}, - ).WithIdempotencyKey("testing"), - }, - setup: func(t *testing.T, r Store) { - log := ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "mint", "GEM", big.NewInt(100)), - ). - WithDate(now), - map[string]metadata.Metadata{}, - ).WithIdempotencyKey("testing") - err := r.InsertLogs(context.Background(), log.ChainLog(nil)) - require.NoError(t, err) - }, - parameters: Parameters{ - IdempotencyKey: "testing", - }, - }, -} - -func TestCreateTransaction(t *testing.T) { - t.Parallel() - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - - commander := New(store, NoOpLocker, NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - if tc.setup != nil { - tc.setup(t, store) - } - ret, err := commander.CreateTransaction(ctx, tc.parameters, ledger.RunScript{ - Script: ledger.Script{ - Plain: tc.script, - }, - Timestamp: now, - Reference: tc.reference, - }) - - if tc.expectedErrorCode != "" { - require.True(t, IsInvalidTransactionError(err, tc.expectedErrorCode)) - } else { - require.NoError(t, err) - require.NotNil(t, ret) - tc.expectedTx.Timestamp = now - internaltesting.RequireEqual(t, tc.expectedTx, ret) - - for ind := range tc.expectedLogs { - expectedLog := tc.expectedLogs[ind] - switch v := expectedLog.Data.(type) { - case ledger.NewTransactionLogPayload: - v.Transaction.Timestamp = now - expectedLog.Data = v - } - expectedLog.Date = now - } - } - }) - } -} - -func TestRevert(t *testing.T) { - txID := big.NewInt(0) - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - - log := ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - map[string]metadata.Metadata{}, - ).ChainLog(nil) - err := store.InsertLogs(context.Background(), log) - require.NoError(t, err) - - commander := New(store, NoOpLocker, NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - _, err = commander.RevertTransaction(ctx, Parameters{}, txID, false, false) - require.NoError(t, err) -} - -func TestRevertWithAlreadyReverted(t *testing.T) { - - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - - tx := ledger.NewTransaction().WithPostings(ledger.NewPosting("world", "bank", "USD", big.NewInt(100))) - err := store.InsertLogs(context.Background(), - ledger.NewTransactionLog(tx, map[string]metadata.Metadata{}).ChainLog(nil), - ledger.NewRevertedTransactionLog(time.Now(), tx.ID, ledger.NewTransaction()).ChainLog(nil), - ) - require.NoError(t, err) - - commander := New(store, NoOpLocker, NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - _, err = commander.RevertTransaction(context.Background(), Parameters{}, tx.ID, false, false) - require.True(t, IsRevertError(err, ErrRevertTransactionCodeAlreadyReverted)) -} - -func TestRevertWithRevertOccurring(t *testing.T) { - - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - - tx := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - log := ledger.NewTransactionLog(tx, map[string]metadata.Metadata{}) - err := store.InsertLogs(ctx, log.ChainLog(nil)) - require.NoError(t, err) - - referencer := NewReferencer() - commander := New(store, NoOpLocker, NewCompiler(1024), referencer, bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - referencer.take(referenceReverts, big.NewInt(0)) - - _, err = commander.RevertTransaction(ctx, Parameters{}, tx.ID, false, false) - require.True(t, IsRevertError(err, ErrRevertTransactionCodeOccurring)) -} - -func TestForceRevert(t *testing.T) { - - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ) - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "foo", "USD", big.NewInt(100)), - ) - err := store.InsertLogs(ctx, ledger.ChainLogs( - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}), - )...) - require.NoError(t, err) - - commander := New(store, NoOpLocker, NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - _, err = commander.RevertTransaction(ctx, Parameters{}, tx1.ID, false, false) - require.NotNil(t, err) - require.True(t, errors.Is(err, &machine.ErrInsufficientFund{})) - balance, err := store.GetBalance(ctx, "bank", "USD") - require.NoError(t, err) - require.Equal(t, uint64(0), balance.Uint64()) - - _, err = commander.RevertTransaction(ctx, Parameters{}, tx1.ID, true, false) - require.Nil(t, err) - - balance, err = store.GetBalance(ctx, "bank", "USD") - require.NoError(t, err) - require.Equal(t, big.NewInt(-100), balance) - - balance, err = store.GetBalance(ctx, "world", "USD") - require.NoError(t, err) - require.Equal(t, uint64(0), balance.Uint64()) -} - -func TestRevertAtEffectiveDate(t *testing.T) { - - store := storageerrors.NewInMemoryStore() - ctx := logging.TestingContext() - now := time.Now() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ).WithDate(now) - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "foo", "USD", big.NewInt(100)), - ).WithDate(now.Add(time.Second)) - err := store.InsertLogs(ctx, ledger.ChainLogs( - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}), - )...) - require.NoError(t, err) - - commander := New(store, NoOpLocker, NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - revertTx, err := commander.RevertTransaction(ctx, Parameters{}, tx1.ID, false, true) - require.Nil(t, err) - require.Equal(t, tx1.Timestamp, revertTx.Timestamp) - - balance, err := store.GetBalance(ctx, "bank", "USD") - require.NoError(t, err) - internaltesting.RequireEqual(t, big.NewInt(0), balance) - - balance, err = store.GetBalance(ctx, "world", "USD") - require.NoError(t, err) - internaltesting.RequireEqual(t, big.NewInt(-100), balance) -} - -func TestParallelTransactions(t *testing.T) { - dockerPool := docker.NewPool(t, logging.Testing()) - srv := pgtesting.CreatePostgresServer(t, dockerPool) - ctx := logging.TestingContext() - - pgDB := srv.NewDatabase(t) - - connectionOptions := bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDB.ConnString(), - } - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - sqlDB, err := bunconnect.OpenSQLDB(ctx, connectionOptions, hooks...) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, sqlDB.Close()) - }) - - bucketName := uuid.NewString() - - bucket, err := ledgerstore.ConnectToBucket(ctx, connectionOptions, bucketName) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, bucket.Close()) - }) - - err = ledgerstore.MigrateBucket(ctx, sqlDB, bucketName) - require.NoError(t, err) - - store, err := ledgerstore.New(bucket, "default") - require.NoError(t, err) - - commander := New(store, NewDefaultLocker(), NewCompiler(1024), NewReferencer(), bus.NewNoOpMonitor(), chain.New(store), 50) - go commander.Run(ctx) - defer commander.Close() - - _, err = commander.CreateTransaction(ctx, Parameters{}, ledger.TxToScriptData(ledger.TransactionData{ - Postings: []ledger.Posting{{ - Source: "world", - Destination: "foo", - Amount: big.NewInt(1000), - Asset: "USD", - }}, - }, false)) - require.NoError(t, err) - - count := 100 - wg := sync.WaitGroup{} - wg.Add(count) - for i := 0; i < count; i++ { - go func() { - _, _ = commander.CreateTransaction(ctx, Parameters{}, ledger.TxToScriptData(ledger.TransactionData{ - Postings: []ledger.Posting{{ - Source: "foo", - Destination: "bar", - Amount: big.NewInt(100), - Asset: "USD", - }}, - }, false)) - wg.Done() - }() - - } - wg.Wait() - - account, err := store.GetAccountWithVolumes(ctx, ledgerstore.NewGetAccountQuery("bar").WithExpandVolumes()) - require.NoError(t, err) - internaltesting.RequireEqual(t, big.NewInt(1000), account.Volumes.Balances()["USD"]) -} diff --git a/components/ledger/internal/engine/command/compiler.go b/components/ledger/internal/engine/command/compiler.go deleted file mode 100644 index df609eea12..0000000000 --- a/components/ledger/internal/engine/command/compiler.go +++ /dev/null @@ -1,45 +0,0 @@ -package command - -import ( - "crypto/sha256" - "encoding/base64" - - "github.com/bluele/gcache" - "github.com/formancehq/ledger/internal/machine/script/compiler" - "github.com/formancehq/ledger/internal/machine/vm/program" -) - -type Compiler struct { - cache gcache.Cache -} - -func (c *Compiler) Compile(script string) (*program.Program, error) { - - digest := sha256.New() - _, err := digest.Write([]byte(script)) - if err != nil { - return nil, err - } - - cacheKey := base64.StdEncoding.EncodeToString(digest.Sum(nil)) - v, err := c.cache.Get(cacheKey) - if err == nil { - return v.(*program.Program), nil - } - - program, err := compiler.Compile(script) - if err != nil { - return nil, err - } - _ = c.cache.Set(cacheKey, program) - - return program, nil -} - -func NewCompiler(maxCacheCount int) *Compiler { - return &Compiler{ - cache: gcache.New(maxCacheCount). - LFU(). - Build(), - } -} diff --git a/components/ledger/internal/engine/command/compiler_test.go b/components/ledger/internal/engine/command/compiler_test.go deleted file mode 100644 index a8074133ed..0000000000 --- a/components/ledger/internal/engine/command/compiler_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package command - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestCompiler(t *testing.T) { - - script := `send [USD/2 100] ( - source = @world - destination = @bank -)` - - compiler := NewCompiler(1024) - p1, err := compiler.Compile(script) - require.NoError(t, err) - - p2, err := compiler.Compile(script) - require.NoError(t, err) - - require.Equal(t, p1, p2) -} diff --git a/components/ledger/internal/engine/command/context.go b/components/ledger/internal/engine/command/context.go deleted file mode 100644 index e2f93c5ae7..0000000000 --- a/components/ledger/internal/engine/command/context.go +++ /dev/null @@ -1,87 +0,0 @@ -package command - -import ( - "context" - - "github.com/formancehq/ledger/internal/opentelemetry/tracer" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - ledger "github.com/formancehq/ledger/internal" -) - -type executionContext struct { - commander *Commander - parameters Parameters -} - -func (e *executionContext) AppendLog(ctx context.Context, log *ledger.Log) (*ledger.ChainedLog, error) { - ctx, span := tracer.Start(ctx, "AppendLog") - defer span.End() - - if e.parameters.DryRun { - return log.ChainLog(nil), nil - } - - chainedLog := func() *ledger.ChainedLog { - _, span := tracer.Start(ctx, "ChainLog") - defer span.End() - - return e.commander.chain.ChainLog(log) - }() - - done := make(chan struct{}) - func() { - _, span := tracer.Start(ctx, "AppendLogToQueue") - defer span.End() - - e.commander.Append(chainedLog, func() { - close(done) - }) - }() - - err := func() error { - _, span := tracer.Start(ctx, "WaitLogAck") - defer span.End() - - select { - case <-ctx.Done(): - return ctx.Err() - case <-done: - return nil - } - }() - if err != nil { - return nil, err - } - - return chainedLog, nil -} - -func (e *executionContext) run(ctx context.Context, executor func(e *executionContext) (*ledger.ChainedLog, error)) (*ledger.ChainedLog, error) { - if ik := e.parameters.IdempotencyKey; ik != "" { - if err := e.commander.referencer.take(referenceIks, ik); err != nil { - return nil, err - } - defer e.commander.referencer.release(referenceIks, ik) - - ctx, span := tracer.Start(ctx, "CheckIK") - defer span.End() - - chainedLog, err := e.commander.store.ReadLogWithIdempotencyKey(ctx, ik) - if err == nil { - return chainedLog, nil - } - if err != nil && !storageerrors.IsNotFoundError(err) { - return nil, err - } - } - return executor(e) -} - -func newExecutionContext(commander *Commander, parameters Parameters) *executionContext { - return &executionContext{ - commander: commander, - parameters: parameters, - } -} diff --git a/components/ledger/internal/engine/command/errors.go b/components/ledger/internal/engine/command/errors.go deleted file mode 100644 index 16ad75c8cf..0000000000 --- a/components/ledger/internal/engine/command/errors.go +++ /dev/null @@ -1,212 +0,0 @@ -package command - -import ( - "fmt" - - "github.com/pkg/errors" -) - -const ( - ErrSaveMetaCodeTransactionNotFound = "TRANSACTION_NOT_FOUND" -) - -type errSaveMeta struct { - code string -} - -func (e *errSaveMeta) Error() string { - return fmt.Sprintf("invalid transaction: %s", e.code) -} - -func (e *errSaveMeta) Is(err error) bool { - _, ok := err.(*errSaveMeta) - return ok -} - -func newErrSaveMeta(code string) *errSaveMeta { - return &errSaveMeta{ - code: code, - } -} - -func newErrSaveMetadataTransactionNotFound() *errSaveMeta { - return newErrSaveMeta(ErrSaveMetaCodeTransactionNotFound) -} - -func IsSaveMetaError(err error, code string) bool { - e := &errSaveMeta{} - if errors.As(err, &e) { - return e.code == code - } - - return false -} - -const ( - ErrDeleteMetaCodeTransactionNotFound = "TRANSACTION_NOT_FOUND" -) - -type errDeleteMeta struct { - code string -} - -func (e *errDeleteMeta) Error() string { - return fmt.Sprintf("invalid transaction: %s", e.code) -} - -func (e *errDeleteMeta) Is(err error) bool { - _, ok := err.(*errDeleteMeta) - return ok -} - -func newErrDeleteMeta(code string) *errDeleteMeta { - return &errDeleteMeta{ - code: code, - } -} - -func IsDeleteMetaError(err error, code string) bool { - e := &errDeleteMeta{} - if errors.As(err, &e) { - return e.code == code - } - - return false -} - -func newErrDeleteMetadataTransactionNotFound() *errDeleteMeta { - return newErrDeleteMeta(ErrDeleteMetaCodeTransactionNotFound) -} - -type errRevert struct { - code string -} - -func (e *errRevert) Error() string { - return fmt.Sprintf("invalid transaction: %s", e.code) -} - -func (e *errRevert) Is(err error) bool { - _, ok := err.(*errRevert) - return ok -} - -func NewErrRevert(code string) *errRevert { - return &errRevert{ - code: code, - } -} - -const ( - ErrRevertTransactionCodeAlreadyReverted = "ALREADY_REVERTED" - ErrRevertTransactionCodeOccurring = "REVERT_OCCURRING" - ErrRevertTransactionCodeNotFound = "NOT_FOUND" -) - -func NewErrRevertTransactionOccurring() *errRevert { - return NewErrRevert(ErrRevertTransactionCodeOccurring) -} - -func NewErrRevertTransactionAlreadyReverted() *errRevert { - return NewErrRevert(ErrRevertTransactionCodeAlreadyReverted) -} - -func NewErrRevertTransactionNotFound() *errRevert { - return NewErrRevert(ErrRevertTransactionCodeNotFound) -} - -func IsRevertError(err error, code string) bool { - e := &errRevert{} - if errors.As(err, &e) { - return e.code == code - } - - return false -} - -type errInvalidTransaction struct { - code string - err error -} - -func (e *errInvalidTransaction) Error() string { - if e.err == nil { - return fmt.Sprintf("invalid transaction: %s", e.code) - } - return fmt.Sprintf("invalid transaction: %s (%s)", e.code, e.err) -} - -func (e *errInvalidTransaction) Is(err error) bool { - _, ok := err.(*errInvalidTransaction) - return ok -} - -func (e *errInvalidTransaction) Cause() error { - return e.err -} - -func NewErrInvalidTransaction(code string, err error) *errInvalidTransaction { - return &errInvalidTransaction{ - code: code, - err: err, - } -} - -const ( - ErrInvalidTransactionCodeCompilationFailed = "COMPILATION_FAILED" - ErrInvalidTransactionCodeNoScript = "NO_SCRIPT" - ErrInvalidTransactionCodeNoPostings = "NO_POSTINGS" - ErrInvalidTransactionCodeConflict = "CONFLICT" -) - -func NewErrCompilationFailed(err error) *errInvalidTransaction { - return NewErrInvalidTransaction(ErrInvalidTransactionCodeCompilationFailed, err) -} - -func NewErrNoScript() *errInvalidTransaction { - return NewErrInvalidTransaction(ErrInvalidTransactionCodeNoScript, nil) -} - -func NewErrNoPostings() *errInvalidTransaction { - return NewErrInvalidTransaction(ErrInvalidTransactionCodeNoPostings, nil) -} - -func NewErrConflict() *errInvalidTransaction { - return NewErrInvalidTransaction(ErrInvalidTransactionCodeConflict, nil) -} - -func IsInvalidTransactionError(err error, code string) bool { - e := &errInvalidTransaction{} - if errors.As(err, &e) { - return e.code == code - } - - return false -} - -type errMachine struct { - err error -} - -func (e *errMachine) Error() string { - return errors.Wrap(e.err, "running numscript").Error() -} - -func (e *errMachine) Is(err error) bool { - _, ok := err.(*errMachine) - return ok -} - -func (e *errMachine) Unwrap() error { - return e.err -} - -func NewErrMachine(err error) *errMachine { - return &errMachine{ - err: err, - } -} - -func IsErrMachine(err error) bool { - return errors.Is(err, &errMachine{}) -} diff --git a/components/ledger/internal/engine/command/lock.go b/components/ledger/internal/engine/command/lock.go deleted file mode 100644 index 57a197d164..0000000000 --- a/components/ledger/internal/engine/command/lock.go +++ /dev/null @@ -1,151 +0,0 @@ -package command - -import ( - "context" - "sync" - "sync/atomic" - "time" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/pkg/errors" -) - -type Unlock func(ctx context.Context) - -type Locker interface { - Lock(ctx context.Context, accounts Accounts) (Unlock, error) -} -type LockerFn func(ctx context.Context, accounts Accounts) (Unlock, error) - -func (fn LockerFn) Lock(ctx context.Context, accounts Accounts) (Unlock, error) { - return fn(ctx, accounts) -} - -var NoOpLocker = LockerFn(func(ctx context.Context, accounts Accounts) (Unlock, error) { - return func(ctx context.Context) {}, nil -}) - -type Accounts struct { - Read []string - Write []string -} - -type lockIntent struct { - accounts Accounts - acquired chan struct{} - at time.Time -} - -func (intent *lockIntent) tryLock(chain *DefaultLocker) bool { - - for _, account := range intent.accounts.Read { - _, ok := chain.writeLocks[account] - if ok { - return false - } - } - - for _, account := range intent.accounts.Write { - _, ok := chain.readLocks[account] - if ok { - return false - } - _, ok = chain.writeLocks[account] - if ok { - return false - } - } - - for _, account := range intent.accounts.Read { - atomicValue, ok := chain.readLocks[account] - if !ok { - atomicValue = &atomic.Int64{} - chain.readLocks[account] = atomicValue - } - atomicValue.Add(1) - } - for _, account := range intent.accounts.Write { - chain.writeLocks[account] = struct{}{} - } - - return true -} - -func (intent *lockIntent) unlock(chain *DefaultLocker) { - for _, account := range intent.accounts.Read { - atomicValue := chain.readLocks[account] - if atomicValue.Add(-1) == 0 { - delete(chain.readLocks, account) - } - } - for _, account := range intent.accounts.Write { - delete(chain.writeLocks, account) - } -} - -type DefaultLocker struct { - intents *collectionutils.LinkedList[*lockIntent] - mu sync.Mutex - readLocks map[string]*atomic.Int64 - writeLocks map[string]struct{} -} - -func (defaultLocker *DefaultLocker) Lock(ctx context.Context, accounts Accounts) (Unlock, error) { - defaultLocker.mu.Lock() - - intent := &lockIntent{ - accounts: accounts, - acquired: make(chan struct{}), - at: time.Now(), - } - - recheck := func() { - node := defaultLocker.intents.FirstNode() - for { - if node == nil { - return - } - if node.Value().tryLock(defaultLocker) { - node.Remove() - close(node.Value().acquired) - return - } - node = node.Next() - } - } - - releaseIntent := func(ctx context.Context) { - defaultLocker.mu.Lock() - defer defaultLocker.mu.Unlock() - - intent.unlock(defaultLocker) - - recheck() - } - - acquired := intent.tryLock(defaultLocker) - if acquired { - defaultLocker.mu.Unlock() - - return releaseIntent, nil - } - - defaultLocker.intents.Append(intent) - defaultLocker.mu.Unlock() - - select { - case <-ctx.Done(): - defaultLocker.intents.RemoveValue(intent) - return nil, errors.Wrapf(ctx.Err(), "locking accounts: %s as read, and %s as write", accounts.Read, accounts.Write) - case <-intent.acquired: - return releaseIntent, nil - } -} - -func NewDefaultLocker() *DefaultLocker { - return &DefaultLocker{ - intents: collectionutils.NewLinkedList[*lockIntent](), - readLocks: map[string]*atomic.Int64{}, - writeLocks: map[string]struct{}{}, - } -} diff --git a/components/ledger/internal/engine/command/lock_test.go b/components/ledger/internal/engine/command/lock_test.go deleted file mode 100644 index bc6fe5f7d3..0000000000 --- a/components/ledger/internal/engine/command/lock_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package command - -import ( - "fmt" - "math/rand" - "sync" - "testing" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/stretchr/testify/require" -) - -func TestLock(t *testing.T) { - locker := NewDefaultLocker() - var accounts []string - for i := 0; i < 10; i++ { - accounts = append(accounts, fmt.Sprintf("accounts:%d", i)) - } - - r := rand.New(rand.NewSource(time.Now().Unix())) - ctx := logging.TestingContext() - - const nbLoop = 1000 - wg := sync.WaitGroup{} - wg.Add(nbLoop) - - for i := 0; i < nbLoop; i++ { - read := accounts[r.Int31n(10)] - write := accounts[r.Int31n(10)] - go func() { - unlock, err := locker.Lock(ctx, Accounts{ - Read: []string{read}, - Write: []string{write}, - }) - require.NoError(t, err) - defer unlock(ctx) - - <-time.After(10 * time.Millisecond) - wg.Add(-1) - }() - } - - wg.Wait() - -} diff --git a/components/ledger/internal/engine/command/reference.go b/components/ledger/internal/engine/command/reference.go deleted file mode 100644 index 86d4a10c61..0000000000 --- a/components/ledger/internal/engine/command/reference.go +++ /dev/null @@ -1,42 +0,0 @@ -package command - -import ( - "fmt" - "sync" - - "github.com/pkg/errors" -) - -type Reference int - -const ( - referenceReverts = iota - referenceIks - referenceTxReference -) - -type Referencer struct { - references map[Reference]*sync.Map -} - -func (r *Referencer) take(ref Reference, key any) error { - _, loaded := r.references[ref].LoadOrStore(fmt.Sprintf("%d/%s", ref, key), struct{}{}) - if loaded { - return errors.New("already taken") - } - return nil -} - -func (r *Referencer) release(ref Reference, key any) { - r.references[ref].Delete(fmt.Sprintf("%d/%s", ref, key)) -} - -func NewReferencer() *Referencer { - return &Referencer{ - references: map[Reference]*sync.Map{ - referenceReverts: {}, - referenceIks: {}, - referenceTxReference: {}, - }, - } -} diff --git a/components/ledger/internal/engine/command/store.go b/components/ledger/internal/engine/command/store.go deleted file mode 100644 index 25569e56f5..0000000000 --- a/components/ledger/internal/engine/command/store.go +++ /dev/null @@ -1,19 +0,0 @@ -package command - -import ( - "context" - "math/big" - - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/machine/vm" -) - -type Store interface { - vm.Store - InsertLogs(ctx context.Context, logs ...*ledger.ChainedLog) error - GetLastLog(ctx context.Context) (*ledger.ChainedLog, error) - GetLastTransaction(ctx context.Context) (*ledger.ExpandedTransaction, error) - ReadLogWithIdempotencyKey(ctx context.Context, key string) (*ledger.ChainedLog, error) - GetTransactionByReference(ctx context.Context, ref string) (*ledger.ExpandedTransaction, error) - GetTransaction(ctx context.Context, txID *big.Int) (*ledger.Transaction, error) -} diff --git a/components/ledger/internal/engine/errors.go b/components/ledger/internal/engine/errors.go deleted file mode 100644 index 930c358f97..0000000000 --- a/components/ledger/internal/engine/errors.go +++ /dev/null @@ -1,73 +0,0 @@ -package engine - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type storageError struct { - err error - msg string -} - -func (e *storageError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.err) -} - -func (e *storageError) Is(err error) bool { - _, ok := err.(*storageError) - return ok -} - -func (e *storageError) Unwrap() error { - return e.err -} - -func newStorageError(err error, msg string) error { - if err == nil { - return nil - } - return &storageError{ - err: err, - msg: msg, - } -} - -func IsStorageError(err error) bool { - return errors.Is(err, &storageError{}) -} - -type commandError struct { - err error -} - -func (e *commandError) Error() string { - return e.err.Error() -} - -func (e *commandError) Is(err error) bool { - _, ok := err.(*commandError) - return ok -} - -func (e *commandError) Unwrap() error { - return e.err -} - -func (e *commandError) Cause() error { - return e.err -} - -func NewCommandError(err error) error { - if err == nil { - return nil - } - return &commandError{ - err: err, - } -} - -func IsCommandError(err error) bool { - return errors.Is(err, &commandError{}) -} diff --git a/components/ledger/internal/engine/export.go b/components/ledger/internal/engine/export.go deleted file mode 100644 index 07f04a1af2..0000000000 --- a/components/ledger/internal/engine/export.go +++ /dev/null @@ -1,39 +0,0 @@ -package engine - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -type ExportWriter interface { - Write(ctx context.Context, log *ledger.ChainedLog) error -} - -type ExportWriterFn func(ctx context.Context, log *ledger.ChainedLog) error - -func (fn ExportWriterFn) Write(ctx context.Context, log *ledger.ChainedLog) error { - return fn(ctx, log) -} - -func (l *Ledger) Export(ctx context.Context, w ExportWriter) error { - return bunpaginate.Iterate( - ctx, - ledgerstore. - NewGetLogsQuery(ledgerstore.NewPaginatedQueryOptions[any](nil).WithPageSize(100)). - WithOrder(bunpaginate.OrderAsc), - func(ctx context.Context, q ledgerstore.GetLogsQuery) (*bunpaginate.Cursor[ledger.ChainedLog], error) { - return l.store.GetLogs(ctx, q) - }, - func(cursor *bunpaginate.Cursor[ledger.ChainedLog]) error { - for _, data := range cursor.Data { - if err := w.Write(ctx, &data); err != nil { - return err - } - } - return nil - }, - ) -} diff --git a/components/ledger/internal/engine/import.go b/components/ledger/internal/engine/import.go deleted file mode 100644 index e62722264b..0000000000 --- a/components/ledger/internal/engine/import.go +++ /dev/null @@ -1,128 +0,0 @@ -package engine - -import ( - "context" - "encoding/base64" - "fmt" - "math/big" - "reflect" - - ledger "github.com/formancehq/ledger/internal" - "github.com/pkg/errors" -) - -type ImportError struct { - err error - logID *big.Int -} - -func (i ImportError) Error() string { - return i.err.Error() -} - -func (i ImportError) Is(err error) bool { - _, ok := err.(ImportError) - return ok -} - -var _ error = (*ImportError)(nil) - -func newImportError(logID *big.Int, err error) ImportError { - return ImportError{ - logID: logID, - err: err, - } -} - -type InvalidIdError struct { - Expected *big.Int - Got *big.Int -} - -func (i InvalidIdError) Error() string { - return fmt.Sprintf("invalid id, got %s, expected %s", i.Got, i.Expected) -} - -func (i InvalidIdError) Is(err error) bool { - _, ok := err.(InvalidIdError) - return ok -} - -var _ error = (*InvalidIdError)(nil) - -func newInvalidIdError(got, expected *big.Int) ImportError { - return newImportError(got, InvalidIdError{ - Expected: expected, - Got: got, - }) -} - -type InvalidHashError struct { - Expected []byte - Got []byte -} - -func (i InvalidHashError) Error() string { - return fmt.Sprintf( - "invalid hash, expected %s got %s", - base64.StdEncoding.EncodeToString(i.Expected), - base64.StdEncoding.EncodeToString(i.Got), - ) -} - -func (i InvalidHashError) Is(err error) bool { - _, ok := err.(InvalidHashError) - return ok -} - -var _ error = (*InvalidHashError)(nil) - -func newInvalidHashError(logID *big.Int, got, expected []byte) ImportError { - return newImportError(logID, InvalidHashError{ - Expected: expected, - Got: got, - }) -} - -func (l *Ledger) Import(ctx context.Context, stream chan *ledger.ChainedLog) error { - if l.config.LedgerState.State != "initializing" { - return errors.New("ledger must be in initializing state to be imported") - } - batch := make([]*ledger.ChainedLog, 0) - for log := range stream { - lastLog := l.chain.GetLastLog() - nextLogID := big.NewInt(0) - if lastLog != nil { - nextLogID = nextLogID.Add(lastLog.ID, big.NewInt(1)) - } - if log.ID.String() != nextLogID.String() { - return newInvalidIdError(log.ID, nextLogID) - } - logHash := log.Hash - log.Hash = nil - log.ID = big.NewInt(0) - log.ComputeHash(lastLog) - - if !reflect.DeepEqual(log.Hash, logHash) { - return newInvalidHashError(log.ID, log.Hash, logHash) - } - - log.ID = nextLogID - l.chain.ReplaceLast(log) - - batch = append(batch, log) - if len(batch) == 100 { // notes(gfyrag): maybe we could parameterize that, but i don't think it will be useful - if err := l.store.InsertLogs(ctx, batch...); err != nil { - return err - } - batch = make([]*ledger.ChainedLog, 0) - } - } - if len(batch) > 0 { - if err := l.store.InsertLogs(ctx, batch...); err != nil { - return err - } - } - - return nil -} diff --git a/components/ledger/internal/engine/ledger.go b/components/ledger/internal/engine/ledger.go deleted file mode 100644 index 0b5271fef4..0000000000 --- a/components/ledger/internal/engine/ledger.go +++ /dev/null @@ -1,195 +0,0 @@ -package engine - -import ( - "context" - "math/big" - "sync" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/ledger/internal/engine/chain" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/formancehq/ledger/internal/storage/systemstore" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/bus" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/storage/ledgerstore" -) - -type Ledger struct { - commander *command.Commander - systemStore *systemstore.Store - store *ledgerstore.Store - mu sync.Mutex - config LedgerConfig - chain *chain.Chain -} - -type GlobalLedgerConfig struct { - batchSize int -} - -type LedgerConfig struct { - GlobalLedgerConfig - driver.LedgerState - isSchemaUpToDate bool -} - -var ( - defaultLedgerConfig = GlobalLedgerConfig{ - batchSize: 50, - } -) - -func New( - systemStore *systemstore.Store, - store *ledgerstore.Store, - publisher message.Publisher, - compiler *command.Compiler, - ledgerConfig LedgerConfig, -) *Ledger { - var monitor bus.Monitor = bus.NewNoOpMonitor() - if publisher != nil { - monitor = bus.NewLedgerMonitor(publisher, store.Name()) - } - chain := chain.New(store) - ret := &Ledger{ - commander: command.New( - store, - command.NewDefaultLocker(), - compiler, - command.NewReferencer(), - monitor, - chain, - ledgerConfig.batchSize, - ), - store: store, - config: ledgerConfig, - systemStore: systemStore, - chain: chain, - } - return ret -} - -func (l *Ledger) Start(ctx context.Context) { - if err := l.chain.Init(ctx); err != nil { - panic(err) - } - go l.commander.Run(logging.ContextWithField(ctx, "component", "commander")) -} - -func (l *Ledger) Close(ctx context.Context) { - logging.FromContext(ctx).Debugf("Close commander") - l.commander.Close() -} - -func (l *Ledger) GetTransactions(ctx context.Context, q ledgerstore.GetTransactionsQuery) (*bunpaginate.Cursor[ledger.ExpandedTransaction], error) { - txs, err := l.store.GetTransactions(ctx, q) - return txs, newStorageError(err, "getting transactions") -} - -func (l *Ledger) CountTransactions(ctx context.Context, q ledgerstore.GetTransactionsQuery) (int, error) { - count, err := l.store.CountTransactions(ctx, q) - return count, newStorageError(err, "counting transactions") -} - -func (l *Ledger) GetTransactionWithVolumes(ctx context.Context, query ledgerstore.GetTransactionQuery) (*ledger.ExpandedTransaction, error) { - tx, err := l.store.GetTransactionWithVolumes(ctx, query) - return tx, newStorageError(err, "getting transaction") -} - -func (l *Ledger) CountAccounts(ctx context.Context, a ledgerstore.GetAccountsQuery) (int, error) { - count, err := l.store.CountAccounts(ctx, a) - return count, newStorageError(err, "counting accounts") -} - -func (l *Ledger) GetAccountsWithVolumes(ctx context.Context, a ledgerstore.GetAccountsQuery) (*bunpaginate.Cursor[ledger.ExpandedAccount], error) { - accounts, err := l.store.GetAccountsWithVolumes(ctx, a) - return accounts, newStorageError(err, "getting accounts") -} - -func (l *Ledger) GetAccountWithVolumes(ctx context.Context, q ledgerstore.GetAccountQuery) (*ledger.ExpandedAccount, error) { - accounts, err := l.store.GetAccountWithVolumes(ctx, q) - return accounts, newStorageError(err, "getting account") -} - -func (l *Ledger) GetAggregatedBalances(ctx context.Context, q ledgerstore.GetAggregatedBalanceQuery) (ledger.BalancesByAssets, error) { - balances, err := l.store.GetAggregatedBalances(ctx, q) - return balances, newStorageError(err, "getting balances aggregated") -} - -func (l *Ledger) GetLogs(ctx context.Context, q ledgerstore.GetLogsQuery) (*bunpaginate.Cursor[ledger.ChainedLog], error) { - logs, err := l.store.GetLogs(ctx, q) - return logs, newStorageError(err, "getting logs") -} - -func (l *Ledger) markInUseIfNeeded(ctx context.Context) { - if l.config.LedgerState.State == systemstore.StateInitializing { - if err := l.systemStore.UpdateLedgerState(ctx, l.store.Name(), systemstore.StateInUse); err != nil { - logging.FromContext(ctx).Error("Unable to declare ledger as in use") - return - } - l.config.LedgerState.State = systemstore.StateInUse - } -} - -func (l *Ledger) CreateTransaction(ctx context.Context, parameters command.Parameters, data ledger.RunScript) (*ledger.Transaction, error) { - ret, err := l.commander.CreateTransaction(ctx, parameters, data) - if err != nil { - return nil, NewCommandError(err) - } - l.markInUseIfNeeded(ctx) - return ret, nil -} - -func (l *Ledger) RevertTransaction(ctx context.Context, parameters command.Parameters, id *big.Int, force, atEffectiveDate bool) (*ledger.Transaction, error) { - ret, err := l.commander.RevertTransaction(ctx, parameters, id, force, atEffectiveDate) - if err != nil { - return nil, NewCommandError(err) - } - l.markInUseIfNeeded(ctx) - return ret, nil -} - -func (l *Ledger) SaveMeta(ctx context.Context, parameters command.Parameters, targetType string, targetID any, m metadata.Metadata) error { - if err := l.commander.SaveMeta(ctx, parameters, targetType, targetID, m); err != nil { - return NewCommandError(err) - } - - l.markInUseIfNeeded(ctx) - return nil -} - -func (l *Ledger) DeleteMetadata(ctx context.Context, parameters command.Parameters, targetType string, targetID any, key string) error { - if err := l.commander.DeleteMetadata(ctx, parameters, targetType, targetID, key); err != nil { - return NewCommandError(err) - } - - l.markInUseIfNeeded(ctx) - return nil -} - -func (l *Ledger) IsDatabaseUpToDate(ctx context.Context) (bool, error) { - if l.config.isSchemaUpToDate { - return true, nil - } - l.mu.Lock() - defer l.mu.Unlock() - - if l.config.isSchemaUpToDate { - return true, nil - } - - var err error - l.config.isSchemaUpToDate, err = l.store.IsUpToDate(ctx) - - return l.config.isSchemaUpToDate, err -} - -func (l *Ledger) GetVolumesWithBalances(ctx context.Context, q ledgerstore.GetVolumesWithBalancesQuery) (*bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount], error) { - volumes, err := l.store.GetVolumesWithBalances(ctx, q) - return volumes, newStorageError(err, "getting Volumes with balances") -} diff --git a/components/ledger/internal/engine/migrations.go b/components/ledger/internal/engine/migrations.go deleted file mode 100644 index 47eda0bcd8..0000000000 --- a/components/ledger/internal/engine/migrations.go +++ /dev/null @@ -1,11 +0,0 @@ -package engine - -import ( - "context" - - "github.com/formancehq/go-libs/migrations" -) - -func (l *Ledger) GetMigrationsInfo(ctx context.Context) ([]migrations.Info, error) { - return l.store.GetMigrationsInfo(ctx) -} diff --git a/components/ledger/internal/engine/module.go b/components/ledger/internal/engine/module.go deleted file mode 100644 index 608d9422ea..0000000000 --- a/components/ledger/internal/engine/module.go +++ /dev/null @@ -1,58 +0,0 @@ -package engine - -import ( - "context" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/ledger/internal/bus" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/driver" - "go.uber.org/fx" -) - -type NumscriptCacheConfiguration struct { - MaxCount int -} - -type Configuration struct { - NumscriptCache NumscriptCacheConfiguration - LedgerBatchSize int -} - -func Module(configuration Configuration) fx.Option { - return fx.Options( - fx.Provide(func( - storageDriver *driver.Driver, - publisher message.Publisher, - metricsRegistry metrics.GlobalRegistry, - logger logging.Logger, - ) *Resolver { - options := []option{ - WithMessagePublisher(publisher), - WithMetricsRegistry(metricsRegistry), - WithLogger(logger), - } - if configuration.NumscriptCache.MaxCount != 0 { - options = append(options, WithCompiler(command.NewCompiler(configuration.NumscriptCache.MaxCount))) - } - if configuration.LedgerBatchSize != 0 { - options = append(options, WithLedgerConfig(GlobalLedgerConfig{ - batchSize: configuration.LedgerBatchSize, - })) - } - return NewResolver(storageDriver, options...) - }), - fx.Provide(fx.Annotate(bus.NewNoOpMonitor, fx.As(new(bus.Monitor)))), - fx.Provide(fx.Annotate(metrics.NewNoOpRegistry, fx.As(new(metrics.GlobalRegistry)))), - //TODO(gfyrag): Move in pkg/ledger package - fx.Invoke(func(lc fx.Lifecycle, resolver *Resolver) { - lc.Append(fx.Hook{ - OnStop: func(ctx context.Context) error { - return resolver.CloseLedgers(ctx) - }, - }) - }), - ) -} diff --git a/components/ledger/internal/engine/resolver.go b/components/ledger/internal/engine/resolver.go deleted file mode 100644 index 738b01d1fb..0000000000 --- a/components/ledger/internal/engine/resolver.go +++ /dev/null @@ -1,171 +0,0 @@ -package engine - -import ( - "context" - "sync" - - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/formancehq/ledger/internal/storage/systemstore" - - "github.com/pkg/errors" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/ledger/internal/engine/command" - "github.com/formancehq/ledger/internal/opentelemetry/metrics" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/sirupsen/logrus" -) - -type option func(r *Resolver) - -func WithMessagePublisher(publisher message.Publisher) option { - return func(r *Resolver) { - r.publisher = publisher - } -} - -func WithMetricsRegistry(registry metrics.GlobalRegistry) option { - return func(r *Resolver) { - r.metricsRegistry = registry - } -} - -func WithCompiler(compiler *command.Compiler) option { - return func(r *Resolver) { - r.compiler = compiler - } -} - -func WithLogger(logger logging.Logger) option { - return func(r *Resolver) { - r.logger = logger - } -} - -func WithLedgerConfig(config GlobalLedgerConfig) option { - return func(r *Resolver) { - r.ledgerConfig = config - } -} - -var defaultOptions = []option{ - WithMetricsRegistry(metrics.NewNoOpRegistry()), - WithCompiler(command.NewCompiler(1024)), - WithLogger(logging.NewLogrus(logrus.New())), -} - -type Resolver struct { - storageDriver *driver.Driver - lock sync.RWMutex - metricsRegistry metrics.GlobalRegistry - //TODO(gfyrag): add a routine to clean old ledger - ledgers map[string]*Ledger - ledgerConfig GlobalLedgerConfig - compiler *command.Compiler - logger logging.Logger - publisher message.Publisher -} - -func NewResolver(storageDriver *driver.Driver, options ...option) *Resolver { - r := &Resolver{ - storageDriver: storageDriver, - ledgers: map[string]*Ledger{}, - ledgerConfig: defaultLedgerConfig, - } - for _, opt := range append(defaultOptions, options...) { - opt(r) - } - - return r -} - -func (r *Resolver) startLedger(ctx context.Context, name string, store *ledgerstore.Store, state driver.LedgerState) (*Ledger, error) { - - ledger := New(r.storageDriver.GetSystemStore(), store, r.publisher, r.compiler, LedgerConfig{ - GlobalLedgerConfig: r.ledgerConfig, - LedgerState: state, - }) - ledger.Start(logging.ContextWithLogger(context.Background(), r.logger)) - r.ledgers[name] = ledger - r.metricsRegistry.ActiveLedgers().Add(ctx, +1) - - return ledger, nil -} - -func (r *Resolver) GetLedger(ctx context.Context, name string) (*Ledger, error) { - if name == "" { - return nil, errors.New("empty name") - } - r.lock.RLock() - ledger, ok := r.ledgers[name] - r.lock.RUnlock() - - if !ok { - r.lock.Lock() - defer r.lock.Unlock() - - ledger, ok = r.ledgers[name] - if ok { - return ledger, nil - } - - ledgerConfiguration, err := r.storageDriver.GetSystemStore().GetLedger(ctx, name) - if err != nil { - return nil, err - } - - store, err := r.storageDriver.GetLedgerStore(ctx, name, driver.LedgerState{ - LedgerConfiguration: driver.LedgerConfiguration{ - Bucket: ledgerConfiguration.Bucket, - Metadata: ledgerConfiguration.Metadata, - }, - State: ledgerConfiguration.State, - }) - if err != nil { - return nil, err - } - - return r.startLedger(ctx, name, store, driver.LedgerState{ - LedgerConfiguration: driver.LedgerConfiguration{ - Bucket: ledgerConfiguration.Bucket, - Metadata: ledgerConfiguration.Metadata, - }, - }) - } - - return ledger, nil -} - -func (r *Resolver) CreateLedger(ctx context.Context, name string, configuration driver.LedgerConfiguration) (*Ledger, error) { - if name == "" { - return nil, errors.New("empty name") - } - - r.lock.Lock() - defer r.lock.Unlock() - - store, err := r.storageDriver.CreateLedgerStore(ctx, name, configuration) - if err != nil { - return nil, err - } - - return r.startLedger(ctx, name, store, driver.LedgerState{ - LedgerConfiguration: configuration, - State: systemstore.StateInitializing, - }) -} - -func (r *Resolver) CloseLedgers(ctx context.Context) error { - r.logger.Info("Close all ledgers") - defer func() { - r.logger.Info("All ledgers closed") - }() - for name, ledger := range r.ledgers { - r.logger.Infof("Close ledger %s", name) - ledger.Close(logging.ContextWithLogger(ctx, r.logger.WithField("ledger", name))) - delete(r.ledgers, name) - } - - return nil -} diff --git a/components/ledger/internal/engine/stats.go b/components/ledger/internal/engine/stats.go deleted file mode 100644 index 34a0cdb380..0000000000 --- a/components/ledger/internal/engine/stats.go +++ /dev/null @@ -1,32 +0,0 @@ -package engine - -import ( - "context" - - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/pkg/errors" -) - -type Stats struct { - Transactions int `json:"transactions"` - Accounts int `json:"accounts"` -} - -func (l *Ledger) Stats(ctx context.Context) (Stats, error) { - var stats Stats - - transactions, err := l.store.CountTransactions(ctx, ledgerstore.NewGetTransactionsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}))) - if err != nil { - return stats, errors.Wrap(err, "counting transactions") - } - - accounts, err := l.store.CountAccounts(ctx, ledgerstore.NewGetAccountsQuery(ledgerstore.NewPaginatedQueryOptions(ledgerstore.PITFilterWithVolumes{}))) - if err != nil { - return stats, errors.Wrap(err, "counting accounts") - } - - return Stats{ - Transactions: transactions, - Accounts: accounts, - }, nil -} diff --git a/components/ledger/internal/engine/utils/batching/batcher.go b/components/ledger/internal/engine/utils/batching/batcher.go deleted file mode 100644 index c41a853012..0000000000 --- a/components/ledger/internal/engine/utils/batching/batcher.go +++ /dev/null @@ -1,85 +0,0 @@ -package batching - -import ( - "context" - "fmt" - "sync" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/ledger/internal/engine/utils/job" -) - -type OnBatchProcessed[T any] func(...T) - -func NoOpOnBatchProcessed[T any]() func(...T) { - return func(t ...T) {} -} - -type pending[T any] struct { - object T - callback func() -} - -type batcherJob[T any] struct { - items []*pending[T] -} - -func (b batcherJob[T]) String() string { - return fmt.Sprintf("processing %d items", len(b.items)) -} - -func (b batcherJob[T]) Terminated() { - for _, v := range b.items { - v.callback() - } -} - -type Batcher[T any] struct { - *job.Runner[batcherJob[T]] - pending []*pending[T] - mu sync.Mutex - maxBatchSize int -} - -func (s *Batcher[T]) Append(object T, callback func()) { - s.mu.Lock() - s.pending = append(s.pending, &pending[T]{ - callback: callback, - object: object, - }) - s.mu.Unlock() - s.Runner.Next() -} - -func (s *Batcher[T]) nextBatch() *batcherJob[T] { - s.mu.Lock() - defer s.mu.Unlock() - - if len(s.pending) == 0 { - return nil - } - if len(s.pending) > s.maxBatchSize { - batch := s.pending[:s.maxBatchSize] - s.pending = s.pending[s.maxBatchSize:] - return &batcherJob[T]{ - items: batch, - } - } - batch := s.pending - s.pending = make([]*pending[T], 0) - return &batcherJob[T]{ - items: batch, - } -} - -func NewBatcher[T any](runner func(context.Context, ...T) error, nbWorkers, maxBatchSize int) *Batcher[T] { - ret := &Batcher[T]{ - maxBatchSize: maxBatchSize, - } - ret.Runner = job.NewJobRunner[batcherJob[T]](func(ctx context.Context, job *batcherJob[T]) error { - return runner(ctx, collectionutils.Map(job.items, func(from *pending[T]) T { - return from.object - })...) - }, ret.nextBatch, nbWorkers) - return ret -} diff --git a/components/ledger/internal/engine/utils/job/jobs.go b/components/ledger/internal/engine/utils/job/jobs.go deleted file mode 100644 index e695ae5738..0000000000 --- a/components/ledger/internal/engine/utils/job/jobs.go +++ /dev/null @@ -1,143 +0,0 @@ -package job - -import ( - "context" - "fmt" - "runtime/debug" - "sync/atomic" - - "github.com/alitto/pond" - "github.com/formancehq/go-libs/logging" - "github.com/pkg/errors" -) - -type Job interface { - Terminated() -} - -type builtJob struct { - terminatedFn func() -} - -func (j builtJob) Terminated() { - j.terminatedFn() -} - -func newJob(terminatedFn func()) *builtJob { - return &builtJob{ - terminatedFn: terminatedFn, - } -} - -type Runner[JOB Job] struct { - stopChan chan chan struct{} - runner func(context.Context, *JOB) error - nbWorkers int - parkedWorkers atomic.Int64 - nextJob func() *JOB - jobs chan *JOB - newJobsAvailable chan struct{} -} - -func (r *Runner[JOB]) Next() { - r.newJobsAvailable <- struct{}{} -} - -func (r *Runner[JOB]) Close() { - done := make(chan struct{}) - r.stopChan <- done - <-done -} - -func (r *Runner[JOB]) Run(ctx context.Context) { - - logger := logging.FromContext(ctx) - logger.Infof("Start worker") - - defer func() { - if e := recover(); e != nil { - logger.Error(e) - debug.PrintStack() - panic(e) - } - }() - - terminatedJobs := make(chan *JOB, r.nbWorkers) - jobsErrors := make(chan error, r.nbWorkers) - - w := pond.New(r.nbWorkers, r.nbWorkers) - for i := 0; i < r.nbWorkers; i++ { - i := i - w.Submit(func() { - defer func() { - if e := recover(); e != nil { - if err, isError := e.(error); isError { - jobsErrors <- errors.WithStack(err) - return - } - jobsErrors <- errors.WithStack(fmt.Errorf("%s", e)) - } - }() - logger := logger.WithFields(map[string]any{ - "worker": i, - }) - for { - select { - case job, ok := <-r.jobs: - if !ok { - logger.Debugf("Worker %d stopped", i) - return - } - logger := logger.WithField("job", job) - logger.Debugf("Got new job") - if err := r.runner(ctx, job); err != nil { - panic(err) - } - logger.Debugf("Job terminated") - terminatedJobs <- job - } - } - }) - } - - for { - select { - case jobError := <-jobsErrors: - panic(jobError) - case done := <-r.stopChan: - close(r.jobs) - w.StopAndWait() - close(terminatedJobs) - close(done) - return - case <-r.newJobsAvailable: - if r.parkedWorkers.Load() > 0 { - if job := r.nextJob(); job != nil { - r.jobs <- job - r.parkedWorkers.Add(-1) - } - } - case job := <-terminatedJobs: - (*job).Terminated() - if job := r.nextJob(); job != nil { - r.jobs <- job - } else { - r.parkedWorkers.Add(1) - } - } - } -} - -func NewJobRunner[JOB Job](runner func(context.Context, *JOB) error, nextJob func() *JOB, nbWorkers int) *Runner[JOB] { - parkedWorkers := atomic.Int64{} - parkedWorkers.Add(int64(nbWorkers)) - return &Runner[JOB]{ - stopChan: make(chan chan struct{}), - runner: runner, - nbWorkers: nbWorkers, - parkedWorkers: parkedWorkers, - nextJob: nextJob, - jobs: make(chan *JOB, nbWorkers), - newJobsAvailable: make(chan struct{}), - } -} diff --git a/components/ledger/internal/engine/utils/job/jobs_test.go b/components/ledger/internal/engine/utils/job/jobs_test.go deleted file mode 100644 index 6b955e377c..0000000000 --- a/components/ledger/internal/engine/utils/job/jobs_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package job - -import ( - "context" - "sync/atomic" - "testing" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/stretchr/testify/require" -) - -func TestWorkerPool(t *testing.T) { - t.Parallel() - - const countJobs = 10000 - createdJobs := atomic.Int64{} - terminatedJobs := atomic.Int64{} - nextJob := func() *builtJob { - if createdJobs.Load() == 10000 { - return nil - } - createdJobs.Add(1) - return newJob(func() { - terminatedJobs.Add(1) - }) - } - runner := func(ctx context.Context, job *builtJob) error { - return nil - } - ctx := logging.TestingContext() - - pool := NewJobRunner[builtJob](runner, nextJob, 5) - go pool.Run(ctx) - defer pool.Close() - - for i := 0; i < 100; i++ { - go pool.Next() // Simulate random input - } - - require.Eventually(t, func() bool { - return countJobs == createdJobs.Load() - }, 5*time.Second, time.Millisecond*100) -} diff --git a/components/ledger/internal/log.go b/components/ledger/internal/log.go deleted file mode 100644 index 87cb1772ce..0000000000 --- a/components/ledger/internal/log.go +++ /dev/null @@ -1,317 +0,0 @@ -package ledger - -import ( - "context" - "crypto/sha256" - "encoding/json" - "math/big" - "reflect" - "strconv" - "strings" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/metadata" - "github.com/pkg/errors" -) - -type LogType int16 - -const ( - // TODO(gfyrag): Create dedicated log type for account and metadata - SetMetadataLogType LogType = iota // "SET_METADATA" - NewTransactionLogType // "NEW_TRANSACTION" - RevertedTransactionLogType // "REVERTED_TRANSACTION" - DeleteMetadataLogType -) - -func (l LogType) String() string { - switch l { - case SetMetadataLogType: - return "SET_METADATA" - case NewTransactionLogType: - return "NEW_TRANSACTION" - case RevertedTransactionLogType: - return "REVERTED_TRANSACTION" - case DeleteMetadataLogType: - return "DELETE_METADATA" - } - - return "" -} - -func LogTypeFromString(logType string) LogType { - switch logType { - case "SET_METADATA": - return SetMetadataLogType - case "NEW_TRANSACTION": - return NewTransactionLogType - case "REVERTED_TRANSACTION": - return RevertedTransactionLogType - case "DELETE_METADATA": - return DeleteMetadataLogType - } - - panic(errors.New("invalid log type")) -} - -// Needed in order to keep the compatibility with the openapi response for -// ListLogs. -func (lt LogType) MarshalJSON() ([]byte, error) { - return json.Marshal(lt.String()) -} - -func (lt *LogType) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - - *lt = LogTypeFromString(s) - - return nil -} - -type ChainedLogWithContext struct { - ChainedLog - Context context.Context -} - -type ChainedLog struct { - Log - ID *big.Int `json:"id"` - Hash []byte `json:"hash"` -} - -func (l *ChainedLog) WithID(id uint64) *ChainedLog { - l.ID = big.NewInt(int64(id)) - return l -} - -func (l *ChainedLog) UnmarshalJSON(data []byte) error { - type auxLog ChainedLog - type log struct { - auxLog - Data json.RawMessage `json:"data"` - } - rawLog := log{} - if err := json.Unmarshal(data, &rawLog); err != nil { - return err - } - - var err error - rawLog.auxLog.Data, err = HydrateLog(rawLog.Type, rawLog.Data) - if err != nil { - return err - } - *l = ChainedLog(rawLog.auxLog) - return err -} - -func (l *ChainedLog) ComputeHash(previous *ChainedLog) { - digest := sha256.New() - enc := json.NewEncoder(digest) - - if previous != nil { - if err := enc.Encode(previous.Hash); err != nil { - panic(err) - } - } - - if err := enc.Encode(l); err != nil { - panic(err) - } - - l.Hash = digest.Sum(nil) -} - -type Log struct { - Type LogType `json:"type"` - Data any `json:"data"` - Date time.Time `json:"date"` - IdempotencyKey string `json:"idempotencyKey"` -} - -func (l *Log) WithDate(date time.Time) *Log { - l.Date = date - return l -} - -func (l *Log) WithIdempotencyKey(key string) *Log { - l.IdempotencyKey = key - return l -} - -func (l *Log) ChainLog(previous *ChainedLog) *ChainedLog { - ret := &ChainedLog{ - Log: *l, - ID: big.NewInt(0), - } - ret.ComputeHash(previous) - if previous != nil { - ret.ID = ret.ID.Add(previous.ID, big.NewInt(1)) - } - return ret -} - -type AccountMetadata map[string]metadata.Metadata - -type NewTransactionLogPayload struct { - Transaction *Transaction `json:"transaction"` - AccountMetadata AccountMetadata `json:"accountMetadata"` -} - -func NewTransactionLogWithDate(tx *Transaction, accountMetadata map[string]metadata.Metadata, time time.Time) *Log { - // Since the id is unique and the hash is a hash of the previous log, they - // will be filled at insertion time during the batch process. - return &Log{ - Type: NewTransactionLogType, - Date: time, - Data: NewTransactionLogPayload{ - Transaction: tx, - AccountMetadata: accountMetadata, - }, - } -} - -func NewTransactionLog(tx *Transaction, accountMetadata map[string]metadata.Metadata) *Log { - return NewTransactionLogWithDate(tx, accountMetadata, time.Now()) -} - -type SetMetadataLogPayload struct { - TargetType string `json:"targetType"` - TargetID any `json:"targetId"` - Metadata metadata.Metadata `json:"metadata"` -} - -func (s *SetMetadataLogPayload) UnmarshalJSON(data []byte) error { - type X struct { - TargetType string `json:"targetType"` - TargetID json.RawMessage `json:"targetId"` - Metadata metadata.Metadata `json:"metadata"` - } - x := X{} - err := json.Unmarshal(data, &x) - if err != nil { - return err - } - var id interface{} - switch strings.ToUpper(x.TargetType) { - case strings.ToUpper(MetaTargetTypeAccount): - id = "" - err = json.Unmarshal(x.TargetID, &id) - case strings.ToUpper(MetaTargetTypeTransaction): - id, err = strconv.ParseUint(string(x.TargetID), 10, 64) - default: - panic("unknown type") - } - if err != nil { - return err - } - - *s = SetMetadataLogPayload{ - TargetType: x.TargetType, - TargetID: id, - Metadata: x.Metadata, - } - return nil -} - -func NewSetMetadataLog(at time.Time, metadata SetMetadataLogPayload) *Log { - // Since the id is unique and the hash is a hash of the previous log, they - // will be filled at insertion time during the batch process. - return &Log{ - Type: SetMetadataLogType, - Date: at, - Data: metadata, - } -} - -type DeleteMetadataLogPayload struct { - TargetType string `json:"targetType"` - TargetID any `json:"targetId"` - Key string `json:"key"` -} - -func NewDeleteMetadataLog(at time.Time, payload DeleteMetadataLogPayload) *Log { - // Since the id is unique and the hash is a hash of the previous log, they - // will be filled at insertion time during the batch process. - return &Log{ - Type: DeleteMetadataLogType, - Date: at, - Data: payload, - } -} - -func NewSetMetadataOnAccountLog(at time.Time, account string, metadata metadata.Metadata) *Log { - return &Log{ - Type: SetMetadataLogType, - Date: at, - Data: SetMetadataLogPayload{ - TargetType: MetaTargetTypeAccount, - TargetID: account, - Metadata: metadata, - }, - } -} - -func NewSetMetadataOnTransactionLog(at time.Time, txID *big.Int, metadata metadata.Metadata) *Log { - return &Log{ - Type: SetMetadataLogType, - Date: at, - Data: SetMetadataLogPayload{ - TargetType: MetaTargetTypeTransaction, - TargetID: txID, - Metadata: metadata, - }, - } -} - -type RevertedTransactionLogPayload struct { - RevertedTransactionID *big.Int `json:"revertedTransactionID"` - RevertTransaction *Transaction `json:"transaction"` -} - -func NewRevertedTransactionLog(at time.Time, revertedTxID *big.Int, tx *Transaction) *Log { - return &Log{ - Type: RevertedTransactionLogType, - Date: at, - Data: RevertedTransactionLogPayload{ - RevertedTransactionID: revertedTxID, - RevertTransaction: tx, - }, - } -} - -func HydrateLog(_type LogType, data []byte) (any, error) { - var payload any - switch _type { - case NewTransactionLogType: - payload = &NewTransactionLogPayload{} - case SetMetadataLogType: - payload = &SetMetadataLogPayload{} - case RevertedTransactionLogType: - payload = &RevertedTransactionLogPayload{} - default: - panic("unknown type " + _type.String()) - } - err := json.Unmarshal(data, &payload) - if err != nil { - return nil, err - } - - return reflect.ValueOf(payload).Elem().Interface(), nil -} - -type Accounts map[string]Account - -func ChainLogs(logs ...*Log) []*ChainedLog { - var previous *ChainedLog - ret := make([]*ChainedLog, 0) - for _, log := range logs { - next := log.ChainLog(previous) - ret = append(ret, next) - previous = next - } - return ret -} diff --git a/components/ledger/internal/machine/account.go b/components/ledger/internal/machine/account.go deleted file mode 100644 index bf98e74b3c..0000000000 --- a/components/ledger/internal/machine/account.go +++ /dev/null @@ -1,21 +0,0 @@ -package machine - -import ( - "fmt" - - "github.com/formancehq/ledger/pkg/core/accounts" -) - -type AccountAddress string - -func (AccountAddress) GetType() Type { return TypeAccount } -func (a AccountAddress) String() string { - return fmt.Sprintf("@%v", string(a)) -} - -func ValidateAccountAddress(acc AccountAddress) error { - if !accounts.Regexp.MatchString(string(acc)) { - return fmt.Errorf("accounts should respect pattern %s", accounts.Pattern) - } - return nil -} diff --git a/components/ledger/internal/machine/address.go b/components/ledger/internal/machine/address.go deleted file mode 100644 index 56c8fac4c9..0000000000 --- a/components/ledger/internal/machine/address.go +++ /dev/null @@ -1,31 +0,0 @@ -package machine - -import "encoding/binary" - -// Address represents an address in the machine's resources, which include -// constants (literals) and variables passed to the program -type Address uint16 - -func NewAddress(x uint16) Address { - return Address(x) -} - -func (a Address) ToBytes() []byte { - bytes := make([]byte, 2) - binary.LittleEndian.PutUint16(bytes, uint16(a)) - return bytes -} - -type Addresses []Address - -func (a Addresses) Len() int { - return len(a) -} - -func (a Addresses) Less(i, j int) bool { - return a[i] < a[j] -} - -func (a Addresses) Swap(i, j int) { - a[i], a[j] = a[j], a[i] -} diff --git a/components/ledger/internal/machine/allotment.go b/components/ledger/internal/machine/allotment.go deleted file mode 100644 index db1ae393e5..0000000000 --- a/components/ledger/internal/machine/allotment.go +++ /dev/null @@ -1,75 +0,0 @@ -package machine - -import ( - "errors" - "fmt" - "math/big" -) - -type Allotment []big.Rat - -func (Allotment) GetType() Type { return TypeAllotment } - -func NewAllotment(portions []Portion) (*Allotment, error) { - n := len(portions) - total := big.NewRat(0, 1) - var remainingIdx *int - allotment := make([]big.Rat, n) - for i := 0; i < n; i++ { - if portions[i].Remaining { - if remainingIdx != nil { - return nil, errors.New("two uses of `remaining` in the same allotment") - } - allotment[i] = big.Rat{} // temporary - idx := i - remainingIdx = &idx - } else { - rat := *portions[i].Specific - allotment[i] = rat - total.Add(total, &rat) - } - } - if total.Cmp(big.NewRat(1, 1)) == 1 { - return nil, errors.New("sum of portions exceeded 100%") - } - if remainingIdx != nil { - remaining := big.NewRat(1, 1) - remaining.Sub(remaining, total) - allotment[*remainingIdx] = *remaining - } - result := Allotment(allotment) - return &result, nil -} - -func (a Allotment) String() string { - out := "{ " - for i, ratio := range a { - out += fmt.Sprintf("%v", &ratio) - if i != len(a)-1 { - out += " : " - } - } - return out + " }" -} - -func (a Allotment) Allocate(amount *MonetaryInt) []*MonetaryInt { - amtBigint := big.Int(*amount) - parts := make([]*MonetaryInt, len(a)) - totalAllocated := Zero - // for every part in the allotment, calculate the floored value - for i, allot := range a { - var res big.Int - res.Mul(&amtBigint, allot.Num()) - res.Div(&res, allot.Denom()) - mi := MonetaryInt(res) - parts[i] = &mi - totalAllocated = totalAllocated.Add(parts[i]) - } - for i := range parts { - if totalAllocated.Lt(amount) { - parts[i] = parts[i].Add(NewMonetaryInt(1)) - totalAllocated = totalAllocated.Add(NewMonetaryInt(1)) - } - } - return parts -} diff --git a/components/ledger/internal/machine/allotment_test.go b/components/ledger/internal/machine/allotment_test.go deleted file mode 100644 index 22d76ee869..0000000000 --- a/components/ledger/internal/machine/allotment_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package machine - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAllocate(t *testing.T) { - allotment, err := NewAllotment([]Portion{ - {Specific: big.NewRat(4, 5)}, - {Specific: big.NewRat(2, 25)}, - {Specific: big.NewRat(3, 25)}, - }) - require.NoError(t, err) - - parts := allotment.Allocate(NewMonetaryInt(15)) - expectedParts := []*MonetaryInt{NewMonetaryInt(13), NewMonetaryInt(1), NewMonetaryInt(1)} - if len(parts) != len(expectedParts) { - t.Fatalf("unexpected output %v != %v", parts, expectedParts) - } - for i := range parts { - if !parts[i].Equal(expectedParts[i]) { - t.Fatalf("unexpected output %v != %v", parts, expectedParts) - } - } -} - -func TestAllocateEmptyRemainder(t *testing.T) { - allotment, err := NewAllotment([]Portion{ - {Specific: big.NewRat(1, 2)}, - {Specific: big.NewRat(1, 2)}, - {Remaining: true}, - }) - require.NoError(t, err) - - parts := allotment.Allocate(NewMonetaryInt(15)) - expectedParts := []*MonetaryInt{NewMonetaryInt(8), NewMonetaryInt(7), NewMonetaryInt(0)} - if len(parts) != len(expectedParts) { - t.Fatalf("unexpected output %v != %v", parts, expectedParts) - } - for i := range parts { - if !parts[i].Equal(expectedParts[i]) { - t.Fatalf("unexpected output %v != %v", parts, expectedParts) - } - } - -} - -func TestInvalidAllotments(t *testing.T) { - _, err := NewAllotment([]Portion{ - {Remaining: true}, - {Specific: big.NewRat(2, 25)}, - {Remaining: true}, - }) - assert.Errorf(t, err, "allowed two remainings") - - _, err = NewAllotment([]Portion{ - {Specific: big.NewRat(1, 2)}, - {Specific: big.NewRat(1, 2)}, - {Specific: big.NewRat(1, 2)}, - }) - assert.Errorf(t, err, "allowed more than 100%") -} diff --git a/components/ledger/internal/machine/asset.go b/components/ledger/internal/machine/asset.go deleted file mode 100644 index ceb03ac659..0000000000 --- a/components/ledger/internal/machine/asset.go +++ /dev/null @@ -1,27 +0,0 @@ -package machine - -import ( - "fmt" - - "github.com/formancehq/ledger/pkg/core/assets" -) - -type Asset string - -func (Asset) GetType() Type { return TypeAsset } -func (a Asset) String() string { - return fmt.Sprintf("%v", string(a)) -} - -type HasAsset interface { - GetAsset() Asset -} - -func (a Asset) GetAsset() Asset { return a } - -func ValidateAsset(ass Asset) error { - if !assets.Regexp.MatchString(string(ass)) { - return fmt.Errorf("asset should respect pattern '%s'", assets.Pattern) - } - return nil -} diff --git a/components/ledger/internal/machine/docs/instructions.md b/components/ledger/internal/machine/docs/instructions.md deleted file mode 100644 index 17ee360b67..0000000000 --- a/components/ledger/internal/machine/docs/instructions.md +++ /dev/null @@ -1,27 +0,0 @@ -# Formance Machine Instruction Set - -``` -INIT -LOAD address -BEGIN -BALANCE asset, address -IPUSH value -MPUSH value -RPUSH value -GET register -SET register -IADD -ISUB -IMUL -MADD -MSUB -RMUL -RDD -RSUB -TXSTART size -TXEND -SEND source, destination, value -FLUSH -COMMIT -ABORT -``` diff --git a/components/ledger/internal/machine/docs/types.md b/components/ledger/internal/machine/docs/types.md deleted file mode 100644 index 195c2e8970..0000000000 --- a/components/ledger/internal/machine/docs/types.md +++ /dev/null @@ -1,9 +0,0 @@ -# Formance Machine Types - -``` -# Integers -unsigned 128 bits integers - -# Monetary values -{USD/2 100} -``` diff --git a/components/ledger/internal/machine/errors.go b/components/ledger/internal/machine/errors.go deleted file mode 100644 index fd6c8b41b0..0000000000 --- a/components/ledger/internal/machine/errors.go +++ /dev/null @@ -1,136 +0,0 @@ -package machine - -import ( - "fmt" - - "github.com/pkg/errors" -) - -var ( - ErrScriptFailed = errors.New("script exited with error code") - ErrResourcesNotInitialized = errors.New("resources not initialized") - ErrBalancesNotInitialized = errors.New("balances not initialized") - ErrResourceNotFound = errors.New("resource not found") -) - -type ErrInvalidScript struct { - msg string -} - -func (e *ErrInvalidScript) Error() string { - return e.msg -} - -func (e *ErrInvalidScript) Is(err error) bool { - _, ok := err.(*ErrInvalidScript) - return ok -} - -func NewErrInvalidScript(f string, args ...any) *ErrInvalidScript { - return &ErrInvalidScript{ - msg: fmt.Sprintf(f, args...), - } -} - -type ErrInsufficientFund struct { - msg string -} - -func (e *ErrInsufficientFund) Error() string { - return e.msg -} - -func (e *ErrInsufficientFund) Is(err error) bool { - _, ok := err.(*ErrInsufficientFund) - return ok -} - -func NewErrInsufficientFund(f string, args ...any) *ErrInsufficientFund { - return &ErrInsufficientFund{ - msg: fmt.Sprintf(f, args...), - } -} - -func IsInsufficientFundError(err error) bool { - return errors.Is(err, &ErrInsufficientFund{}) -} - -type ErrNegativeAmount struct { - msg string -} - -func (e *ErrNegativeAmount) Error() string { - return e.msg -} - -func (e *ErrNegativeAmount) Is(err error) bool { - _, ok := err.(*ErrNegativeAmount) - return ok -} - -func NewErrNegativeAmount(f string, args ...any) *ErrNegativeAmount { - return &ErrNegativeAmount{ - msg: fmt.Sprintf(f, args...), - } -} - -type ErrMissingMetadata struct { - msg string -} - -func (e *ErrMissingMetadata) Error() string { - return e.msg -} - -func (e *ErrMissingMetadata) Is(err error) bool { - _, ok := err.(*ErrMissingMetadata) - return ok -} - -func NewErrMissingMetadata(f string, args ...any) *ErrMissingMetadata { - return &ErrMissingMetadata{ - msg: fmt.Sprintf(f, args...), - } -} - -type ErrInvalidVars struct { - msg string -} - -func (e *ErrInvalidVars) Error() string { - return e.msg -} - -func (e *ErrInvalidVars) Is(err error) bool { - _, ok := err.(*ErrInvalidVars) - return ok -} - -func NewErrInvalidVars(f string, args ...any) *ErrInvalidVars { - return &ErrInvalidVars{ - msg: fmt.Sprintf(f, args...), - } -} - -type ErrMetadataOverride struct { - key string -} - -func (e *ErrMetadataOverride) Error() string { - return fmt.Sprintf("cannot override metadata '%s'", e.key) -} - -func (e *ErrMetadataOverride) Is(err error) bool { - _, ok := err.(*ErrMetadataOverride) - return ok -} - -func NewErrMetadataOverride(key string) *ErrMetadataOverride { - return &ErrMetadataOverride{ - key: key, - } -} - -func IsMetadataOverride(err error) bool { - return errors.Is(err, &ErrMetadataOverride{}) -} diff --git a/components/ledger/internal/machine/examples/basic.go b/components/ledger/internal/machine/examples/basic.go deleted file mode 100644 index ba87e3b190..0000000000 --- a/components/ledger/internal/machine/examples/basic.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "context" - "fmt" - "math/big" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/machine/script/compiler" - vm2 "github.com/formancehq/ledger/internal/machine/vm" -) - -func main() { - program, err := compiler.Compile(` - // This is a comment - vars { - account $dest - } - send [COIN 99] ( - source = { - 15% from { - @alice - @bob - } - remaining from @bob - } - destination = $dest - )`) - if err != nil { - panic(err) - } - fmt.Print(program) - - m := vm2.NewMachine(*program) - m.Debug = true - - if err = m.SetVarsFromJSON(map[string]string{ - "dest": "charlie", - }); err != nil { - panic(err) - } - - initialVolumes := map[string]map[string]*big.Int{ - "alice": { - "COIN": big.NewInt(10), - }, - "bob": { - "COIN": big.NewInt(100), - }, - } - - store := vm2.StaticStore{} - for account, balances := range initialVolumes { - store[account] = &vm2.AccountWithBalances{ - Account: ledger.Account{ - Address: account, - Metadata: metadata.Metadata{}, - }, - Balances: balances, - } - } - - _, _, err = m.ResolveResources(context.Background(), vm2.EmptyStore) - if err != nil { - panic(err) - } - - err = m.ResolveBalances(context.Background(), store) - if err != nil { - panic(err) - } - - err = m.Execute() - if err != nil { - panic(err) - } - - fmt.Println(m.Postings) - fmt.Println(m.TxMeta) -} diff --git a/components/ledger/internal/machine/funding.go b/components/ledger/internal/machine/funding.go deleted file mode 100644 index f3b49e176b..0000000000 --- a/components/ledger/internal/machine/funding.go +++ /dev/null @@ -1,177 +0,0 @@ -package machine - -import ( - "errors" - "fmt" - "strings" - - collec "github.com/formancehq/go-libs/collectionutils" -) - -type FundingPart struct { - Amount *MonetaryInt - Account AccountAddress -} - -func (Funding) GetType() Type { return TypeFunding } - -func (f Funding) GetAsset() Asset { return f.Asset } - -func (lhs FundingPart) Equals(rhs FundingPart) bool { - return lhs.Account == rhs.Account && lhs.Amount.Equal(rhs.Amount) -} - -type Funding struct { - Asset Asset - Parts []FundingPart -} - -func (lhs Funding) Equals(rhs Funding) bool { - if lhs.Asset != rhs.Asset { - return false - } - if len(lhs.Parts) != len(rhs.Parts) { - return false - } - for i := range lhs.Parts { - if !lhs.Parts[i].Equals(rhs.Parts[i]) { - return false - } - } - return true -} - -func (f Funding) String() string { - out := fmt.Sprintf("[%v", string(f.Asset)) - for _, part := range f.Parts { - out += fmt.Sprintf(" %v %v", part.Account, part.Amount) - } - return out + "]" -} - -func (f Funding) Take(amount *MonetaryInt) (Funding, Funding, error) { - result := Funding{ - Asset: f.Asset, - } - remainder := Funding{ - Asset: f.Asset, - } - - if amount.Eq(Zero) && len(f.Parts) > 0 { - result.Parts = append(result.Parts, FundingPart{ - Account: f.Parts[0].Account, - Amount: amount, - }) - } - - remainingToWithdraw := amount - i := 0 - for remainingToWithdraw.Gt(Zero) && i < len(f.Parts) { - amtToWithdraw := f.Parts[i].Amount - // if this part has excess balance, put it in the remainder & only take what's needed - if amtToWithdraw.Gt(remainingToWithdraw) { - rem := amtToWithdraw.Sub(remainingToWithdraw) - amtToWithdraw = remainingToWithdraw - remainder.Parts = append(remainder.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: rem, - }) - } - remainingToWithdraw = remainingToWithdraw.Sub(amtToWithdraw) - result.Parts = append(result.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: amtToWithdraw, - }) - i++ - } - for i < len(f.Parts) { - remainder.Parts = append(remainder.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: f.Parts[i].Amount, - }) - i++ - } - if !remainingToWithdraw.Eq(Zero) { - - lstAccounts := collec.Map[FundingPart, string](f.Parts, func(fp FundingPart) string { - return fp.Account.String() - }) - - return Funding{}, Funding{}, NewErrInsufficientFund(fmt.Sprintf("account(s) %s had/have insufficient funds", strings.Join(lstAccounts, "|"))) - } - return result, remainder, nil -} - -func (f Funding) TakeMax(amount *MonetaryInt) (Funding, Funding) { - result := Funding{ - Asset: f.Asset, - } - remainder := Funding{ - Asset: f.Asset, - } - remainingToWithdraw := amount - i := 0 - for remainingToWithdraw.Gt(Zero) && i < len(f.Parts) { - amtToWithdraw := f.Parts[i].Amount - // if this part has excess balance, put it in the remainder & only take what's needed - if amtToWithdraw.Gt(remainingToWithdraw) { - rem := amtToWithdraw.Sub(remainingToWithdraw) - amtToWithdraw = remainingToWithdraw - remainder.Parts = append(remainder.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: rem, - }) - } - remainingToWithdraw = remainingToWithdraw.Sub(amtToWithdraw) - result.Parts = append(result.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: amtToWithdraw, - }) - i++ - } - for i < len(f.Parts) { - remainder.Parts = append(remainder.Parts, FundingPart{ - Account: f.Parts[i].Account, - Amount: f.Parts[i].Amount, - }) - i++ - } - return result, remainder -} - -func (f Funding) Concat(other Funding) (Funding, error) { - if f.Asset != other.Asset { - return Funding{}, errors.New("tried to concat different assets") - } - res := Funding{ - Asset: f.Asset, - Parts: f.Parts, - } - if len(res.Parts) > 0 && len(other.Parts) > 0 && res.Parts[len(res.Parts)-1].Account == other.Parts[0].Account { - res.Parts[len(res.Parts)-1].Amount = res.Parts[len(res.Parts)-1].Amount.Add(other.Parts[0].Amount) - res.Parts = append(res.Parts, other.Parts[1:]...) - } else { - res.Parts = append(res.Parts, other.Parts...) - } - return res, nil -} - -func (f Funding) Total() *MonetaryInt { - total := Zero - for _, part := range f.Parts { - total = total.Add(part.Amount) - } - return total -} - -func (f Funding) Reverse() Funding { - newParts := []FundingPart{} - for i := len(f.Parts) - 1; i >= 0; i-- { - newParts = append(newParts, f.Parts[i]) - } - newFunding := Funding{ - Asset: f.Asset, - Parts: newParts, - } - return newFunding -} diff --git a/components/ledger/internal/machine/funding_test.go b/components/ledger/internal/machine/funding_test.go deleted file mode 100644 index d5353e55a0..0000000000 --- a/components/ledger/internal/machine/funding_test.go +++ /dev/null @@ -1,165 +0,0 @@ -package machine - -import ( - "testing" -) - -func TestFundingTake(t *testing.T) { - f := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(70), - }, - { - Account: "bbb", - Amount: NewMonetaryInt(30), - }, - { - Account: "ccc", - Amount: NewMonetaryInt(50), - }, - }, - } - result, remainder, err := f.Take(NewMonetaryInt(80)) - if err != nil { - t.Fatal(err) - } - expectedResult := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(70), - }, - { - Account: "bbb", - Amount: NewMonetaryInt(10), - }, - }, - } - if !ValueEquals(result, expectedResult) { - t.Fatalf("unexpected result: %v", result) - } - expectedRemainder := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "bbb", - Amount: NewMonetaryInt(20), - }, - { - Account: "ccc", - Amount: NewMonetaryInt(50), - }, - }, - } - if !ValueEquals(remainder, expectedRemainder) { - t.Fatalf("unexpected remainder: %v", remainder) - } -} - -func TestFundingTakeMaxUnder(t *testing.T) { - f := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(30), - }, - }, - } - result, remainder := f.TakeMax(NewMonetaryInt(80)) - if !ValueEquals(result, Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(30), - }, - }, - }) { - t.Fatalf("unexpected result: %v", result) - } - if !ValueEquals(remainder, Funding{ - Asset: "COIN", - }) { - t.Fatalf("unexpected remainder: %v", remainder) - } -} - -func TestFundingTakeMaxAbove(t *testing.T) { - f := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(90), - }, - }, - } - result, remainder := f.TakeMax(NewMonetaryInt(80)) - if !ValueEquals(result, Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(80), - }, - }, - }) { - t.Fatalf("unexpected result: %v", result) - } - if !ValueEquals(remainder, Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(10), - }, - }, - }) { - t.Fatalf("unexpected remainder: %v", remainder) - } -} - -func TestFundingReversal(t *testing.T) { - f := Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "aaa", - Amount: NewMonetaryInt(10), - }, - { - Account: "bbb", - Amount: NewMonetaryInt(20), - }, - { - Account: "ccc", - Amount: NewMonetaryInt(30), - }, - }, - } - rev := f.Reverse() - if !ValueEquals(rev, Funding{ - Asset: "COIN", - Parts: []FundingPart{ - { - Account: "ccc", - Amount: NewMonetaryInt(30), - }, - { - Account: "bbb", - Amount: NewMonetaryInt(20), - }, - { - Account: "aaa", - Amount: NewMonetaryInt(10), - }, - }, - }) { - t.Fatalf("unexpected result: %v", rev) - } -} diff --git a/components/ledger/internal/machine/json.go b/components/ledger/internal/machine/json.go deleted file mode 100644 index 7f93874721..0000000000 --- a/components/ledger/internal/machine/json.go +++ /dev/null @@ -1,85 +0,0 @@ -package machine - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/pkg/errors" -) - -type ValueJSON struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} - -func NewValueFromString(typ Type, data string) (Value, error) { - var value Value - switch typ { - case TypeAccount: - if err := ValidateAccountAddress(AccountAddress(data)); err != nil { - return nil, errors.Wrapf(err, "value %s", data) - } - value = AccountAddress(data) - case TypeAsset: - if err := ValidateAsset(Asset(data)); err != nil { - return nil, errors.Wrapf(err, "value %s", data) - } - value = Asset(data) - case TypeNumber: - var number Number - if err := json.Unmarshal([]byte(data), &number); err != nil { - return nil, err - } - value = number - case TypeMonetary: - parts := strings.SplitN(data, " ", 2) - if len(parts) != 2 { - return nil, errors.New("monetary must have two parts") - } - mi, err := ParseMonetaryInt(parts[1]) - if err != nil { - return nil, err - } - mon := Monetary{ - Asset: Asset(parts[0]), - Amount: mi, - } - if err := ParseMonetary(mon); err != nil { - return nil, errors.Wrapf(err, "value %s", mon.String()) - } - value = mon - case TypePortion: - res, err := ParsePortionSpecific(data) - if err != nil { - return nil, err - } - value = *res - case TypeString: - value = String(data) - default: - return nil, fmt.Errorf("invalid type '%v'", typ) - } - - return value, nil -} - -func NewStringFromValue(value Value) (string, error) { - switch value.GetType() { - case TypeAccount: - return string(value.(AccountAddress)), nil - case TypeAsset: - return string(value.(Asset)), nil - case TypeString: - return string(value.(String)), nil - case TypeNumber: - return value.(*MonetaryInt).String(), nil - case TypeMonetary: - m := value.(Monetary) - return fmt.Sprintf("%s %s", m.Asset, m.Amount), nil - case TypePortion: - return value.(Portion).String(), nil - default: - return "", fmt.Errorf("invalid type '%v'", value.GetType()) - } -} diff --git a/components/ledger/internal/machine/json_test.go b/components/ledger/internal/machine/json_test.go deleted file mode 100644 index 29b7d881ea..0000000000 --- a/components/ledger/internal/machine/json_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package machine - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAccountTypedJSON(t *testing.T) { - j := "users:001" - value, err := NewValueFromString(TypeAccount, j) - require.NoError(t, err) - - if !ValueEquals(value, AccountAddress("users:001")) { - t.Fatalf("unexpected value: %v", value) - } -} - -func TestAssetTypedJSON(t *testing.T) { - j := "EUR/2" - value, err := NewValueFromString(TypeAsset, j) - require.NoError(t, err) - - if !ValueEquals(value, Asset("EUR/2")) { - t.Fatalf("unexpected value: %v", value) - } -} - -func TestNumberTypedJSON(t *testing.T) { - j := "89849865111111111111111111111111111555555555555555555555555555555555555555555555555999999999999999999999" - value, err := NewValueFromString(TypeNumber, j) - require.NoError(t, err) - - num, err := ParseNumber("89849865111111111111111111111111111555555555555555555555555555555555555555555555555999999999999999999999") - require.NoError(t, err) - - if !ValueEquals(value, num) { - t.Fatalf("unexpected value: %v", value) - } -} - -func TestMonetaryTypedJSON(t *testing.T) { - j := "EUR/2 123456" - value, err := NewValueFromString(TypeMonetary, j) - require.NoError(t, err) - - if !ValueEquals(value, Monetary{ - Asset: "EUR/2", - Amount: NewMonetaryInt(123456), - }) { - t.Fatalf("unexpected value: %v", value) - } -} - -func TestPortionTypedJSON(t *testing.T) { - j := "90%" - value, err := NewValueFromString(TypePortion, j) - require.NoError(t, err) - - portion, err := NewPortionSpecific(*big.NewRat(90, 100)) - require.NoError(t, err) - - if !ValueEquals(value, *portion) { - t.Fatalf("unexpected value: %v", value) - } -} - -func TestMarshalJSON(t *testing.T) { - t.Run("account", func(t *testing.T) { - by, err := json.Marshal(AccountAddress("platform")) - require.NoError(t, err) - assert.Equal(t, `"platform"`, string(by)) - }) - t.Run("asset", func(t *testing.T) { - by, err := json.Marshal(Asset("COIN")) - require.NoError(t, err) - assert.Equal(t, `"COIN"`, string(by)) - }) - t.Run("number", func(t *testing.T) { - by, err := json.Marshal( - Number(big.NewInt(42))) - require.NoError(t, err) - assert.Equal(t, `42`, string(by)) - }) - t.Run("string", func(t *testing.T) { - by, err := json.Marshal(String("test")) - require.NoError(t, err) - assert.Equal(t, `"test"`, string(by)) - }) - t.Run("monetary", func(t *testing.T) { - by, err := json.Marshal( - Monetary{ - Asset: "COIN", - Amount: NewMonetaryInt(42), - }) - require.NoError(t, err) - assert.Equal(t, `{"asset":"COIN","amount":42}`, string(by)) - }) - t.Run("portion", func(t *testing.T) { - by, err := json.Marshal( - Portion{ - Remaining: true, - Specific: big.NewRat(10, 12), - }) - require.NoError(t, err) - assert.Equal(t, `{"remaining":true,"specific":"5/6"}`, string(by)) - }) -} diff --git a/components/ledger/internal/machine/monetary.go b/components/ledger/internal/machine/monetary.go deleted file mode 100644 index 489f75e0a0..0000000000 --- a/components/ledger/internal/machine/monetary.go +++ /dev/null @@ -1,160 +0,0 @@ -package machine - -import ( - "fmt" - "math/big" - - "github.com/pkg/errors" -) - -type Monetary struct { - Asset Asset `json:"asset"` - Amount *MonetaryInt `json:"amount"` -} - -func (Monetary) GetType() Type { return TypeMonetary } - -func (m Monetary) String() string { - if m.Amount == nil { - return fmt.Sprintf("[%s nil]", m.Asset) - } - amt := *m.Amount - return fmt.Sprintf("[%v %s]", m.Asset, amt.String()) -} - -func (m Monetary) GetAsset() Asset { return m.Asset } - -var Zero = NewMonetaryInt(0) - -func ParseMonetary(mon Monetary) error { - if err := ValidateAsset(mon.Asset); err != nil { - return errors.Wrapf(err, "asset '%s'", mon.Asset) - } - if mon.Amount == nil { - return errors.Errorf("nil amount") - } - if mon.Amount.Ltz() { - return errors.Errorf("negative amount") - } - return nil -} - -type MonetaryInt big.Int - -func (MonetaryInt) GetType() Type { return TypeNumber } - -func (a *MonetaryInt) Add(b *MonetaryInt) *MonetaryInt { - if a == nil { - a = (*MonetaryInt)(&big.Int{}) - } - - if b == nil { - b = (*MonetaryInt)(&big.Int{}) - } - - return (*MonetaryInt)((&big.Int{}).Add((*big.Int)(a), (*big.Int)(b))) -} - -func (a *MonetaryInt) Sub(b *MonetaryInt) *MonetaryInt { - if a == nil { - a = (*MonetaryInt)(&big.Int{}) - } - - if b == nil { - b = (*MonetaryInt)(&big.Int{}) - } - - return (*MonetaryInt)((&big.Int{}).Sub((*big.Int)(a), (*big.Int)(b))) -} - -func (a *MonetaryInt) Neg() *MonetaryInt { - return (*MonetaryInt)((&big.Int{}).Neg((*big.Int)(a))) -} - -func (a *MonetaryInt) OrZero() *MonetaryInt { - if a == nil { - return NewMonetaryInt(0) - } - - return a -} - -func (a *MonetaryInt) Lte(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) <= 0 -} - -func (a *MonetaryInt) Gte(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) >= 0 -} - -func (a *MonetaryInt) Lt(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) < 0 -} - -func (a *MonetaryInt) Ltz() bool { - return (*big.Int)(a).Cmp(new(big.Int)) < 0 -} - -func (a *MonetaryInt) Gt(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) > 0 -} - -func (a *MonetaryInt) Eq(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) == 0 -} - -func (a *MonetaryInt) Equal(b *MonetaryInt) bool { - return (*big.Int)(a).Cmp((*big.Int)(b)) == 0 -} - -func (a *MonetaryInt) Cmp(b *MonetaryInt) int { - return (*big.Int)(a).Cmp((*big.Int)(b)) -} - -func (a *MonetaryInt) Uint64() uint64 { - return (*big.Int)(a).Uint64() -} - -func (a *MonetaryInt) String() string { - if a == nil { - return "0" - } - - return (*big.Int)(a).String() -} - -func (a *MonetaryInt) UnmarshalJSON(b []byte) error { - return (*big.Int)(a).UnmarshalJSON(b) -} - -func (a *MonetaryInt) MarshalJSON() ([]byte, error) { - if a == nil { - return []byte("0"), nil - } - return (*big.Int)(a).MarshalJSON() -} - -func (a *MonetaryInt) MarshalText() ([]byte, error) { - return (*big.Int)(a).MarshalText() -} - -func (a *MonetaryInt) UnmarshalText(b []byte) error { - return (*big.Int)(a).UnmarshalText(b) -} - -func NewMonetaryInt(i int64) *MonetaryInt { - return (*MonetaryInt)(big.NewInt(i)) -} - -func NewMonetaryIntFromBigInt(v *big.Int) *MonetaryInt { - return (*MonetaryInt)(v) -} - -func ParseMonetaryInt(s string) (*MonetaryInt, error) { - i, ok := (&big.Int{}).SetString(s, 10) - if !ok { - return nil, errors.New("invalid monetary int") - } - - return (*MonetaryInt)(i), nil -} diff --git a/components/ledger/internal/machine/number.go b/components/ledger/internal/machine/number.go deleted file mode 100644 index e8be571b0b..0000000000 --- a/components/ledger/internal/machine/number.go +++ /dev/null @@ -1,11 +0,0 @@ -package machine - -type Number = *MonetaryInt - -func NewNumber(i int64) Number { - return NewMonetaryInt(i) -} - -func ParseNumber(s string) (Number, error) { - return ParseMonetaryInt(s) -} diff --git a/components/ledger/internal/machine/portion.go b/components/ledger/internal/machine/portion.go deleted file mode 100644 index f6364bb521..0000000000 --- a/components/ledger/internal/machine/portion.go +++ /dev/null @@ -1,94 +0,0 @@ -package machine - -import ( - "errors" - "fmt" - "math/big" - "regexp" -) - -type Portion struct { - Remaining bool `json:"remaining"` - Specific *big.Rat `json:"specific"` -} - -func (Portion) GetType() Type { return TypePortion } - -func NewPortionRemaining() Portion { - return Portion{ - Remaining: true, - Specific: nil, - } -} - -func NewPortionSpecific(r big.Rat) (*Portion, error) { - if r.Cmp(big.NewRat(0, 1)) == -1 || r.Cmp(big.NewRat(1, 1)) == 1 { - return nil, errors.New("portion must be between 0% and 100% inclusive") - } - return &Portion{ - Remaining: false, - Specific: &r, - }, nil -} - -func ValidatePortionSpecific(p Portion) error { - if p.Remaining { - return errors.New("remaining should not be true for a specific portion") - } - if p.Specific == nil { - return errors.New("specific portion should not be nil") - } - if p.Specific.Cmp(big.NewRat(0, 1)) == -1 || p.Specific.Cmp(big.NewRat(1, 1)) == 1 { - return errors.New("specific portion must be between 0% and 100% inclusive") - } - return nil -} - -func (lhs Portion) Equals(rhs Portion) bool { - if lhs.Remaining != rhs.Remaining { - return false - } - if !lhs.Remaining && lhs.Specific.Cmp(rhs.Specific) != 0 { - return false - } - return true -} - -func ParsePortionSpecific(input string) (*Portion, error) { - var res *big.Rat - var ok bool - - re := regexp.MustCompile(`^([0-9]+)(?:[.]([0-9]+))?[%]$`) - percentMatch := re.FindStringSubmatch(input) - if len(percentMatch) != 0 { - integral := percentMatch[1] - fractional := percentMatch[2] - res, ok = new(big.Rat).SetString(integral + "." + fractional) - if !ok { - return nil, errors.New("invalid percent format") - } - res.Mul(res, big.NewRat(1, 100)) - } else { - re = regexp.MustCompile(`^([0-9]+)\s?[/]\s?([0-9]+)$`) - fractionMatch := re.FindStringSubmatch(input) - if len(fractionMatch) != 0 { - numerator := fractionMatch[1] - denominator := fractionMatch[2] - res, ok = new(big.Rat).SetString(numerator + "/" + denominator) - if !ok { - return nil, errors.New("invalid fractional format") - } - } - } - if res == nil { - return nil, errors.New("invalid format") - } - return NewPortionSpecific(*res) -} - -func (p Portion) String() string { - if p.Remaining { - return "remaining" - } - return fmt.Sprintf("%v", p.Specific) -} diff --git a/components/ledger/internal/machine/portion_test.go b/components/ledger/internal/machine/portion_test.go deleted file mode 100644 index fe05e931a5..0000000000 --- a/components/ledger/internal/machine/portion_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package machine - -import ( - "math/big" - "strings" - "testing" -) - -func TestBetween0And1Inclusive(t *testing.T) { - tests := []struct { - in string - want *big.Rat - wantErr bool - }{ - { - in: "0%", - want: big.NewRat(0, 1), - }, - { - in: "0.0%", - want: big.NewRat(0, 1), - }, - { - in: "0/1", - want: big.NewRat(0, 1), - }, - { - in: "0/25", - want: big.NewRat(0, 1), - }, - { - in: "0/100", - want: big.NewRat(0, 1), - }, - { - in: "1%", - want: big.NewRat(1, 100), - }, - { - in: "1/100", - want: big.NewRat(1, 100), - }, - { - in: "10/1000", - want: big.NewRat(1, 100), - }, - { - in: "50/100", - want: big.NewRat(50, 100), - }, - { - in: "50%", - want: big.NewRat(50, 100), - }, - { - in: "50.0%", - want: big.NewRat(50, 100), - }, - { - in: "1/1", - want: big.NewRat(1, 1), - }, - { - in: "100/100", - want: big.NewRat(1, 1), - }, - { - in: "100.0%", - want: big.NewRat(1, 1), - }, - { - in: "100%", - want: big.NewRat(1, 1), - }, - // Now for the failures. We don't check negative numbers in this test because - // those are a parsing failure, not a range failure. - { - in: "100.1%", - wantErr: true, - }, - { - in: "101%", - wantErr: true, - }, - { - in: "101/100", - wantErr: true, - }, - { - in: "2/1", - wantErr: true, - }, - { - in: "3/2", - wantErr: true, - }, - } - - for _, test := range tests { - t.Run(test.in, func(t *testing.T) { - got, err := ParsePortionSpecific(test.in) - if test.wantErr { - if err == nil { - t.Fatal("should have errored") - } - if !strings.Contains(err.Error(), "between") { - t.Fatal("wrong error") - } - return - } - if err != nil { - t.Fatalf("ParsePortionSpecific(%q): %v", test.in, err) - } - if test.want.Cmp(got.Specific) != 0 { - t.Fatalf("ParsePortionSpecific(%q) = %q, want %q", test.in, got, test.want) - } - }) - } -} - -func TestInvalidFormat(t *testing.T) { - _, err := ParsePortionSpecific("this is not a portion") - if err == nil { - t.Fatal("should have errored") - } - if !strings.Contains(err.Error(), "format") { - t.Fatal("wrong error") - } -} diff --git a/components/ledger/internal/machine/script/NumScript.g4 b/components/ledger/internal/machine/script/NumScript.g4 deleted file mode 100644 index 07583c20ae..0000000000 --- a/components/ledger/internal/machine/script/NumScript.g4 +++ /dev/null @@ -1,177 +0,0 @@ -grammar NumScript; - -NEWLINE: [\r\n]+; -WHITESPACE: [ \t]+ -> skip; - -MULTILINE_COMMENT: '/*' (MULTILINE_COMMENT|.)*? '*/' -> skip; -LINE_COMMENT: '//' .*? NEWLINE -> skip; -VARS: 'vars'; -META: 'meta'; -SET_TX_META: 'set_tx_meta'; -SET_ACCOUNT_META: 'set_account_meta'; -PRINT: 'print'; -FAIL: 'fail'; -SEND: 'send'; -SOURCE: 'source'; -FROM: 'from'; -MAX: 'max'; -DESTINATION: 'destination'; -TO: 'to'; -ALLOCATE: 'allocate'; -OP_ADD: '+'; -OP_SUB: '-'; -LPAREN: '('; -RPAREN: ')'; -LBRACK: '['; -RBRACK: ']'; -LBRACE: '{'; -RBRACE: '}'; -EQ: '='; -TY_ACCOUNT: 'account'; -TY_ASSET: 'asset'; -TY_NUMBER: 'number'; -TY_MONETARY: 'monetary'; -TY_PORTION: 'portion'; -TY_STRING: 'string'; -STRING: '"' ('\\"' | ~[\r\n"])* '"'; -PORTION: - ( [0-9]+ [ ]? '/' [ ]? [0-9]+ - | [0-9]+ ('.' [0-9]+)? '%' - ); -REMAINING: 'remaining'; -KEPT: 'kept'; -BALANCE: 'balance'; -SAVE: 'save'; -NUMBER: [0-9]+; -PERCENT: '%'; -VARIABLE_NAME: '$' [a-z_]+ [a-z0-9_]*; -ACCOUNT: '@' [a-zA-Z0-9_-]+ (':' [a-zA-Z0-9_-]+)*; - - - -ASSET: [A-Z/0-9]+; - -monetary: LBRACK asset=expression amt=NUMBER RBRACK; - -monetaryAll: LBRACK asset=expression '*' RBRACK; - -literal - : ACCOUNT # LitAccount - | ASSET # LitAsset - | NUMBER # LitNumber - | STRING # LitString - | PORTION # LitPortion - | monetary # LitMonetary - ; - -variable: VARIABLE_NAME; - -expression - : lhs=expression op=(OP_ADD|OP_SUB) rhs=expression # ExprAddSub - | lit=literal # ExprLiteral - | var_=variable # ExprVariable - ; - -allotmentPortion - : PORTION # AllotmentPortionConst - | por=variable # AllotmentPortionVar - | REMAINING # AllotmentPortionRemaining - ; - -destinationInOrder - : LBRACE NEWLINE - (MAX amounts+=expression dests+=keptOrDestination NEWLINE)+ - REMAINING remainingDest=keptOrDestination NEWLINE - RBRACE - ; - -destinationAllotment - : LBRACE NEWLINE - (portions+=allotmentPortion dests+=keptOrDestination NEWLINE)+ - RBRACE - ; - -keptOrDestination - : TO destination # IsDestination - | KEPT # IsKept - ; - -destination - : expression # DestAccount - | destinationInOrder # DestInOrder - | destinationAllotment # DestAllotment - ; - -sourceAccountOverdraft - : 'allowing overdraft up to' specific=expression # SrcAccountOverdraftSpecific - | 'allowing unbounded overdraft' # SrcAccountOverdraftUnbounded - ; - -sourceAccount: account=expression (overdraft=sourceAccountOverdraft)?; - -sourceInOrder - : LBRACE NEWLINE - (sources+=source NEWLINE)+ - RBRACE - ; - -sourceMaxed: MAX max=expression FROM src=source; - -source - : sourceAccount # SrcAccount - | sourceMaxed # SrcMaxed - | sourceInOrder # SrcInOrder - ; - -sourceAllotment - : LBRACE NEWLINE - (portions+=allotmentPortion FROM sources+=source NEWLINE)+ - RBRACE - ; - -valueAwareSource - : source # Src - | sourceAllotment # SrcAllotment - ; - -statement - : PRINT expr=expression # Print - | SAVE (mon=expression | monAll=monetaryAll) FROM acc=expression # SaveFromAccount - | SET_TX_META '(' key=STRING ',' value=expression ')' # SetTxMeta - | SET_ACCOUNT_META '(' acc=expression ',' key=STRING ',' value=expression ')' # SetAccountMeta - | FAIL # Fail - | SEND (mon=expression | monAll=monetaryAll) LPAREN NEWLINE - ( SOURCE '=' src=valueAwareSource NEWLINE DESTINATION '=' dest=destination - | DESTINATION '=' dest=destination NEWLINE SOURCE '=' src=valueAwareSource) NEWLINE RPAREN # Send - ; - -type_ - : TY_ACCOUNT - | TY_ASSET - | TY_NUMBER - | TY_STRING - | TY_MONETARY - | TY_PORTION - ; - -origin - : META '(' account=expression ',' key=STRING ')' # OriginAccountMeta - | BALANCE '(' account=expression ',' asset=expression ')' # OriginAccountBalance - ; - -varDecl: ty=type_ name=variable (EQ orig=origin)?; - -varListDecl - : VARS LBRACE NEWLINE - (v+=varDecl NEWLINE+)+ - RBRACE NEWLINE - ; - -script: - NEWLINE* - vars=varListDecl? - stmts+=statement - (NEWLINE stmts+=statement)* - NEWLINE* - EOF - ; diff --git a/components/ledger/internal/machine/script/compiler/allotment.go b/components/ledger/internal/machine/script/compiler/allotment.go deleted file mode 100644 index 40bb0b6d6c..0000000000 --- a/components/ledger/internal/machine/script/compiler/allotment.go +++ /dev/null @@ -1,85 +0,0 @@ -package compiler - -import ( - "errors" - "fmt" - "math/big" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/antlr/antlr4/runtime/Go/antlr" - "github.com/formancehq/ledger/internal/machine/script/parser" - program2 "github.com/formancehq/ledger/internal/machine/vm/program" -) - -func (p *parseVisitor) VisitAllotment(c antlr.ParserRuleContext, portions []parser.IAllotmentPortionContext) *CompileError { - total := big.NewRat(0, 1) - hasVariable := false - hasRemaining := false - for i := len(portions) - 1; i >= 0; i-- { - c := portions[i] - switch c := c.(type) { - case *parser.AllotmentPortionConstContext: - portion, err := machine.ParsePortionSpecific(c.GetText()) - if err != nil { - return LogicError(c, err) - } - rat := *portion.Specific - total.Add(&rat, total) - addr, err := p.AllocateResource(program2.Constant{Inner: *portion}) - if err != nil { - return LogicError(c, err) - } - p.PushAddress(*addr) - case *parser.AllotmentPortionVarContext: - ty, _, err := p.VisitVariable(c.GetPor(), true) - if err != nil { - return err - } - if ty != machine.TypePortion { - return LogicError(c, - fmt.Errorf("wrong type: expected type portion for variable: %v", ty), - ) - } - hasVariable = true - case *parser.AllotmentPortionRemainingContext: - if hasRemaining { - return LogicError(c, - errors.New("two uses of `remaining` in the same allocation"), - ) - } - addr, err := p.AllocateResource(program2.Constant{Inner: machine.NewPortionRemaining()}) - if err != nil { - return LogicError(c, err) - } - p.PushAddress(*addr) - hasRemaining = true - } - } - if total.Cmp(big.NewRat(1, 1)) == 1 { - return LogicError(c, - errors.New("the sum of known portions is greater than 100%"), - ) - } - if total.Cmp(big.NewRat(1, 1)) == -1 && !hasRemaining { - return LogicError(c, - errors.New("the sum of portions might be less than 100%"), - ) - } - if total.Cmp(big.NewRat(1, 1)) == 0 && hasVariable { - return LogicError(c, - errors.New("the sum of portions might be greater than 100%"), - ) - } - if total.Cmp(big.NewRat(1, 1)) == 0 && hasRemaining { - return LogicError(c, - errors.New("known portions are already equal to 100%"), - ) - } - err := p.PushInteger(machine.NewNumber(int64(len(portions)))) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program2.OP_MAKE_ALLOTMENT) - return nil -} diff --git a/components/ledger/internal/machine/script/compiler/compiler.go b/components/ledger/internal/machine/script/compiler/compiler.go deleted file mode 100644 index 7507963233..0000000000 --- a/components/ledger/internal/machine/script/compiler/compiler.go +++ /dev/null @@ -1,737 +0,0 @@ -package compiler - -import ( - "fmt" - "sort" - "strconv" - "strings" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/antlr/antlr4/runtime/Go/antlr" - parser "github.com/formancehq/ledger/internal/machine/script/parser" - program "github.com/formancehq/ledger/internal/machine/vm/program" - "github.com/pkg/errors" -) - -type parseVisitor struct { - errListener *ErrorListener - instructions []byte - // resources must not exceed 65536 elements - resources []program.Resource - // sources store all source accounts - // a source can be also a destination of another posting - sources map[machine.Address]struct{} - // varIdx maps name to resource index - varIdx map[string]machine.Address - // needBalances store for each account, the set of assets needed - neededBalances map[machine.Address]map[machine.Address]struct{} - - // The sources accounts that aren't unbounded - // that is, @world or sources that appear within a - // '.. allowing unboundeed overdraft' clause - writeLockAccounts map[machine.Address]struct{} - - // all the accounts that appear in either the destination - // or in the balance() function - readLockAccounts map[machine.Address]struct{} -} - -// Allocates constants if it hasn't already been, -// and returns its resource address. -func (p *parseVisitor) findConstant(constant program.Constant) (*machine.Address, bool) { - for i := 0; i < len(p.resources); i++ { - if c, ok := p.resources[i].(program.Constant); ok { - if machine.ValueEquals(c.Inner, constant.Inner) { - addr := machine.Address(i) - return &addr, true - } - } - } - return nil, false -} - -func (p *parseVisitor) AllocateResource(res program.Resource) (*machine.Address, error) { - if c, ok := res.(program.Constant); ok { - idx, ok := p.findConstant(c) - if ok { - return idx, nil - } - } - if len(p.resources) >= 65536 { - return nil, errors.New("number of unique constants exceeded 65536") - } - p.resources = append(p.resources, res) - addr := machine.NewAddress(uint16(len(p.resources) - 1)) - return &addr, nil -} - -func (p *parseVisitor) isWorld(addr machine.Address) bool { - idx := int(addr) - if idx < len(p.resources) { - if c, ok := p.resources[idx].(program.Constant); ok { - if acc, ok := c.Inner.(machine.AccountAddress); ok { - if string(acc) == "world" { - return true - } - } - } - } - return false -} - -func (p *parseVisitor) VisitVariable(c parser.IVariableContext, push bool) (machine.Type, *machine.Address, *CompileError) { - name := c.GetText()[1:] // strip '$' prefix - if idx, ok := p.varIdx[name]; ok { - res := p.resources[idx] - if push { - p.PushAddress(idx) - } - return res.GetType(), &idx, nil - } else { - return 0, nil, LogicError(c, errors.New("variable not declared")) - } -} - -func (p *parseVisitor) VisitExpr(c parser.IExpressionContext, push bool) (machine.Type, *machine.Address, *CompileError) { - switch c := c.(type) { - case *parser.ExprAddSubContext: - lhsType, lhsAddr, err := p.VisitExpr(c.GetLhs(), push) - if err != nil { - return 0, nil, err - } - switch lhsType { - case machine.TypeNumber: - rhsType, _, err := p.VisitExpr(c.GetRhs(), push) - if err != nil { - return 0, nil, err - } - if rhsType != machine.TypeNumber { - return 0, nil, LogicError(c, fmt.Errorf( - "tried to do an arithmetic operation with incompatible left and right-hand side operand types: %s and %s", - lhsType, rhsType)) - } - if push { - switch c.GetOp().GetTokenType() { - case parser.NumScriptLexerOP_ADD: - p.AppendInstruction(program.OP_IADD) - case parser.NumScriptLexerOP_SUB: - p.AppendInstruction(program.OP_ISUB) - } - } - return machine.TypeNumber, nil, nil - case machine.TypeMonetary: - rhsType, _, err := p.VisitExpr(c.GetRhs(), push) - if err != nil { - return 0, nil, err - } - if rhsType != machine.TypeMonetary { - return 0, nil, LogicError(c, fmt.Errorf( - "tried to do an arithmetic operation with incompatible left and right-hand side operand types: %s and %s", - lhsType, rhsType)) - } - if push { - switch c.GetOp().GetTokenType() { - case parser.NumScriptLexerOP_ADD: - p.AppendInstruction(program.OP_MONETARY_ADD) - case parser.NumScriptLexerOP_SUB: - p.AppendInstruction(program.OP_MONETARY_SUB) - } - } - return machine.TypeMonetary, lhsAddr, nil - default: - return 0, nil, LogicError(c, fmt.Errorf( - "tried to do an arithmetic operation with unsupported left-hand side operand type: %s", - lhsType)) - } - case *parser.ExprLiteralContext: - return p.VisitLit(c.GetLit(), push) - case *parser.ExprVariableContext: - return p.VisitVariable(c.GetVar_(), push) - default: - return 0, nil, InternalError(c) - } -} - -func (p *parseVisitor) VisitLit(c parser.ILiteralContext, push bool) (machine.Type, *machine.Address, *CompileError) { - switch c := c.(type) { - case *parser.LitAccountContext: - account := machine.AccountAddress(c.GetText()[1:]) - addr, err := p.AllocateResource(program.Constant{Inner: account}) - if err != nil { - return 0, nil, LogicError(c, err) - } - if push { - p.PushAddress(*addr) - } - return machine.TypeAccount, addr, nil - case *parser.LitAssetContext: - asset := machine.Asset(c.GetText()) - addr, err := p.AllocateResource(program.Constant{Inner: asset}) - if err != nil { - return 0, nil, LogicError(c, err) - } - if push { - p.PushAddress(*addr) - } - return machine.TypeAsset, addr, nil - case *parser.LitNumberContext: - number, err := machine.ParseNumber(c.GetText()) - if err != nil { - return 0, nil, LogicError(c, err) - } - addr, err := p.AllocateResource(program.Constant{Inner: number}) - if err != nil { - return 0, nil, LogicError(c, err) - } - if push { - p.PushAddress(*addr) - } - return machine.TypeNumber, addr, nil - case *parser.LitStringContext: - unquoted, err := strconv.Unquote(c.GetText()) - if err != nil { - return 0, nil, LogicError(c, err) - } - - addr, err := p.AllocateResource(program.Constant{ - Inner: machine.String(unquoted), - }) - if err != nil { - return 0, nil, LogicError(c, err) - } - if push { - p.PushAddress(*addr) - } - return machine.TypeString, addr, nil - case *parser.LitPortionContext: - portion, err := machine.ParsePortionSpecific(c.GetText()) - if err != nil { - return 0, nil, LogicError(c, err) - } - addr, err := p.AllocateResource(program.Constant{Inner: *portion}) - if err != nil { - return 0, nil, LogicError(c, err) - } - if push { - p.PushAddress(*addr) - } - return machine.TypePortion, addr, nil - case *parser.LitMonetaryContext: - typ, assetAddr, compErr := p.VisitExpr(c.Monetary().GetAsset(), false) - if compErr != nil { - return 0, nil, compErr - } - if typ != machine.TypeAsset { - return 0, nil, LogicError(c, fmt.Errorf( - "the expression in monetary literal should be of type '%s' instead of '%s'", - machine.TypeAsset, typ)) - } - - amt, err := machine.ParseMonetaryInt(c.Monetary().GetAmt().GetText()) - if err != nil { - return 0, nil, LogicError(c, err) - } - - var ( - monAddr *machine.Address - alreadyAllocated bool - ) - for i, r := range p.resources { - switch v := r.(type) { - case program.Monetary: - if v.Asset == *assetAddr && v.Amount.Equal(amt) { - alreadyAllocated = true - tmp := machine.Address(uint16(i)) - monAddr = &tmp - break - } - } - } - if !alreadyAllocated { - monAddr, err = p.AllocateResource(program.Monetary{ - Asset: *assetAddr, - Amount: amt, - }) - if err != nil { - return 0, nil, LogicError(c, err) - } - } - if push { - p.PushAddress(*monAddr) - } - return machine.TypeMonetary, monAddr, nil - default: - return 0, nil, InternalError(c) - } -} - -func (p *parseVisitor) VisitMonetaryAll(c *parser.SendContext, monAll parser.IMonetaryAllContext) *CompileError { - assetType, assetAddr, compErr := p.VisitExpr(monAll.GetAsset(), false) - if compErr != nil { - return compErr - } - if assetType != machine.TypeAsset { - return LogicError(c, fmt.Errorf( - "send monetary all: the expression should be of type 'asset' instead of '%s'", assetType)) - } - - switch c := c.GetSrc().(type) { - case *parser.SrcContext: - accounts, _, _, compErr := p.VisitSource(c.Source(), func() { - p.PushAddress(*assetAddr) - }, true) - if compErr != nil { - return compErr - } - p.setNeededBalances(accounts, assetAddr) - - case *parser.SrcAllotmentContext: - return LogicError(c, errors.New("cannot take all balance of an allotment source")) - } - return nil -} - -func (p *parseVisitor) VisitMonetary(c *parser.SendContext, mon parser.IExpressionContext) *CompileError { - monType, monAddr, compErr := p.VisitExpr(mon, false) - if compErr != nil { - return compErr - } - if monType != machine.TypeMonetary { - return LogicError(c, fmt.Errorf( - "send monetary: the expression should be of type 'monetary' instead of '%s'", monType)) - } - - switch c := c.GetSrc().(type) { - case *parser.SrcContext: - accounts, _, fallback, compErr := p.VisitSource(c.Source(), func() { - p.PushAddress(*monAddr) - p.AppendInstruction(program.OP_ASSET) - }, false) - if compErr != nil { - return compErr - } - p.setNeededBalances(accounts, monAddr) - - if _, _, err := p.VisitExpr(mon, true); err != nil { - return err - } - - if err := p.TakeFromSource(fallback); err != nil { - return LogicError(c, err) - } - case *parser.SrcAllotmentContext: - if _, _, err := p.VisitExpr(mon, true); err != nil { - return err - } - err := p.VisitAllotment(c.SourceAllotment(), c.SourceAllotment().GetPortions()) - if err != nil { - return err - } - - p.AppendInstruction(program.OP_ALLOC) - - sources := c.SourceAllotment().GetSources() - n := len(sources) - for i := 0; i < n; i++ { - accounts, _, fallback, compErr := p.VisitSource(sources[i], func() { - p.PushAddress(*monAddr) - p.AppendInstruction(program.OP_ASSET) - }, false) - if compErr != nil { - return compErr - } - p.setNeededBalances(accounts, monAddr) - - if err := p.Bump(int64(i + 1)); err != nil { - return LogicError(c, err) - } - - if err := p.TakeFromSource(fallback); err != nil { - return LogicError(c, err) - } - } - - if err := p.PushInteger(machine.NewNumber(int64(n))); err != nil { - return LogicError(c, err) - } - - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } - return nil -} - -func (p *parseVisitor) setNeededBalances(accounts map[machine.Address]struct{}, addr *machine.Address) { - for acc := range accounts { - if b, ok := p.neededBalances[acc]; ok { - b[*addr] = struct{}{} - } else { - p.neededBalances[acc] = map[machine.Address]struct{}{ - *addr: {}, - } - } - } -} - -func (p *parseVisitor) VisitSend(c *parser.SendContext) *CompileError { - if monAll := c.GetMonAll(); monAll != nil { - if err := p.VisitMonetaryAll(c, monAll); err != nil { - return err - } - } else if mon := c.GetMon(); mon != nil { - if err := p.VisitMonetary(c, mon); err != nil { - return err - } - } - - if err := p.VisitDestination(c.GetDest()); err != nil { - return err - } - - return nil -} - -func (p *parseVisitor) VisitSetTxMeta(ctx *parser.SetTxMetaContext) *CompileError { - _, _, compErr := p.VisitExpr(ctx.GetValue(), true) - if compErr != nil { - return compErr - } - - unquoted, err := strconv.Unquote(ctx.GetKey().GetText()) - if err != nil { - return LogicError(ctx, err) - } - - keyAddr, err := p.AllocateResource(program.Constant{ - Inner: machine.String(unquoted), - }) - if err != nil { - return LogicError(ctx, err) - } - p.PushAddress(*keyAddr) - - p.AppendInstruction(program.OP_TX_META) - - return nil -} - -func (p *parseVisitor) VisitSetAccountMeta(ctx *parser.SetAccountMetaContext) *CompileError { - _, _, compErr := p.VisitExpr(ctx.GetValue(), true) - if compErr != nil { - return compErr - } - - unquoted, err := strconv.Unquote(ctx.GetKey().GetText()) - if err != nil { - return LogicError(ctx, err) - } - - keyAddr, err := p.AllocateResource(program.Constant{ - Inner: machine.String(unquoted), - }) - if err != nil { - return LogicError(ctx, err) - } - p.PushAddress(*keyAddr) - - ty, accAddr, compErr := p.VisitExpr(ctx.GetAcc(), false) - if compErr != nil { - return compErr - } - if ty != machine.TypeAccount { - return LogicError(ctx, fmt.Errorf( - "set_account_meta: expression is of type %s, and should be of type account", ty)) - } - p.PushAddress(*accAddr) - - p.AppendInstruction(program.OP_ACCOUNT_META) - - return nil -} - -func (p *parseVisitor) VisitSaveFromAccount(c *parser.SaveFromAccountContext) *CompileError { - var ( - typ machine.Type - addr *machine.Address - compErr *CompileError - ) - if monAll := c.GetMonAll(); monAll != nil { - typ, addr, compErr = p.VisitExpr(monAll.GetAsset(), false) - if compErr != nil { - return compErr - } - if typ != machine.TypeAsset { - return LogicError(c, fmt.Errorf( - "save monetary all from account: the first expression should be of type 'asset' instead of '%s'", typ)) - } - } else if mon := c.GetMon(); mon != nil { - typ, addr, compErr = p.VisitExpr(mon, false) - if compErr != nil { - return compErr - } - if typ != machine.TypeMonetary { - return LogicError(c, fmt.Errorf( - "save monetary from account: the first expression should be of type 'monetary' instead of '%s'", typ)) - } - } - p.PushAddress(*addr) - - typ, addr, compErr = p.VisitExpr(c.GetAcc(), false) - if compErr != nil { - return compErr - } - if typ != machine.TypeAccount { - return LogicError(c, fmt.Errorf( - "save monetary from account: the second expression should be of type 'account' instead of '%s'", typ)) - } - p.PushAddress(*addr) - - p.AppendInstruction(program.OP_SAVE) - - return nil -} - -func (p *parseVisitor) VisitPrint(ctx *parser.PrintContext) *CompileError { - _, _, err := p.VisitExpr(ctx.GetExpr(), true) - if err != nil { - return err - } - - p.AppendInstruction(program.OP_PRINT) - - return nil -} - -func (p *parseVisitor) VisitVars(c *parser.VarListDeclContext) *CompileError { - if len(c.GetV()) > 32768 { - return LogicError(c, fmt.Errorf("number of variables exceeded %v", 32768)) - } - - for _, v := range c.GetV() { - name := v.GetName().GetText()[1:] - if _, ok := p.varIdx[name]; ok { - return LogicError(c, fmt.Errorf("duplicate variable $%s", name)) - } - var ty machine.Type - switch v.GetTy().GetText() { - case "account": - ty = machine.TypeAccount - case "asset": - ty = machine.TypeAsset - case "number": - ty = machine.TypeNumber - case "string": - ty = machine.TypeString - case "monetary": - ty = machine.TypeMonetary - case "portion": - ty = machine.TypePortion - default: - return InternalError(c) - } - - var addr *machine.Address - var err error - if v.GetOrig() == nil { - addr, err = p.AllocateResource(program.Variable{Typ: ty, Name: name}) - if err != nil { - return &CompileError{ - Msg: errors.Wrap(err, - "allocating variable resource").Error(), - } - } - p.varIdx[name] = *addr - continue - } - - switch c := v.GetOrig().(type) { - case *parser.OriginAccountMetaContext: - srcTy, src, compErr := p.VisitExpr(c.GetAccount(), false) - if compErr != nil { - return compErr - } - if srcTy != machine.TypeAccount { - return LogicError(c, fmt.Errorf( - "variable $%s: type should be 'account' to pull account metadata", name)) - } - key := strings.Trim(c.GetKey().GetText(), `"`) - addr, err = p.AllocateResource(program.VariableAccountMetadata{ - Typ: ty, - Name: name, - Account: *src, - Key: key, - }) - case *parser.OriginAccountBalanceContext: - if ty != machine.TypeMonetary { - return LogicError(c, fmt.Errorf( - "variable $%s: type should be 'monetary' to pull account balance", name)) - } - accTy, accAddr, compErr := p.VisitExpr(c.GetAccount(), false) - if compErr != nil { - return compErr - } - if accTy != machine.TypeAccount { - return LogicError(c, fmt.Errorf( - "variable $%s: the first argument to pull account balance should be of type 'account'", name)) - } - - assTy, assAddr, compErr := p.VisitExpr(c.GetAsset(), false) - if compErr != nil { - return compErr - } - if assTy != machine.TypeAsset { - return LogicError(c, fmt.Errorf( - "variable $%s: the second argument to pull account balance should be of type 'asset'", name)) - } - - addr, err = p.AllocateResource(program.VariableAccountBalance{ - Name: name, - Account: *accAddr, - Asset: *assAddr, - }) - p.readLockAccounts[*accAddr] = struct{}{} - if err != nil { - return LogicError(c, err) - } - } - if err != nil { - return LogicError(c, err) - } - - p.varIdx[name] = *addr - } - - return nil -} - -func (p *parseVisitor) VisitScript(c parser.IScriptContext) *CompileError { - switch c := c.(type) { - case *parser.ScriptContext: - vars := c.GetVars() - if vars != nil { - switch c := vars.(type) { - case *parser.VarListDeclContext: - if err := p.VisitVars(c); err != nil { - return err - } - default: - return InternalError(c) - } - } - - for _, stmt := range c.GetStmts() { - var err *CompileError - switch c := stmt.(type) { - case *parser.PrintContext: - err = p.VisitPrint(c) - case *parser.FailContext: - p.AppendInstruction(program.OP_FAIL) - case *parser.SendContext: - err = p.VisitSend(c) - case *parser.SetTxMetaContext: - err = p.VisitSetTxMeta(c) - case *parser.SetAccountMetaContext: - err = p.VisitSetAccountMeta(c) - case *parser.SaveFromAccountContext: - err = p.VisitSaveFromAccount(c) - default: - return InternalError(c) - } - if err != nil { - return err - } - } - default: - return InternalError(c) - } - - return nil -} - -type CompileArtifacts struct { - Source string - Tokens []antlr.Token - Errors []CompileError - Program *program.Program -} - -func CompileFull(input string) CompileArtifacts { - artifacts := CompileArtifacts{ - Source: input, - } - - errListener := &ErrorListener{} - - is := antlr.NewInputStream(input) - lexer := parser.NewNumScriptLexer(is) - lexer.RemoveErrorListeners() - lexer.AddErrorListener(errListener) - - stream := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel) - p := parser.NewNumScriptParser(stream) - p.RemoveErrorListeners() - p.AddErrorListener(errListener) - - p.BuildParseTrees = true - - tree := p.Script() - - artifacts.Tokens = stream.GetAllTokens() - artifacts.Errors = append(artifacts.Errors, errListener.Errors...) - - if len(errListener.Errors) != 0 { - return artifacts - } - - visitor := parseVisitor{ - errListener: errListener, - instructions: make([]byte, 0), - resources: make([]program.Resource, 0), - varIdx: make(map[string]machine.Address), - neededBalances: make(map[machine.Address]map[machine.Address]struct{}), - sources: map[machine.Address]struct{}{}, - writeLockAccounts: map[machine.Address]struct{}{}, - readLockAccounts: map[machine.Address]struct{}{}, - } - - err := visitor.VisitScript(tree) - if err != nil { - artifacts.Errors = append(artifacts.Errors, *err) - return artifacts - } - - readLockAccounts := make(machine.Addresses, 0) - for address := range visitor.readLockAccounts { - readLockAccounts = append(readLockAccounts, address) - } - sort.Stable(readLockAccounts) - - writeLockAccounts := make(machine.Addresses, 0) - for address := range visitor.writeLockAccounts { - writeLockAccounts = append(writeLockAccounts, address) - } - sort.Stable(writeLockAccounts) - - artifacts.Program = &program.Program{ - Instructions: visitor.instructions, - Resources: visitor.resources, - NeededBalances: visitor.neededBalances, - ReadLockAccounts: readLockAccounts, - WriteLockAccounts: writeLockAccounts, - } - - return artifacts -} - -func Compile(input string) (*program.Program, error) { - artifacts := CompileFull(input) - if len(artifacts.Errors) > 0 { - err := CompileErrorList{ - Errors: artifacts.Errors, - Source: artifacts.Source, - } - return nil, &err - } - - return artifacts.Program, nil -} diff --git a/components/ledger/internal/machine/script/compiler/compiler_test.go b/components/ledger/internal/machine/script/compiler/compiler_test.go deleted file mode 100644 index a3b694022d..0000000000 --- a/components/ledger/internal/machine/script/compiler/compiler_test.go +++ /dev/null @@ -1,1744 +0,0 @@ -package compiler - -import ( - "bytes" - "fmt" - "math/big" - "reflect" - "testing" - - "github.com/formancehq/ledger/internal/machine" - - program2 "github.com/formancehq/ledger/internal/machine/vm/program" - "github.com/stretchr/testify/require" -) - -type TestCase struct { - Case string - Expected CaseResult -} - -type CaseResult struct { - Instructions []byte - Resources []program2.Resource - Variables []string - Error string -} - -func test(t *testing.T, c TestCase) { - p, err := Compile(c.Case) - if c.Expected.Error != "" { - require.Error(t, err) - require.NotEmpty(t, err.Error()) - require.ErrorContains(t, err, c.Expected.Error) - return - } - require.NoError(t, err) - require.NotNil(t, p) - - if len(c.Expected.Instructions) > 0 && !bytes.Equal(p.Instructions, c.Expected.Instructions) { - t.Error(fmt.Errorf( - "unexpected instructions:\n%v\nhas: %+v\nwant:%+v", - *p, p.Instructions, c.Expected.Instructions)) - return - } else if len(p.Resources) != len(c.Expected.Resources) { - t.Error(fmt.Errorf( - "unexpected resources\n%v\nhas: \n%+v\nwant:\n%+v", - *p, p.Resources, c.Expected.Resources)) - return - } - - for i, expected := range c.Expected.Resources { - if !checkResourcesEqual(p.Resources[i], c.Expected.Resources[i]) { - t.Error(fmt.Errorf("%v: %v is not %v: %v", - p.Resources[i], reflect.TypeOf(p.Resources[i]).Name(), - expected, reflect.TypeOf(expected).Name(), - )) - t.Error(fmt.Errorf( - "unexpected resources\n%v\nhas: \n%+v\nwant:\n%+v", - *p, p.Resources, c.Expected.Resources)) - return - } - } -} - -func checkResourcesEqual(actual, expected program2.Resource) bool { - if reflect.TypeOf(actual) != reflect.TypeOf(expected) { - return false - } - switch res := actual.(type) { - case program2.Constant: - return machine.ValueEquals(res.Inner, expected.(program2.Constant).Inner) - case program2.Variable: - e := expected.(program2.Variable) - return res.Typ == e.Typ && res.Name == e.Name - case program2.VariableAccountMetadata: - e := expected.(program2.VariableAccountMetadata) - return res.Account == e.Account && - res.Key == e.Key && - res.Typ == e.Typ - case program2.VariableAccountBalance: - e := expected.(program2.VariableAccountBalance) - return res.Account == e.Account && - res.Asset == e.Asset - case program2.Monetary: - e := expected.(program2.Monetary) - return res.Amount.Equal(e.Amount) && res.Asset == e.Asset - default: - panic(fmt.Errorf("invalid resource of type '%T'", res)) - } -} - -func TestSimplePrint(t *testing.T) { - test(t, TestCase{ - Case: "print 1", - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_PRINT, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - }, - }, - }) -} - -func TestCompositeExpr(t *testing.T) { - test(t, TestCase{ - Case: "print 29 + 15 - 2", - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_IADD, - program2.OP_APUSH, 02, 00, - program2.OP_ISUB, - program2.OP_PRINT, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.NewMonetaryInt(29)}, - program2.Constant{Inner: machine.NewMonetaryInt(15)}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - }, - }, - }) -} - -func TestFail(t *testing.T) { - test(t, TestCase{ - Case: "fail", - Expected: CaseResult{ - Instructions: []byte{program2.OP_FAIL}, - Resources: []program2.Resource{}, - }, - }) -} - -func TestCRLF(t *testing.T) { - test(t, TestCase{ - Case: "print @a\r\nprint @b", - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_PRINT, - program2.OP_APUSH, 01, 00, - program2.OP_PRINT, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.AccountAddress("a")}, - program2.Constant{Inner: machine.AccountAddress("b")}, - }, - }, - }) -} - -func TestConstant(t *testing.T) { - user := machine.AccountAddress("user:U001") - test(t, TestCase{ - Case: "print @user:U001", - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_PRINT, - }, - Resources: []program2.Resource{program2.Constant{Inner: user}}, - }, - }) -} - -func TestSetTxMeta(t *testing.T) { - test(t, TestCase{ - Case: ` - set_tx_meta("aaa", @platform) - set_tx_meta("bbb", GEM) - set_tx_meta("ccc", 42) - set_tx_meta("ddd", "test") - set_tx_meta("eee", [COIN 30]) - set_tx_meta("fff", 15%) - `, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_TX_META, - program2.OP_APUSH, 02, 00, - program2.OP_APUSH, 03, 00, - program2.OP_TX_META, - program2.OP_APUSH, 04, 00, - program2.OP_APUSH, 05, 00, - program2.OP_TX_META, - program2.OP_APUSH, 06, 00, - program2.OP_APUSH, 07, 00, - program2.OP_TX_META, - program2.OP_APUSH, 9, 00, - program2.OP_APUSH, 10, 00, - program2.OP_TX_META, - program2.OP_APUSH, 11, 00, - program2.OP_APUSH, 12, 00, - program2.OP_TX_META, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.AccountAddress("platform")}, - program2.Constant{Inner: machine.String("aaa")}, - program2.Constant{Inner: machine.Asset("GEM")}, - program2.Constant{Inner: machine.String("bbb")}, - program2.Constant{Inner: machine.NewNumber(42)}, - program2.Constant{Inner: machine.String("ccc")}, - program2.Constant{Inner: machine.String("test")}, - program2.Constant{Inner: machine.String("ddd")}, - program2.Constant{Inner: machine.Asset("COIN")}, - program2.Monetary{Asset: 8, Amount: machine.NewMonetaryInt(30)}, - program2.Constant{Inner: machine.String("eee")}, - program2.Constant{Inner: machine.Portion{ - Remaining: false, - Specific: big.NewRat(15, 100), - }}, - program2.Constant{Inner: machine.String("fff")}, - }, - }, - }) -} - -func TestSetTxMetaVars(t *testing.T) { - test(t, TestCase{ - Case: ` - vars { - portion $commission - } - set_tx_meta("fee", $commission) - `, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_TX_META, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypePortion, Name: "commission"}, - program2.Constant{Inner: machine.String("fee")}, - }, - }, - }) -} - -func TestComments(t *testing.T) { - test(t, TestCase{ - Case: ` - /* This is a multi-line comment, it spans multiple lines - and /* doesn't choke on nested comments */ ! */ - vars { - account $a - } - // this is a single-line comment - print $a - `, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_PRINT, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAccount, Name: "a"}, - }, - }, - }) -} - -func TestUndeclaredVariable(t *testing.T) { - test(t, TestCase{ - Case: "print $nope", - Expected: CaseResult{ - Error: "declared", - }, - }) -} - -func TestInvalidTypeInSendValue(t *testing.T) { - test(t, TestCase{ - Case: ` - send @a ( - source = { - @a - [GEM 2] - } - destination = @b - )`, - Expected: CaseResult{ - Error: "send monetary: the expression should be of type 'monetary' instead of 'account'", - }, - }) -} - -func TestInvalidTypeInSource(t *testing.T) { - test(t, TestCase{ - Case: ` - send [USD/2 99] ( - source = { - @a - [GEM 2] - } - destination = @b - )`, - Expected: CaseResult{ - Error: "wrong type", - }, - }) -} - -func TestDestinationAllotment(t *testing.T) { - test(t, TestCase{ - Case: `send [EUR/2 43] ( - source = @foo - destination = { - 1/8 to @bar - 7/8 to @baz - } - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 02, 00, // @foo - program2.OP_APUSH, 01, 00, // @foo, [EUR/2 43] - program2.OP_ASSET, // @foo, EUR/2 - program2.OP_APUSH, 03, 00, // @foo, EUR/2, 0 - program2.OP_MONETARY_NEW, // @foo, [EUR/2 0] - program2.OP_TAKE_ALL, // [EUR/2 @foo ] - program2.OP_APUSH, 01, 00, // [EUR/2 @foo ], [EUR/2 43] - program2.OP_TAKE, // [EUR/2 @foo ], [EUR/2 @foo 43] - program2.OP_APUSH, 04, 00, // [EUR/2 @foo ], [EUR/2 @foo 43] 1 - program2.OP_BUMP, // [EUR/2 @foo 43], [EUR/2 @foo ] - program2.OP_REPAY, // [EUR/2 @foo 43] - program2.OP_FUNDING_SUM, // [EUR/2 @foo 43], [EUR/2 43] - program2.OP_APUSH, 05, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8 - program2.OP_APUSH, 06, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8, 1/8 - program2.OP_APUSH, 07, 00, // [EUR/2 @foo 43], [EUR/2 43], 7/8, 1/8, 2 - program2.OP_MAKE_ALLOTMENT, // [EUR/2 @foo 43], [EUR/2 43], {1/8 : 7/8} - program2.OP_ALLOC, // [EUR/2 @foo 43], [EUR/2 37], [EUR/2 6] - program2.OP_APUSH, 07, 00, // [EUR/2 @foo 43], [EUR/2 37] [EUR/2 6], 2 - program2.OP_BUMP, // [EUR/2 37], [EUR/2 6], [EUR/2 @foo 43] - program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 6], [EUR/2 @foo 43] 1 - program2.OP_BUMP, // [EUR/2 37], [EUR/2 @foo 43], [EUR/2 6] - program2.OP_TAKE, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2 @foo 6] - program2.OP_FUNDING_SUM, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2 @foo 6] [EUR/2 6] - program2.OP_TAKE, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] [EUR/2 @foo 6] - program2.OP_APUSH, 8, 00, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] [EUR/2 @foo 6], @bar - program2.OP_SEND, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] - program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 @foo 37], [EUR/2] 1 - program2.OP_BUMP, // [EUR/2 37], [EUR/2], [EUR/2 @foo 37] - program2.OP_APUSH, 07, 00, // [EUR/2 37], [EUR/2], [EUR/2 @foo 37] 2 - program2.OP_FUNDING_ASSEMBLE, // [EUR/2 37], [EUR/2 @foo 37] - program2.OP_APUSH, 04, 00, // [EUR/2 37], [EUR/2 @foo 37], 1 - program2.OP_BUMP, // [EUR/2 @foo 37], [EUR/2 37] - program2.OP_TAKE, // [EUR/2], [EUR/2 @foo 37] - program2.OP_FUNDING_SUM, // [EUR/2], [EUR/2 @foo 37], [EUR/2 37] - program2.OP_TAKE, // [EUR/2], [EUR/2], [EUR/2 @foo 37] - program2.OP_APUSH, 9, 00, // [EUR/2], [EUR/2], [EUR/2 @foo 37], @baz - program2.OP_SEND, // [EUR/2], [EUR/2] - program2.OP_APUSH, 04, 00, // [EUR/2], [EUR/2], 1 - program2.OP_BUMP, // [EUR/2], [EUR/2] - program2.OP_APUSH, 07, 00, // [EUR/2], [EUR/2], 2 - program2.OP_FUNDING_ASSEMBLE, // [EUR/2] - program2.OP_REPAY, // - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(43), - }, - program2.Constant{Inner: machine.AccountAddress("foo")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.Portion{Specific: big.NewRat(7, 8)}}, - program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 8)}}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.AccountAddress("bar")}, - program2.Constant{Inner: machine.AccountAddress("baz")}, - }, - }, - }) -} - -func TestDestinationInOrder(t *testing.T) { - test(t, TestCase{ - Case: `send [COIN 50] ( - source = @a - destination = { - max [COIN 10] to @b - remaining to @c - } - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 02, 00, // @a - program2.OP_APUSH, 01, 00, // @a, [COIN 50] - program2.OP_ASSET, // @a, COIN - program2.OP_APUSH, 03, 00, // @a, COIN, 0 - program2.OP_MONETARY_NEW, // @a, [COIN 0] - program2.OP_TAKE_ALL, // [COIN @a ] - program2.OP_APUSH, 01, 00, // [COIN @a ], [COIN 50] - program2.OP_TAKE, // [COIN @a ], [COIN @a 50] - program2.OP_APUSH, 04, 00, // [COIN @a ], [COIN @a 50], 1 - program2.OP_BUMP, // [COIN @a 50], [COIN @a ] - program2.OP_REPAY, // [COIN @a 50] - program2.OP_FUNDING_SUM, // [COIN @a 50], [COIN 50] <- start of DestinationInOrder - program2.OP_ASSET, // [COIN @a 50], COIN - program2.OP_APUSH, 03, 00, // [COIN @a 50], COIN, 0 - program2.OP_MONETARY_NEW, // [COIN @a 50], [COIN 0] - program2.OP_APUSH, 04, 00, // [COIN @a 50], [COIN 0], 1 - program2.OP_BUMP, // [COIN 0], [COIN @a 50] - program2.OP_APUSH, 05, 00, // [COIN 0], [COIN @a 50], [COIN 10] <- start processing max subdestinations - program2.OP_TAKE_MAX, // [COIN 0], [COIN 0], [COIN @a 40], [COIN @a 10] - program2.OP_APUSH, 06, 00, // [COIN 0], [COIN 0], [COIN @a 40], [COIN @a 10], 2 - program2.OP_BUMP, // [COIN 0], [COIN @a 40], [COIN @a 10], [COIN 0] - program2.OP_DELETE, // [COIN 0], [COIN @a 40], [COIN @a 10] - program2.OP_FUNDING_SUM, // [COIN 0], [COIN @a 40], [COIN @a 10], [COIN 10] - program2.OP_TAKE, // [COIN 0], [COIN @a 40], [COIN], [COIN @a 10] - program2.OP_APUSH, 07, 00, // [COIN 0], [COIN @a 40], [COIN], [COIN @a 10], @b - program2.OP_SEND, // [COIN 0], [COIN @a 40], [COIN] - program2.OP_FUNDING_SUM, // [COIN 0], [COIN @a 40], [COIN], [COIN 0] - program2.OP_APUSH, 8, 00, // [COIN 0], [COIN @a 40], [COIN], [COIN 0], 3 - program2.OP_BUMP, // [COIN @a 40], [COIN], [COIN 0], [COIN 0] - program2.OP_MONETARY_ADD, // [COIN @a 40], [COIN], [COIN 0] - program2.OP_APUSH, 04, 00, // [COIN @a 40], [COIN], [COIN 0], 1 - program2.OP_BUMP, // [COIN @a 40], [COIN 0], [COIN] - program2.OP_APUSH, 06, 00, // [COIN @a 40], [COIN 0], [COIN] 2 - program2.OP_BUMP, // [COIN 0], [COIN], [COIN @a 40] - program2.OP_APUSH, 06, 00, // [COIN 0], [COIN], [COIN @a 40], 2 - program2.OP_FUNDING_ASSEMBLE, // [COIN 0], [COIN @a 40] - program2.OP_FUNDING_REVERSE, // [COIN 0], [COIN @a 40] <- start processing remaining subdestination - program2.OP_APUSH, 04, 00, // [COIN 0], [COIN @a 40], 1 - program2.OP_BUMP, // [COIN @a 40], [COIN 0] - program2.OP_TAKE, // [COIN @a 40], [COIN] - program2.OP_FUNDING_REVERSE, // [COIN @a 40], [COIN] - program2.OP_APUSH, 04, 00, // [COIN @a 40], [COIN], 1 - program2.OP_BUMP, // [COIN], [COIN @a 40] - program2.OP_FUNDING_REVERSE, // [COIN], [COIN @a 40] - program2.OP_FUNDING_SUM, // [COIN], [COIN @a 40], [COIN 40] - program2.OP_TAKE, // [COIN], [COIN], [COIN @a 40] - program2.OP_APUSH, 9, 00, // [COIN], [COIN], [COIN @a 40], @c - program2.OP_SEND, // [COIN], [COIN] - program2.OP_APUSH, 04, 00, // [COIN], [COIN], 1 - program2.OP_BUMP, // [COIN], [COIN] - program2.OP_APUSH, 06, 00, // [COIN], [COIN], 2 - program2.OP_FUNDING_ASSEMBLE, // [COIN] - program2.OP_REPAY, // - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("COIN")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(50), - }, - program2.Constant{Inner: machine.AccountAddress("a")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(10), - }, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.AccountAddress("b")}, - program2.Constant{Inner: machine.NewMonetaryInt(3)}, - program2.Constant{Inner: machine.AccountAddress("c")}, - }, - }, - }) -} - -func TestAllocationPercentages(t *testing.T) { - test(t, TestCase{ - Case: `send [EUR/2 43] ( - source = @foo - destination = { - 12.5% to @bar - 37.5% to @baz - 50% to @qux - } - )`, - Expected: CaseResult{ - Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(43), - }, - program2.Constant{Inner: machine.AccountAddress("foo")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 2)}}, - program2.Constant{Inner: machine.Portion{Specific: big.NewRat(3, 8)}}, - program2.Constant{Inner: machine.Portion{Specific: big.NewRat(1, 8)}}, - program2.Constant{Inner: machine.NewMonetaryInt(3)}, - program2.Constant{Inner: machine.AccountAddress("bar")}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.AccountAddress("baz")}, - program2.Constant{Inner: machine.AccountAddress("qux")}, - }, - }, - }) -} - -func TestSend(t *testing.T) { - script := ` - send [EUR/2 99] ( - source = @alice - destination = @bob - )` - alice := machine.AccountAddress("alice") - bob := machine.AccountAddress("bob") - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 02, 00, // @alice - program2.OP_APUSH, 01, 00, // @alice, [EUR/2 99] - program2.OP_ASSET, // @alice, EUR/2 - program2.OP_APUSH, 03, 00, // @alice, EUR/2, 0 - program2.OP_MONETARY_NEW, // @alice, [EUR/2 0] - program2.OP_TAKE_ALL, // [EUR/2 @alice ] - program2.OP_APUSH, 01, 00, // [EUR/2 @alice ], [EUR/2 99] - program2.OP_TAKE, // [EUR/2 @alice ], [EUR/2 @alice 99] - program2.OP_APUSH, 04, 00, // [EUR/2 @alice ], [EUR/2 @alice 99], 1 - program2.OP_BUMP, // [EUR/2 @alice 99], [EUR/2 @alice ] - program2.OP_REPAY, // [EUR/2 @alice 99] - program2.OP_FUNDING_SUM, // [EUR/2 @alice 99], [EUR/2 99] - program2.OP_TAKE, // [EUR/2], [EUR/2 @alice 99] - program2.OP_APUSH, 05, 00, // [EUR/2], [EUR/2 @alice 99], @bob - program2.OP_SEND, // [EUR/2] - program2.OP_REPAY, // - }, Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(99), - }, - program2.Constant{Inner: alice}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: bob}}, - }, - }) -} - -func TestSendAll(t *testing.T) { - test(t, TestCase{ - Case: `send [EUR/2 *] ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 01, 00, // @alice - program2.OP_APUSH, 00, 00, // @alice, EUR/2 - program2.OP_APUSH, 02, 00, // @alice, EUR/2, 0 - program2.OP_MONETARY_NEW, // @alice, [EUR/2 0] - program2.OP_TAKE_ALL, // [EUR/2 @alice ] - program2.OP_FUNDING_SUM, // [EUR/2 @alice ], [EUR/2 ] - program2.OP_TAKE, // [EUR/2], [EUR/2 @alice ] - program2.OP_APUSH, 03, 00, // [EUR/2], [EUR/2 @alice ], @b - program2.OP_SEND, // [EUR/2] - program2.OP_REPAY, // - }, Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.AccountAddress("bob")}}, - }, - }) -} - -func TestMetadata(t *testing.T) { - test(t, TestCase{ - Case: ` - vars { - account $sale - account $seller = meta($sale, "seller") - portion $commission = meta($seller, "commission") - } - send [EUR/2 53] ( - source = $sale - destination = { - $commission to @platform - remaining to $seller - } - )`, - Expected: CaseResult{ - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAccount, Name: "sale"}, - program2.VariableAccountMetadata{ - Typ: machine.TypeAccount, - Account: machine.NewAddress(0), - Key: "seller", - }, - program2.VariableAccountMetadata{ - Typ: machine.TypePortion, - Account: machine.NewAddress(1), - Key: "commission", - }, - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Monetary{ - Asset: 3, - Amount: machine.NewMonetaryInt(53), - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.NewPortionRemaining()}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.AccountAddress("platform")}, - }, - }, - }) -} - -func TestSyntaxError(t *testing.T) { - test(t, TestCase{ - Case: "print fail", - Expected: CaseResult{ - Error: "mismatched input", - }, - }) -} - -func TestLogicError(t *testing.T) { - test(t, TestCase{ - Case: `send [EUR/2 200] ( - source = 200 - destination = @bob - )`, - Expected: CaseResult{ - Error: "expected", - }, - }) -} - -func TestPreventTakeAllFromWorld(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM *] ( - source = @world - destination = @foo - )`, - Expected: CaseResult{ - Error: "cannot", - }, - }) -} - -func TestPreventAddToBottomlessSource(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 1000] ( - source = { - @a - @world - @c - } - destination = @out - )`, - Expected: CaseResult{ - Error: "world", - }, - }) -} - -func TestPreventAddToBottomlessSource2(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 1000] ( - source = { - { - @a - @world - } - { - @b - @world - } - } - destination = @out - )`, - Expected: CaseResult{ - Error: "world", - }, - }) -} - -func TestPreventSourceAlreadyEmptied(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 1000] ( - source = { - { - @a - @b - } - @a - } - destination = @out - )`, - Expected: CaseResult{ - Error: "empt", - }, - }) -} - -func TestPreventTakeAllFromAllocation(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM *] ( - source = { - 50% from @a - 50% from @b - } - destination = @out - )`, - Expected: CaseResult{ - Error: "all", - }, - }) -} - -func TestWrongTypeSourceMax(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = { - max @foo from @bar - @world - } - destination = @baz - )`, - Expected: CaseResult{ - Error: "type", - }, - }) -} - -func TestOverflowingSources(t *testing.T) { - t.Run(">100%", func(t *testing.T) { - test(t, TestCase{ - Case: `send [COIN 100] ( - source = { - 1/2 from @alice - 3/4 from @bob - } - destination = @dest - )`, - Expected: CaseResult{ - Error: "greater than 100%", - }, - }) - }) - - t.Run("<100%", func(t *testing.T) { - test(t, TestCase{ - Case: `send [COIN 100] ( - source = { - 1/2 from @world - } - destination = @dest - )`, - Expected: CaseResult{ - Error: "less than 100%", - }, - }) - }) - -} - -func TestOverflowingAllocation(t *testing.T) { - t.Run(">100%", func(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = { - 2/3 to @a - 2/3 to @b - } - )`, - Expected: CaseResult{ - Error: "100%", - }, - }) - }) - - t.Run("=100% + remaining", func(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = { - 1/2 to @a - 1/2 to @b - remaining to @c - } - )`, - Expected: CaseResult{ - Error: "100%", - }, - }) - }) - - t.Run(">100% + remaining", func(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = { - 2/3 to @a - 1/2 to @b - remaining to @c - } - )`, - Expected: CaseResult{ - Error: "100%", - }, - }) - }) - - t.Run("const remaining + remaining", func(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = { - 2/3 to @a - remaining to @b - remaining to @c - } - )`, - Expected: CaseResult{ - Error: "`remaining` in the same", - }, - }) - }) - - t.Run("dyn remaining + remaining", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - portion $p - } - send [GEM 15] ( - source = @world - destination = { - $p to @a - remaining to @b - remaining to @c - } - )`, - Expected: CaseResult{ - Error: "`remaining` in the same", - }, - }) - }) - - t.Run(">100% + remaining + variable", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - portion $prop - } - send [GEM 15] ( - source = @world - destination = { - 1/2 to @a - 2/3 to @b - remaining to @c - $prop to @d - } - )`, - Expected: CaseResult{ - Error: "100%", - }, - }) - }) - - t.Run("variable - remaining", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - portion $prop - } - send [GEM 15] ( - source = @world - destination = { - 2/3 to @a - $prop to @b - } - )`, - Expected: CaseResult{ - Error: "100%", - }, - }) - }) -} - -func TestAllocationWrongDestination(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = [GEM 10] - )`, - Expected: CaseResult{ - Error: "account", - }, - }) - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world - destination = { - 2/3 to @a - 1/3 to [GEM 10] - } - )`, - Expected: CaseResult{ - Error: "account", - }, - }) -} - -func TestAllocationInvalidPortion(t *testing.T) { - test(t, TestCase{ - Case: `vars { - account $p - } - send [GEM 15] ( - source = @world - destination = { - 10% to @a - $p to @b - } - )`, - Expected: CaseResult{ - Error: "type", - }, - }) -} - -func TestOverdraftOnWorld(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @world allowing overdraft up to [GEM 10] - destination = @foo - )`, - Expected: CaseResult{ - Error: "overdraft", - }, - }) -} - -func TestOverdraftWrongType(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @foo allowing overdraft up to @baz - destination = @bar - )`, - Expected: CaseResult{ - Error: "type", - }, - }) -} - -func TestDestinationInOrderWrongType(t *testing.T) { - test(t, TestCase{ - Case: `send [GEM 15] ( - source = @foo - destination = { - max @bar to @baz - remaining to @qux - } - )`, - Expected: CaseResult{ - Error: "type", - }, - }) -} - -func TestSetAccountMeta(t *testing.T) { - t.Run("all types", func(t *testing.T) { - test(t, TestCase{ - Case: ` - set_account_meta(@alice, "aaa", @platform) - set_account_meta(@alice, "bbb", GEM) - set_account_meta(@alice, "ccc", 42) - set_account_meta(@alice, "ddd", "test") - set_account_meta(@alice, "eee", [COIN 30]) - set_account_meta(@alice, "fff", 15%) - `, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 04, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - program2.OP_APUSH, 05, 00, - program2.OP_APUSH, 06, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - program2.OP_APUSH, 7, 00, - program2.OP_APUSH, 8, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - program2.OP_APUSH, 10, 00, - program2.OP_APUSH, 11, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - program2.OP_APUSH, 12, 00, - program2.OP_APUSH, 13, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ACCOUNT_META, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.AccountAddress("platform")}, - program2.Constant{Inner: machine.String("aaa")}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Constant{Inner: machine.Asset("GEM")}, - program2.Constant{Inner: machine.String("bbb")}, - program2.Constant{Inner: machine.NewNumber(42)}, - program2.Constant{Inner: machine.String("ccc")}, - program2.Constant{Inner: machine.String("test")}, - program2.Constant{Inner: machine.String("ddd")}, - program2.Constant{Inner: machine.Asset("COIN")}, - program2.Monetary{ - Asset: 9, - Amount: machine.NewMonetaryInt(30), - }, - program2.Constant{Inner: machine.String("eee")}, - program2.Constant{Inner: machine.Portion{ - Remaining: false, - Specific: big.NewRat(15, 100), - }}, - program2.Constant{Inner: machine.String("fff")}, - }, - }, - }) - }) - - t.Run("with vars", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - account $acc - } - send [EUR/2 100] ( - source = @world - destination = $acc - ) - set_account_meta($acc, "fees", 1%)`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ASSET, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALWAYS, - program2.OP_APUSH, 02, 00, - program2.OP_TAKE_MAX, - program2.OP_APUSH, 05, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 06, 00, - program2.OP_BUMP, - program2.OP_TAKE_ALWAYS, - program2.OP_APUSH, 06, 00, - program2.OP_FUNDING_ASSEMBLE, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 00, 00, - program2.OP_SEND, - program2.OP_REPAY, - program2.OP_APUSH, 07, 00, - program2.OP_APUSH, 8, 00, - program2.OP_APUSH, 00, 00, - program2.OP_ACCOUNT_META, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAccount, Name: "acc"}, - program2.Constant{Inner: machine.Asset("EUR/2")}, - program2.Monetary{ - Asset: 1, - Amount: machine.NewMonetaryInt(100), - }, - program2.Constant{Inner: machine.AccountAddress("world")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.Portion{ - Remaining: false, - Specific: big.NewRat(1, 100), - }}, - program2.Constant{Inner: machine.String("fees")}, - }, - }, - }) - }) - - t.Run("errors", func(t *testing.T) { - test(t, TestCase{ - Case: `set_account_meta(@alice, "fees")`, - Expected: CaseResult{ - Error: "mismatched input", - }, - }) - test(t, TestCase{ - Case: `set_account_meta("test")`, - Expected: CaseResult{ - Error: "mismatched input", - }, - }) - test(t, TestCase{ - Case: `set_account_meta(@alice, "t1", "t2", "t3")`, - Expected: CaseResult{ - Error: "mismatched input", - }, - }) - test(t, TestCase{ - Case: `vars { - portion $p - } - set_account_meta($p, "fees", 1%)`, - Expected: CaseResult{ - Error: "should be of type account", - }, - }) - }) -} - -func TestVariableBalance(t *testing.T) { - t.Run("simplest", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - monetary $bal = balance(@alice, COIN) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ASSET, - program2.OP_APUSH, 03, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 02, 00, - program2.OP_TAKE, - program2.OP_APUSH, 04, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 05, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Constant{Inner: machine.Asset("COIN")}, - program2.VariableAccountBalance{Account: 0, Asset: 1}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - }, - }, - }) - }) - - t.Run("with account variable", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - account $acc - monetary $bal = balance($acc, COIN) - } - send $bal ( - source = @world - destination = @alice - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ASSET, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALWAYS, - program2.OP_APUSH, 02, 00, - program2.OP_TAKE_MAX, - program2.OP_APUSH, 05, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 06, 00, - program2.OP_BUMP, - program2.OP_TAKE_ALWAYS, - program2.OP_APUSH, 06, 00, - program2.OP_FUNDING_ASSEMBLE, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 07, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAccount, Name: "acc"}, - program2.Constant{Inner: machine.Asset("COIN")}, - program2.VariableAccountBalance{Account: 0, Asset: 1}, - program2.Constant{Inner: machine.AccountAddress("world")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - }, - }, - }) - }) - - t.Run("error variable type", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - account $bal = balance(@alice, COIN) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "variable $bal: type should be 'monetary' to pull account balance", - }, - }) - }) - - t.Run("error no asset", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - monetary $bal = balance(@alice) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "mismatched input", - }, - }) - }) - - t.Run("error too many arguments", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - monetary $bal = balance(@alice, USD, COIN) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "mismatched input ',' expecting ')'", - }, - }) - }) - - t.Run("error wrong type for account", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - monetary $bal = balance(USD, COIN) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "variable $bal: the first argument to pull account balance should be of type 'account'", - }, - }) - }) - - t.Run("error wrong type for asset", func(t *testing.T) { - test(t, TestCase{ - Case: `vars { - monetary $bal = balance(@alice, @bob) - } - send $bal ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "variable $bal: the second argument to pull account balance should be of type 'asset'", - }, - }) - }) - - t.Run("error not in variables", func(t *testing.T) { - test(t, TestCase{ - Case: `send balance(@alice, COIN) ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Error: "mismatched input 'balance'", - }, - }) - }) -} - -func TestVariableAsset(t *testing.T) { - script := `vars { - asset $ass - monetary $bal = balance(@alice, $ass) - } - - send [$ass *] ( - source = @alice - destination = @bob - ) - - send [$ass 1] ( - source = @bob - destination = @alice - ) - - send $bal ( - source = @alice - destination = @bob - )` - - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 03, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 04, 00, - program2.OP_SEND, - program2.OP_REPAY, - program2.OP_APUSH, 04, 00, - program2.OP_APUSH, 05, 00, - program2.OP_ASSET, - program2.OP_APUSH, 03, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 05, 00, - program2.OP_TAKE, - program2.OP_APUSH, 06, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 01, 00, - program2.OP_SEND, - program2.OP_REPAY, - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ASSET, - program2.OP_APUSH, 03, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 02, 00, - program2.OP_TAKE, - program2.OP_APUSH, 06, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 04, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAsset, Name: "ass"}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.VariableAccountBalance{ - Name: "bal", - Account: 1, - Asset: 0, - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(1), - }, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - }, - }, - }) -} - -func TestPrint(t *testing.T) { - script := `print 1 + 2 + 3` - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_IADD, - program2.OP_APUSH, 02, 00, - program2.OP_IADD, - program2.OP_PRINT, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.NewMonetaryInt(2)}, - program2.Constant{Inner: machine.NewMonetaryInt(3)}, - }, - }, - }) -} - -func TestSendWithArithmetic(t *testing.T) { - t.Run("nominal", func(t *testing.T) { - script := ` - vars { - asset $ass - monetary $mon - } - send [EUR 1] + $mon + [$ass 3] - [EUR 4] ( - source = @a - destination = @b - )` - - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 06, 00, - program2.OP_APUSH, 03, 00, - program2.OP_ASSET, - program2.OP_APUSH, 07, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 03, 00, - program2.OP_APUSH, 01, 00, - program2.OP_MONETARY_ADD, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_ADD, - program2.OP_APUSH, 05, 00, - program2.OP_MONETARY_SUB, - program2.OP_TAKE, - program2.OP_APUSH, 8, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 9, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Variable{ - Typ: machine.TypeAsset, - Name: "ass", - }, - program2.Variable{ - Typ: machine.TypeMonetary, - Name: "mon", - }, - program2.Constant{Inner: machine.Asset("EUR")}, - program2.Monetary{ - Asset: 2, - Amount: machine.NewMonetaryInt(1), - }, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(3), - }, - program2.Monetary{ - Asset: 2, - Amount: machine.NewMonetaryInt(4), - }, - program2.Constant{Inner: machine.AccountAddress("a")}, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("b")}, - }, - }, - }) - }) - - t.Run("error incompatible types", func(t *testing.T) { - script := `send [EUR 1] + 2 ( - source = @world - destination = @bob - )` - - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{}, - Resources: []program2.Resource{}, - Error: "tried to do an arithmetic operation with incompatible left and right-hand side operand types: monetary and number", - }, - }) - }) - - t.Run("error incompatible types var", func(t *testing.T) { - script := ` - vars { - number $nb - } - send [EUR 1] - $nb ( - source = @world - destination = @bob - )` - - test(t, TestCase{ - Case: script, - Expected: CaseResult{ - Instructions: []byte{}, - Resources: []program2.Resource{}, - Error: "tried to do an arithmetic operation with incompatible left and right-hand side operand types: monetary and number", - }, - }) - }) -} - -func TestSaveFromAccount(t *testing.T) { - t.Run("simple", func(t *testing.T) { - test(t, TestCase{ - Case: ` - save [EUR 10] from @alice - - send [EUR 20] ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 02, 00, - program2.OP_SAVE, - program2.OP_APUSH, 02, 00, - program2.OP_APUSH, 03, 00, - program2.OP_ASSET, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 03, 00, - program2.OP_TAKE, - program2.OP_APUSH, 05, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 06, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(10), - }, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(20), - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - }, - }, - }) - }) - - t.Run("save all", func(t *testing.T) { - test(t, TestCase{ - Case: ` - save [EUR *] from @alice - - send [EUR 20] ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_SAVE, - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 02, 00, - program2.OP_ASSET, - program2.OP_APUSH, 03, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 02, 00, - program2.OP_TAKE, - program2.OP_APUSH, 04, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 05, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Constant{Inner: machine.Asset("EUR")}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(20), - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - }, - }, - }) - }) - - t.Run("with asset var", func(t *testing.T) { - test(t, TestCase{ - Case: ` - vars { - asset $ass - } - - save [$ass 10] from @alice - - send [$ass 20] ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 02, 00, - program2.OP_SAVE, - program2.OP_APUSH, 02, 00, - program2.OP_APUSH, 03, 00, - program2.OP_ASSET, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 03, 00, - program2.OP_TAKE, - program2.OP_APUSH, 05, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 06, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeAsset, Name: "ass"}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(10), - }, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Monetary{ - Asset: 0, - Amount: machine.NewMonetaryInt(20), - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - }, - }, - }) - }) - - t.Run("with monetary var", func(t *testing.T) { - test(t, TestCase{ - Case: ` - vars { - monetary $mon - } - - save $mon from @alice - - send [EUR 20] ( - source = @alice - destination = @bob - )`, - Expected: CaseResult{ - Instructions: []byte{ - program2.OP_APUSH, 00, 00, - program2.OP_APUSH, 01, 00, - program2.OP_SAVE, - program2.OP_APUSH, 01, 00, - program2.OP_APUSH, 03, 00, - program2.OP_ASSET, - program2.OP_APUSH, 04, 00, - program2.OP_MONETARY_NEW, - program2.OP_TAKE_ALL, - program2.OP_APUSH, 03, 00, - program2.OP_TAKE, - program2.OP_APUSH, 05, 00, - program2.OP_BUMP, - program2.OP_REPAY, - program2.OP_FUNDING_SUM, - program2.OP_TAKE, - program2.OP_APUSH, 06, 00, - program2.OP_SEND, - program2.OP_REPAY, - }, - Resources: []program2.Resource{ - program2.Variable{Typ: machine.TypeMonetary, Name: "mon"}, - program2.Constant{Inner: machine.AccountAddress("alice")}, - program2.Constant{Inner: machine.Asset("EUR")}, - program2.Monetary{ - Asset: 2, - Amount: machine.NewMonetaryInt(20), - }, - program2.Constant{Inner: machine.NewMonetaryInt(0)}, - program2.Constant{Inner: machine.NewMonetaryInt(1)}, - program2.Constant{Inner: machine.AccountAddress("bob")}, - }, - }, - }) - }) - - t.Run("error wrong type monetary", func(t *testing.T) { - test(t, TestCase{ - Case: ` - save 30 from @alice - `, - Expected: CaseResult{ - Instructions: []byte{}, - Resources: []program2.Resource{}, - Error: "save monetary from account: the first expression should be of type 'monetary' instead of 'number'", - }, - }) - }) - - t.Run("error wrong type account", func(t *testing.T) { - test(t, TestCase{ - Case: ` - save [EUR 30] from ALICE - `, - Expected: CaseResult{ - Instructions: []byte{}, - Resources: []program2.Resource{}, - Error: "save monetary from account: the second expression should be of type 'account' instead of 'asset'", - }, - }) - }) -} diff --git a/components/ledger/internal/machine/script/compiler/destination.go b/components/ledger/internal/machine/script/compiler/destination.go deleted file mode 100644 index 1b454c13fa..0000000000 --- a/components/ledger/internal/machine/script/compiler/destination.go +++ /dev/null @@ -1,183 +0,0 @@ -package compiler - -import ( - "errors" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/ledger/internal/machine/script/parser" - "github.com/formancehq/ledger/internal/machine/vm/program" -) - -func (p *parseVisitor) VisitDestination(c parser.IDestinationContext) *CompileError { - err := p.VisitDestinationRecursive(c) - if err != nil { - return err - } - p.AppendInstruction(program.OP_REPAY) - return nil -} - -func (p *parseVisitor) VisitDestinationRecursive(c parser.IDestinationContext) *CompileError { - switch c := c.(type) { - case *parser.DestAccountContext: - p.AppendInstruction(program.OP_FUNDING_SUM) - p.AppendInstruction(program.OP_TAKE) - ty, destAddr, err := p.VisitExpr(c.Expression(), true) - if err != nil { - return err - } - if ty != machine.TypeAccount { - return LogicError(c, - errors.New("wrong type: expected account as destination"), - ) - } - if !p.isWorld(*destAddr) { - p.readLockAccounts[*destAddr] = struct{}{} - } - p.AppendInstruction(program.OP_SEND) - return nil - case *parser.DestInOrderContext: - dests := c.DestinationInOrder().GetDests() - amounts := c.DestinationInOrder().GetAmounts() - n := len(dests) - - // initialize the `kept` accumulator - p.AppendInstruction(program.OP_FUNDING_SUM) - p.AppendInstruction(program.OP_ASSET) - err := p.PushInteger(machine.NewNumber(0)) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_MONETARY_NEW) - - err = p.Bump(1) - if err != nil { - return LogicError(c, err) - } - - for i := 0; i < n; i++ { - ty, _, compErr := p.VisitExpr(amounts[i], true) - if compErr != nil { - return compErr - } - if ty != machine.TypeMonetary { - return LogicError(c, errors.New("wrong type: expected monetary as max")) - } - p.AppendInstruction(program.OP_TAKE_MAX) - err := p.Bump(2) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_DELETE) - compErr = p.VisitKeptOrDestination(dests[i]) - if compErr != nil { - return compErr - } - p.AppendInstruction(program.OP_FUNDING_SUM) - err = p.Bump(3) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_MONETARY_ADD) - err = p.Bump(1) - if err != nil { - return LogicError(c, err) - } - err = p.Bump(2) - if err != nil { - return LogicError(c, err) - } - err = p.PushInteger(machine.NewNumber(2)) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } - p.AppendInstruction(program.OP_FUNDING_REVERSE) - err = p.Bump(1) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_TAKE) - p.AppendInstruction(program.OP_FUNDING_REVERSE) - err = p.Bump(1) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_REVERSE) - cerr := p.VisitKeptOrDestination(c.DestinationInOrder().GetRemainingDest()) - if cerr != nil { - return cerr - } - err = p.Bump(1) - if err != nil { - return LogicError(c, err) - } - err = p.PushInteger(machine.NewNumber(2)) - if err != nil { - return LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - return nil - case *parser.DestAllotmentContext: - err := p.VisitDestinationAllotment(c.DestinationAllotment()) - return err - default: - return InternalError(c) - } -} - -func (p *parseVisitor) VisitKeptOrDestination(c parser.IKeptOrDestinationContext) *CompileError { - switch c := c.(type) { - case *parser.IsKeptContext: - return nil - case *parser.IsDestinationContext: - err := p.VisitDestinationRecursive(c.Destination()) - return err - default: - return InternalError(c) - } -} - -func (p *parseVisitor) VisitDestinationAllotment(c parser.IDestinationAllotmentContext) *CompileError { - p.AppendInstruction(program.OP_FUNDING_SUM) - err := p.VisitAllotment(c, c.GetPortions()) - if err != nil { - return err - } - p.AppendInstruction(program.OP_ALLOC) - err = p.VisitAllocDestination(c.GetDests()) - if err != nil { - return err - } - return nil -} - -func (p *parseVisitor) VisitAllocDestination(dests []parser.IKeptOrDestinationContext) *CompileError { - err := p.Bump(int64(len(dests))) - if err != nil { - return LogicError(dests[0], err) - } - for _, dest := range dests { - err = p.Bump(1) - if err != nil { - return LogicError(dest, err) - } - p.AppendInstruction(program.OP_TAKE) - compErr := p.VisitKeptOrDestination(dest) - if compErr != nil { - return compErr - } - err = p.Bump(1) - if err != nil { - return LogicError(dest, err) - } - err = p.PushInteger(machine.NewNumber(2)) - if err != nil { - return LogicError(dest, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } - return nil -} diff --git a/components/ledger/internal/machine/script/compiler/error.go b/components/ledger/internal/machine/script/compiler/error.go deleted file mode 100644 index 43ec901ddb..0000000000 --- a/components/ledger/internal/machine/script/compiler/error.go +++ /dev/null @@ -1,124 +0,0 @@ -package compiler - -import ( - "fmt" - "math" - "strings" - - "github.com/antlr/antlr4/runtime/Go/antlr" - "github.com/logrusorgru/aurora" -) - -type CompileError struct { - StartL, StartC int - EndL, EndC int - Msg string -} - -type CompileErrorList struct { - Errors []CompileError - Source string -} - -func (c *CompileErrorList) Error() string { - source := strings.ReplaceAll(c.Source, "\t", " ") - lines := strings.SplitAfter(strings.ReplaceAll(source, "\r\n", "\n"), "\n") - lines[len(lines)-1] += "\n" - - txtBarGood := aurora.Blue("|") - - s := "" - for _, e := range c.Errors { - lnPad := int(math.Log10(float64(e.EndL))) + 1 // line number padding - // error indicator - s += fmt.Sprintf("%v error:%v:%v\n", aurora.Red("-->"), e.StartL, e.StartC) - // initial empty line - s += fmt.Sprintf("%v %v\n", strings.Repeat(" ", lnPad), txtBarGood) - // offending lines - for l := e.StartL; l <= e.EndL; l++ { // "print fail" - line := lines[l-1] - before := "" - after := "" - start := 0 - if l == e.StartL { - before = line[:e.StartC] - line = line[e.StartC:] - start = e.StartC - } - if l == e.EndL { - idx := e.EndC - start + 1 - if idx >= len(line) { // because newline was erased - idx = len(line) - 1 - } - after = line[idx:] - line = line[:idx] - } - s += aurora.Red(fmt.Sprintf("%0*d | ", lnPad, l)).String() - s += fmt.Sprintf("%v%v%v", - aurora.BrightBlack(before), line, aurora.BrightBlack(after)) - } - // message - start := strings.IndexFunc(lines[e.EndL-1], func(r rune) bool { - return r != ' ' - }) - span := e.EndC - start + 1 - if e.StartL == e.EndL { - start = e.StartC - span = e.EndC - e.StartC - } - if span == 0 { - span = 1 - } - s += fmt.Sprintf("%v %v %v%v %v\n", - strings.Repeat(" ", lnPad), - txtBarGood, - strings.Repeat(" ", start), - aurora.Red(strings.Repeat("^", span)), - e.Msg) - } - return s -} - -type ErrorListener struct { - *antlr.DefaultErrorListener - Errors []CompileError -} - -func (l *ErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, startL, startC int, msg string, e antlr.RecognitionException) { - length := 1 - if token, ok := offendingSymbol.(antlr.Token); ok { - length = len(token.GetText()) - } - endL := startL - endC := startC + length - 1 // -1 so that end character is inside the offending token - l.Errors = append(l.Errors, CompileError{ - StartL: startL, - StartC: startC, - EndL: endL, - EndC: endC, - Msg: msg, - }) -} - -func LogicError(c antlr.ParserRuleContext, err error) *CompileError { - endC := c.GetStop().GetColumn() + len(c.GetStop().GetText()) - return &CompileError{ - StartL: c.GetStart().GetLine(), - StartC: c.GetStart().GetColumn(), - EndL: c.GetStop().GetLine(), - EndC: endC, - Msg: err.Error(), - } -} - -const InternalErrorMsg = "internal compiler error, please report to the issue tracker" - -func InternalError(c antlr.ParserRuleContext) *CompileError { - return &CompileError{ - StartL: c.GetStart().GetLine(), - StartC: c.GetStart().GetColumn(), - EndL: c.GetStop().GetLine(), - EndC: c.GetStop().GetColumn(), - Msg: InternalErrorMsg, - } -} diff --git a/components/ledger/internal/machine/script/compiler/error_test.go b/components/ledger/internal/machine/script/compiler/error_test.go deleted file mode 100644 index c9a5241dce..0000000000 --- a/components/ledger/internal/machine/script/compiler/error_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package compiler - -import ( - "testing" -) - -func TestEndCharacter(t *testing.T) { - src := ` - send [CREDIT 200] ( - source = @a - destination = { - 500% to @b - 50% to @c - } - ) - ` - - _, err := Compile(src) - if err == nil { - t.Fatal("expected error and got none") - } - - if _, ok := err.(*CompileErrorList); !ok { - t.Fatal("error had wrong type") - } - - compErr := err.(*CompileErrorList).Errors[0] - - if compErr.StartL != 5 { - t.Fatalf("start line was %v", compErr.StartL) - } - if compErr.StartC != 3 { - t.Fatalf("start character was %v", compErr.StartC) - } - if compErr.EndL != 5 { - t.Fatalf("end line was %v", compErr.EndL) - } - if compErr.EndC != 7 { - t.Fatalf("end character was %v", compErr.EndC) - } -} diff --git a/components/ledger/internal/machine/script/compiler/program.go b/components/ledger/internal/machine/script/compiler/program.go deleted file mode 100644 index 0d1692be8e..0000000000 --- a/components/ledger/internal/machine/script/compiler/program.go +++ /dev/null @@ -1,36 +0,0 @@ -package compiler - -import ( - "github.com/formancehq/ledger/internal/machine" - program2 "github.com/formancehq/ledger/internal/machine/vm/program" -) - -func (p *parseVisitor) AppendInstruction(instruction byte) { - p.instructions = append(p.instructions, instruction) -} - -func (p *parseVisitor) PushAddress(addr machine.Address) { - p.instructions = append(p.instructions, program2.OP_APUSH) - bytes := addr.ToBytes() - p.instructions = append(p.instructions, bytes...) -} - -func (p *parseVisitor) PushInteger(val machine.Number) error { - addr, err := p.AllocateResource(program2.Constant{Inner: val}) - if err != nil { - return err - } - p.instructions = append(p.instructions, program2.OP_APUSH) - bytes := addr.ToBytes() - p.instructions = append(p.instructions, bytes...) - return nil -} - -func (p *parseVisitor) Bump(n int64) error { - err := p.PushInteger(machine.NewNumber(n)) - if err != nil { - return err - } - p.instructions = append(p.instructions, program2.OP_BUMP) - return nil -} diff --git a/components/ledger/internal/machine/script/compiler/source.go b/components/ledger/internal/machine/script/compiler/source.go deleted file mode 100644 index a3d3dc0fb3..0000000000 --- a/components/ledger/internal/machine/script/compiler/source.go +++ /dev/null @@ -1,267 +0,0 @@ -package compiler - -import ( - "errors" - "fmt" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/ledger/internal/machine/script/parser" - "github.com/formancehq/ledger/internal/machine/vm/program" -) - -type FallbackAccount machine.Address - -// VisitValueAwareSource returns the resource addresses of all the accounts -func (p *parseVisitor) VisitValueAwareSource(c parser.IValueAwareSourceContext, pushAsset func(), monAddr *machine.Address) (map[machine.Address]struct{}, *CompileError) { - neededAccounts := map[machine.Address]struct{}{} - isAll := monAddr == nil - switch c := c.(type) { - case *parser.SrcContext: - accounts, _, unbounded, compErr := p.VisitSource(c.Source(), pushAsset, isAll) - if compErr != nil { - return nil, compErr - } - for k, v := range accounts { - neededAccounts[k] = v - } - if !isAll { - p.PushAddress(*monAddr) - err := p.TakeFromSource(unbounded) - if err != nil { - return nil, LogicError(c, err) - } - } - case *parser.SrcAllotmentContext: - if isAll { - return nil, LogicError(c, errors.New("cannot take all balance of an allotment source")) - } - p.PushAddress(*monAddr) - p.VisitAllotment(c.SourceAllotment(), c.SourceAllotment().GetPortions()) - p.AppendInstruction(program.OP_ALLOC) - - sources := c.SourceAllotment().GetSources() - n := len(sources) - for i := 0; i < n; i++ { - accounts, _, fallback, compErr := p.VisitSource(sources[i], pushAsset, isAll) - if compErr != nil { - return nil, compErr - } - for k, v := range accounts { - neededAccounts[k] = v - } - err := p.Bump(int64(i + 1)) - if err != nil { - return nil, LogicError(c, err) - } - err = p.TakeFromSource(fallback) - if err != nil { - return nil, LogicError(c, err) - } - } - err := p.PushInteger(machine.NewNumber(int64(n))) - if err != nil { - return nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } - return neededAccounts, nil -} - -func (p *parseVisitor) TakeFromSource(fallback *FallbackAccount) error { - if fallback == nil { - p.AppendInstruction(program.OP_TAKE) - err := p.Bump(1) - if err != nil { - return err - } - p.AppendInstruction(program.OP_REPAY) - return nil - } - - p.AppendInstruction(program.OP_TAKE_MAX) - err := p.Bump(1) - if err != nil { - return err - } - p.AppendInstruction(program.OP_REPAY) - p.PushAddress(machine.Address(*fallback)) - err = p.Bump(2) - if err != nil { - return err - } - p.AppendInstruction(program.OP_TAKE_ALWAYS) - err = p.PushInteger(machine.NewNumber(2)) - if err != nil { - return err - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - return nil -} - -func (p parseVisitor) isOverdraftUnbounded(overdraftCtx parser.ISourceAccountOverdraftContext) bool { - if overdraftCtx == nil { - return false - } - - switch overdraftCtx.(type) { - case *parser.SrcAccountOverdraftUnboundedContext: - return true - case *parser.SrcAccountOverdraftSpecificContext: - return false - - default: - // even though this branch should be unreachable, - // we default to `false` instead of panicking - // in order to have a more conservative behaviour - return false - } -} - -// VisitSource returns the resource addresses of all the accounts, -// the addresses of accounts already emptied, -// and possibly a fallback account if the source has an unbounded overdraft allowance or contains @world -func (p *parseVisitor) VisitSource(c parser.ISourceContext, pushAsset func(), isAll bool) (map[machine.Address]struct{}, map[machine.Address]struct{}, *FallbackAccount, *CompileError) { - neededAccounts := map[machine.Address]struct{}{} - emptiedAccounts := map[machine.Address]struct{}{} - var fallback *FallbackAccount - switch c := c.(type) { - case *parser.SrcAccountContext: - ty, accAddr, compErr := p.VisitExpr(c.SourceAccount().GetAccount(), true) - if compErr != nil { - return nil, nil, nil, compErr - } - if ty != machine.TypeAccount { - return nil, nil, nil, LogicError(c, errors.New("wrong type: expected account or allocation as destination")) - } - if p.isWorld(*accAddr) { - f := FallbackAccount(*accAddr) - fallback = &f - } - - overdraft := c.SourceAccount().GetOverdraft() - if overdraft == nil { - // no overdraft: use zero monetary - pushAsset() - err := p.PushInteger(machine.NewNumber(0)) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_MONETARY_NEW) - if p.isWorld(*accAddr) { - p.AppendInstruction(program.OP_TAKE_ALWAYS) - } else { - p.AppendInstruction(program.OP_TAKE_ALL) - } - } else { - if p.isWorld(*accAddr) { - return nil, nil, nil, LogicError(c, errors.New("@world is already set to an unbounded overdraft")) - } - switch c := overdraft.(type) { - case *parser.SrcAccountOverdraftSpecificContext: - ty, _, compErr := p.VisitExpr(c.GetSpecific(), true) - if compErr != nil { - return nil, nil, nil, compErr - } - if ty != machine.TypeMonetary { - return nil, nil, nil, LogicError(c, errors.New("wrong type: expected monetary")) - } - p.AppendInstruction(program.OP_TAKE_ALL) - case *parser.SrcAccountOverdraftUnboundedContext: - pushAsset() - err := p.PushInteger(machine.NewNumber(0)) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_MONETARY_NEW) - p.AppendInstruction(program.OP_TAKE_ALWAYS) - f := FallbackAccount(*accAddr) - fallback = &f - } - } - - isUnboundedOverdraft := p.isWorld(*accAddr) || p.isOverdraftUnbounded(overdraft) - if !isUnboundedOverdraft { - p.writeLockAccounts[*accAddr] = struct{}{} - neededAccounts[*accAddr] = struct{}{} - } - - emptiedAccounts[*accAddr] = struct{}{} - - if fallback != nil && isAll { - return nil, nil, nil, LogicError(c, errors.New("cannot take all balance of an unbounded source")) - } - - case *parser.SrcMaxedContext: - accounts, _, subsourceFallback, compErr := p.VisitSource(c.SourceMaxed().GetSrc(), pushAsset, false) - if compErr != nil { - return nil, nil, nil, compErr - } - ty, _, compErr := p.VisitExpr(c.SourceMaxed().GetMax(), true) - if compErr != nil { - return nil, nil, nil, compErr - } - if ty != machine.TypeMonetary { - return nil, nil, nil, LogicError(c, errors.New("wrong type: expected monetary as max")) - } - for k, v := range accounts { - neededAccounts[k] = v - } - p.AppendInstruction(program.OP_TAKE_MAX) - err := p.Bump(1) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_REPAY) - if subsourceFallback != nil { - p.PushAddress(machine.Address(*subsourceFallback)) - err := p.Bump(2) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_TAKE_ALWAYS) - err = p.PushInteger(machine.NewNumber(2)) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } else { - err := p.Bump(1) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_DELETE) - } - case *parser.SrcInOrderContext: - sources := c.SourceInOrder().GetSources() - n := len(sources) - for i := 0; i < n; i++ { - accounts, emptied, subsourceFallback, compErr := p.VisitSource(sources[i], pushAsset, isAll) - if compErr != nil { - return nil, nil, nil, compErr - } - fallback = subsourceFallback - if subsourceFallback != nil && i != n-1 { - return nil, nil, nil, LogicError(c, errors.New("an unbounded subsource can only be in last position")) - } - for k, v := range accounts { - neededAccounts[k] = v - } - for k, v := range emptied { - if _, ok := emptiedAccounts[k]; ok { - return nil, nil, nil, LogicError(sources[i], fmt.Errorf("%v is already empty at this stage", p.resources[k])) - } - emptiedAccounts[k] = v - } - } - err := p.PushInteger(machine.NewNumber(int64(n))) - if err != nil { - return nil, nil, nil, LogicError(c, err) - } - p.AppendInstruction(program.OP_FUNDING_ASSEMBLE) - } - for address := range neededAccounts { - p.sources[address] = struct{}{} - } - return neededAccounts, emptiedAccounts, fallback, nil -} diff --git a/components/ledger/internal/machine/script/generate.go b/components/ledger/internal/machine/script/generate.go deleted file mode 100644 index 9da7fbc6ac..0000000000 --- a/components/ledger/internal/machine/script/generate.go +++ /dev/null @@ -1,3 +0,0 @@ -package parser - -//go:generate ./generate.sh diff --git a/components/ledger/internal/machine/script/generate.sh b/components/ledger/internal/machine/script/generate.sh deleted file mode 100755 index 8378080da5..0000000000 --- a/components/ledger/internal/machine/script/generate.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail -if [[ "${TRACE-0}" == "1" ]]; then - set -o xtrace -fi - -cd "$(dirname "$0")" - -ANTLR_VERSION='4.10.1' - -main() { - curl --continue-at - https://www.antlr.org/download/antlr-$ANTLR_VERSION-complete.jar -O - java -Xmx500M -cp "./antlr-$ANTLR_VERSION-complete.jar" org.antlr.v4.Tool -Dlanguage=Go -o parser NumScript.g4 -} - -main "$@" diff --git a/components/ledger/internal/machine/script/parser/NumScript.interp b/components/ledger/internal/machine/script/parser/NumScript.interp deleted file mode 100644 index 49de522397..0000000000 --- a/components/ledger/internal/machine/script/parser/NumScript.interp +++ /dev/null @@ -1,128 +0,0 @@ -token literal names: -null -'*' -'allowing overdraft up to' -'allowing unbounded overdraft' -',' -null -null -null -null -'vars' -'meta' -'set_tx_meta' -'set_account_meta' -'print' -'fail' -'send' -'source' -'from' -'max' -'destination' -'to' -'allocate' -'+' -'-' -'(' -')' -'[' -']' -'{' -'}' -'=' -'account' -'asset' -'number' -'monetary' -'portion' -'string' -null -null -'remaining' -'kept' -'balance' -'save' -null -'%' -null -null -null - -token symbolic names: -null -null -null -null -null -NEWLINE -WHITESPACE -MULTILINE_COMMENT -LINE_COMMENT -VARS -META -SET_TX_META -SET_ACCOUNT_META -PRINT -FAIL -SEND -SOURCE -FROM -MAX -DESTINATION -TO -ALLOCATE -OP_ADD -OP_SUB -LPAREN -RPAREN -LBRACK -RBRACK -LBRACE -RBRACE -EQ -TY_ACCOUNT -TY_ASSET -TY_NUMBER -TY_MONETARY -TY_PORTION -TY_STRING -STRING -PORTION -REMAINING -KEPT -BALANCE -SAVE -NUMBER -PERCENT -VARIABLE_NAME -ACCOUNT -ASSET - -rule names: -monetary -monetaryAll -literal -variable -expression -allotmentPortion -destinationInOrder -destinationAllotment -keptOrDestination -destination -sourceAccountOverdraft -sourceAccount -sourceInOrder -sourceMaxed -source -sourceAllotment -valueAwareSource -statement -type_ -origin -varDecl -varListDecl -script - - -atn: -[4, 1, 47, 292, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 63, 8, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 3, 4, 70, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 75, 8, 4, 10, 4, 12, 4, 78, 9, 4, 1, 5, 1, 5, 1, 5, 3, 5, 83, 8, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 4, 6, 92, 8, 6, 11, 6, 12, 6, 93, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 4, 7, 107, 8, 7, 11, 7, 12, 7, 108, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 3, 8, 116, 8, 8, 1, 9, 1, 9, 1, 9, 3, 9, 121, 8, 9, 1, 10, 1, 10, 1, 10, 3, 10, 126, 8, 10, 1, 11, 1, 11, 3, 11, 130, 8, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 4, 12, 137, 8, 12, 11, 12, 12, 12, 138, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 3, 14, 151, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 4, 15, 160, 8, 15, 11, 15, 12, 15, 161, 1, 15, 1, 15, 1, 16, 1, 16, 3, 16, 168, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 175, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 200, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 220, 8, 17, 1, 17, 1, 17, 1, 17, 3, 17, 225, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 243, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 249, 8, 20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 4, 21, 256, 8, 21, 11, 21, 12, 21, 257, 4, 21, 260, 8, 21, 11, 21, 12, 21, 261, 1, 21, 1, 21, 1, 21, 1, 22, 5, 22, 268, 8, 22, 10, 22, 12, 22, 271, 9, 22, 1, 22, 3, 22, 274, 8, 22, 1, 22, 1, 22, 1, 22, 5, 22, 279, 8, 22, 10, 22, 12, 22, 282, 9, 22, 1, 22, 5, 22, 285, 8, 22, 10, 22, 12, 22, 288, 9, 22, 1, 22, 1, 22, 1, 22, 0, 1, 8, 23, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 0, 2, 1, 0, 22, 23, 1, 0, 31, 36, 305, 0, 46, 1, 0, 0, 0, 2, 51, 1, 0, 0, 0, 4, 62, 1, 0, 0, 0, 6, 64, 1, 0, 0, 0, 8, 69, 1, 0, 0, 0, 10, 82, 1, 0, 0, 0, 12, 84, 1, 0, 0, 0, 14, 100, 1, 0, 0, 0, 16, 115, 1, 0, 0, 0, 18, 120, 1, 0, 0, 0, 20, 125, 1, 0, 0, 0, 22, 127, 1, 0, 0, 0, 24, 131, 1, 0, 0, 0, 26, 142, 1, 0, 0, 0, 28, 150, 1, 0, 0, 0, 30, 152, 1, 0, 0, 0, 32, 167, 1, 0, 0, 0, 34, 224, 1, 0, 0, 0, 36, 226, 1, 0, 0, 0, 38, 242, 1, 0, 0, 0, 40, 244, 1, 0, 0, 0, 42, 250, 1, 0, 0, 0, 44, 269, 1, 0, 0, 0, 46, 47, 5, 26, 0, 0, 47, 48, 3, 8, 4, 0, 48, 49, 5, 43, 0, 0, 49, 50, 5, 27, 0, 0, 50, 1, 1, 0, 0, 0, 51, 52, 5, 26, 0, 0, 52, 53, 3, 8, 4, 0, 53, 54, 5, 1, 0, 0, 54, 55, 5, 27, 0, 0, 55, 3, 1, 0, 0, 0, 56, 63, 5, 46, 0, 0, 57, 63, 5, 47, 0, 0, 58, 63, 5, 43, 0, 0, 59, 63, 5, 37, 0, 0, 60, 63, 5, 38, 0, 0, 61, 63, 3, 0, 0, 0, 62, 56, 1, 0, 0, 0, 62, 57, 1, 0, 0, 0, 62, 58, 1, 0, 0, 0, 62, 59, 1, 0, 0, 0, 62, 60, 1, 0, 0, 0, 62, 61, 1, 0, 0, 0, 63, 5, 1, 0, 0, 0, 64, 65, 5, 45, 0, 0, 65, 7, 1, 0, 0, 0, 66, 67, 6, 4, -1, 0, 67, 70, 3, 4, 2, 0, 68, 70, 3, 6, 3, 0, 69, 66, 1, 0, 0, 0, 69, 68, 1, 0, 0, 0, 70, 76, 1, 0, 0, 0, 71, 72, 10, 3, 0, 0, 72, 73, 7, 0, 0, 0, 73, 75, 3, 8, 4, 4, 74, 71, 1, 0, 0, 0, 75, 78, 1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 76, 77, 1, 0, 0, 0, 77, 9, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 79, 83, 5, 38, 0, 0, 80, 83, 3, 6, 3, 0, 81, 83, 5, 39, 0, 0, 82, 79, 1, 0, 0, 0, 82, 80, 1, 0, 0, 0, 82, 81, 1, 0, 0, 0, 83, 11, 1, 0, 0, 0, 84, 85, 5, 28, 0, 0, 85, 91, 5, 5, 0, 0, 86, 87, 5, 18, 0, 0, 87, 88, 3, 8, 4, 0, 88, 89, 3, 16, 8, 0, 89, 90, 5, 5, 0, 0, 90, 92, 1, 0, 0, 0, 91, 86, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, 91, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 95, 1, 0, 0, 0, 95, 96, 5, 39, 0, 0, 96, 97, 3, 16, 8, 0, 97, 98, 5, 5, 0, 0, 98, 99, 5, 29, 0, 0, 99, 13, 1, 0, 0, 0, 100, 101, 5, 28, 0, 0, 101, 106, 5, 5, 0, 0, 102, 103, 3, 10, 5, 0, 103, 104, 3, 16, 8, 0, 104, 105, 5, 5, 0, 0, 105, 107, 1, 0, 0, 0, 106, 102, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 106, 1, 0, 0, 0, 108, 109, 1, 0, 0, 0, 109, 110, 1, 0, 0, 0, 110, 111, 5, 29, 0, 0, 111, 15, 1, 0, 0, 0, 112, 113, 5, 20, 0, 0, 113, 116, 3, 18, 9, 0, 114, 116, 5, 40, 0, 0, 115, 112, 1, 0, 0, 0, 115, 114, 1, 0, 0, 0, 116, 17, 1, 0, 0, 0, 117, 121, 3, 8, 4, 0, 118, 121, 3, 12, 6, 0, 119, 121, 3, 14, 7, 0, 120, 117, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 120, 119, 1, 0, 0, 0, 121, 19, 1, 0, 0, 0, 122, 123, 5, 2, 0, 0, 123, 126, 3, 8, 4, 0, 124, 126, 5, 3, 0, 0, 125, 122, 1, 0, 0, 0, 125, 124, 1, 0, 0, 0, 126, 21, 1, 0, 0, 0, 127, 129, 3, 8, 4, 0, 128, 130, 3, 20, 10, 0, 129, 128, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 23, 1, 0, 0, 0, 131, 132, 5, 28, 0, 0, 132, 136, 5, 5, 0, 0, 133, 134, 3, 28, 14, 0, 134, 135, 5, 5, 0, 0, 135, 137, 1, 0, 0, 0, 136, 133, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 136, 1, 0, 0, 0, 138, 139, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 141, 5, 29, 0, 0, 141, 25, 1, 0, 0, 0, 142, 143, 5, 18, 0, 0, 143, 144, 3, 8, 4, 0, 144, 145, 5, 17, 0, 0, 145, 146, 3, 28, 14, 0, 146, 27, 1, 0, 0, 0, 147, 151, 3, 22, 11, 0, 148, 151, 3, 26, 13, 0, 149, 151, 3, 24, 12, 0, 150, 147, 1, 0, 0, 0, 150, 148, 1, 0, 0, 0, 150, 149, 1, 0, 0, 0, 151, 29, 1, 0, 0, 0, 152, 153, 5, 28, 0, 0, 153, 159, 5, 5, 0, 0, 154, 155, 3, 10, 5, 0, 155, 156, 5, 17, 0, 0, 156, 157, 3, 28, 14, 0, 157, 158, 5, 5, 0, 0, 158, 160, 1, 0, 0, 0, 159, 154, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 5, 29, 0, 0, 164, 31, 1, 0, 0, 0, 165, 168, 3, 28, 14, 0, 166, 168, 3, 30, 15, 0, 167, 165, 1, 0, 0, 0, 167, 166, 1, 0, 0, 0, 168, 33, 1, 0, 0, 0, 169, 170, 5, 13, 0, 0, 170, 225, 3, 8, 4, 0, 171, 174, 5, 42, 0, 0, 172, 175, 3, 8, 4, 0, 173, 175, 3, 2, 1, 0, 174, 172, 1, 0, 0, 0, 174, 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 17, 0, 0, 177, 178, 3, 8, 4, 0, 178, 225, 1, 0, 0, 0, 179, 180, 5, 11, 0, 0, 180, 181, 5, 24, 0, 0, 181, 182, 5, 37, 0, 0, 182, 183, 5, 4, 0, 0, 183, 184, 3, 8, 4, 0, 184, 185, 5, 25, 0, 0, 185, 225, 1, 0, 0, 0, 186, 187, 5, 12, 0, 0, 187, 188, 5, 24, 0, 0, 188, 189, 3, 8, 4, 0, 189, 190, 5, 4, 0, 0, 190, 191, 5, 37, 0, 0, 191, 192, 5, 4, 0, 0, 192, 193, 3, 8, 4, 0, 193, 194, 5, 25, 0, 0, 194, 225, 1, 0, 0, 0, 195, 225, 5, 14, 0, 0, 196, 199, 5, 15, 0, 0, 197, 200, 3, 8, 4, 0, 198, 200, 3, 2, 1, 0, 199, 197, 1, 0, 0, 0, 199, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 5, 24, 0, 0, 202, 219, 5, 5, 0, 0, 203, 204, 5, 16, 0, 0, 204, 205, 5, 30, 0, 0, 205, 206, 3, 32, 16, 0, 206, 207, 5, 5, 0, 0, 207, 208, 5, 19, 0, 0, 208, 209, 5, 30, 0, 0, 209, 210, 3, 18, 9, 0, 210, 220, 1, 0, 0, 0, 211, 212, 5, 19, 0, 0, 212, 213, 5, 30, 0, 0, 213, 214, 3, 18, 9, 0, 214, 215, 5, 5, 0, 0, 215, 216, 5, 16, 0, 0, 216, 217, 5, 30, 0, 0, 217, 218, 3, 32, 16, 0, 218, 220, 1, 0, 0, 0, 219, 203, 1, 0, 0, 0, 219, 211, 1, 0, 0, 0, 220, 221, 1, 0, 0, 0, 221, 222, 5, 5, 0, 0, 222, 223, 5, 25, 0, 0, 223, 225, 1, 0, 0, 0, 224, 169, 1, 0, 0, 0, 224, 171, 1, 0, 0, 0, 224, 179, 1, 0, 0, 0, 224, 186, 1, 0, 0, 0, 224, 195, 1, 0, 0, 0, 224, 196, 1, 0, 0, 0, 225, 35, 1, 0, 0, 0, 226, 227, 7, 1, 0, 0, 227, 37, 1, 0, 0, 0, 228, 229, 5, 10, 0, 0, 229, 230, 5, 24, 0, 0, 230, 231, 3, 8, 4, 0, 231, 232, 5, 4, 0, 0, 232, 233, 5, 37, 0, 0, 233, 234, 5, 25, 0, 0, 234, 243, 1, 0, 0, 0, 235, 236, 5, 41, 0, 0, 236, 237, 5, 24, 0, 0, 237, 238, 3, 8, 4, 0, 238, 239, 5, 4, 0, 0, 239, 240, 3, 8, 4, 0, 240, 241, 5, 25, 0, 0, 241, 243, 1, 0, 0, 0, 242, 228, 1, 0, 0, 0, 242, 235, 1, 0, 0, 0, 243, 39, 1, 0, 0, 0, 244, 245, 3, 36, 18, 0, 245, 248, 3, 6, 3, 0, 246, 247, 5, 30, 0, 0, 247, 249, 3, 38, 19, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 41, 1, 0, 0, 0, 250, 251, 5, 9, 0, 0, 251, 252, 5, 28, 0, 0, 252, 259, 5, 5, 0, 0, 253, 255, 3, 40, 20, 0, 254, 256, 5, 5, 0, 0, 255, 254, 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, 0, 0, 258, 260, 1, 0, 0, 0, 259, 253, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 264, 5, 29, 0, 0, 264, 265, 5, 5, 0, 0, 265, 43, 1, 0, 0, 0, 266, 268, 5, 5, 0, 0, 267, 266, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 273, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 272, 274, 3, 42, 21, 0, 273, 272, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 275, 1, 0, 0, 0, 275, 280, 3, 34, 17, 0, 276, 277, 5, 5, 0, 0, 277, 279, 3, 34, 17, 0, 278, 276, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 286, 1, 0, 0, 0, 282, 280, 1, 0, 0, 0, 283, 285, 5, 5, 0, 0, 284, 283, 1, 0, 0, 0, 285, 288, 1, 0, 0, 0, 286, 284, 1, 0, 0, 0, 286, 287, 1, 0, 0, 0, 287, 289, 1, 0, 0, 0, 288, 286, 1, 0, 0, 0, 289, 290, 5, 0, 0, 1, 290, 45, 1, 0, 0, 0, 26, 62, 69, 76, 82, 93, 108, 115, 120, 125, 129, 138, 150, 161, 167, 174, 199, 219, 224, 242, 248, 257, 261, 269, 273, 280, 286] \ No newline at end of file diff --git a/components/ledger/internal/machine/script/parser/NumScript.tokens b/components/ledger/internal/machine/script/parser/NumScript.tokens deleted file mode 100644 index cb7dd35a28..0000000000 --- a/components/ledger/internal/machine/script/parser/NumScript.tokens +++ /dev/null @@ -1,84 +0,0 @@ -T__0=1 -T__1=2 -T__2=3 -T__3=4 -NEWLINE=5 -WHITESPACE=6 -MULTILINE_COMMENT=7 -LINE_COMMENT=8 -VARS=9 -META=10 -SET_TX_META=11 -SET_ACCOUNT_META=12 -PRINT=13 -FAIL=14 -SEND=15 -SOURCE=16 -FROM=17 -MAX=18 -DESTINATION=19 -TO=20 -ALLOCATE=21 -OP_ADD=22 -OP_SUB=23 -LPAREN=24 -RPAREN=25 -LBRACK=26 -RBRACK=27 -LBRACE=28 -RBRACE=29 -EQ=30 -TY_ACCOUNT=31 -TY_ASSET=32 -TY_NUMBER=33 -TY_MONETARY=34 -TY_PORTION=35 -TY_STRING=36 -STRING=37 -PORTION=38 -REMAINING=39 -KEPT=40 -BALANCE=41 -SAVE=42 -NUMBER=43 -PERCENT=44 -VARIABLE_NAME=45 -ACCOUNT=46 -ASSET=47 -'*'=1 -'allowing overdraft up to'=2 -'allowing unbounded overdraft'=3 -','=4 -'vars'=9 -'meta'=10 -'set_tx_meta'=11 -'set_account_meta'=12 -'print'=13 -'fail'=14 -'send'=15 -'source'=16 -'from'=17 -'max'=18 -'destination'=19 -'to'=20 -'allocate'=21 -'+'=22 -'-'=23 -'('=24 -')'=25 -'['=26 -']'=27 -'{'=28 -'}'=29 -'='=30 -'account'=31 -'asset'=32 -'number'=33 -'monetary'=34 -'portion'=35 -'string'=36 -'remaining'=39 -'kept'=40 -'balance'=41 -'save'=42 -'%'=44 diff --git a/components/ledger/internal/machine/script/parser/NumScriptLexer.interp b/components/ledger/internal/machine/script/parser/NumScriptLexer.interp deleted file mode 100644 index 1bac45f4fd..0000000000 --- a/components/ledger/internal/machine/script/parser/NumScriptLexer.interp +++ /dev/null @@ -1,158 +0,0 @@ -token literal names: -null -'*' -'allowing overdraft up to' -'allowing unbounded overdraft' -',' -null -null -null -null -'vars' -'meta' -'set_tx_meta' -'set_account_meta' -'print' -'fail' -'send' -'source' -'from' -'max' -'destination' -'to' -'allocate' -'+' -'-' -'(' -')' -'[' -']' -'{' -'}' -'=' -'account' -'asset' -'number' -'monetary' -'portion' -'string' -null -null -'remaining' -'kept' -'balance' -'save' -null -'%' -null -null -null - -token symbolic names: -null -null -null -null -null -NEWLINE -WHITESPACE -MULTILINE_COMMENT -LINE_COMMENT -VARS -META -SET_TX_META -SET_ACCOUNT_META -PRINT -FAIL -SEND -SOURCE -FROM -MAX -DESTINATION -TO -ALLOCATE -OP_ADD -OP_SUB -LPAREN -RPAREN -LBRACK -RBRACK -LBRACE -RBRACE -EQ -TY_ACCOUNT -TY_ASSET -TY_NUMBER -TY_MONETARY -TY_PORTION -TY_STRING -STRING -PORTION -REMAINING -KEPT -BALANCE -SAVE -NUMBER -PERCENT -VARIABLE_NAME -ACCOUNT -ASSET - -rule names: -T__0 -T__1 -T__2 -T__3 -NEWLINE -WHITESPACE -MULTILINE_COMMENT -LINE_COMMENT -VARS -META -SET_TX_META -SET_ACCOUNT_META -PRINT -FAIL -SEND -SOURCE -FROM -MAX -DESTINATION -TO -ALLOCATE -OP_ADD -OP_SUB -LPAREN -RPAREN -LBRACK -RBRACK -LBRACE -RBRACE -EQ -TY_ACCOUNT -TY_ASSET -TY_NUMBER -TY_MONETARY -TY_PORTION -TY_STRING -STRING -PORTION -REMAINING -KEPT -BALANCE -SAVE -NUMBER -PERCENT -VARIABLE_NAME -ACCOUNT -ASSET - -channel names: -DEFAULT_TOKEN_CHANNEL -HIDDEN - -mode names: -DEFAULT_MODE - -atn: -[4, 0, 47, 464, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 4, 4, 155, 8, 4, 11, 4, 12, 4, 156, 1, 5, 4, 5, 160, 8, 5, 11, 5, 12, 5, 161, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 171, 8, 6, 10, 6, 12, 6, 174, 9, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 185, 8, 7, 10, 7, 12, 7, 188, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 356, 8, 36, 10, 36, 12, 36, 359, 9, 36, 1, 36, 1, 36, 1, 37, 4, 37, 364, 8, 37, 11, 37, 12, 37, 365, 1, 37, 3, 37, 369, 8, 37, 1, 37, 1, 37, 3, 37, 373, 8, 37, 1, 37, 4, 37, 376, 8, 37, 11, 37, 12, 37, 377, 1, 37, 4, 37, 381, 8, 37, 11, 37, 12, 37, 382, 1, 37, 1, 37, 4, 37, 387, 8, 37, 11, 37, 12, 37, 388, 3, 37, 391, 8, 37, 1, 37, 3, 37, 394, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 4, 42, 425, 8, 42, 11, 42, 12, 42, 426, 1, 43, 1, 43, 1, 44, 1, 44, 4, 44, 433, 8, 44, 11, 44, 12, 44, 434, 1, 44, 5, 44, 438, 8, 44, 10, 44, 12, 44, 441, 9, 44, 1, 45, 1, 45, 4, 45, 445, 8, 45, 11, 45, 12, 45, 446, 1, 45, 1, 45, 4, 45, 451, 8, 45, 11, 45, 12, 45, 452, 5, 45, 455, 8, 45, 10, 45, 12, 45, 458, 9, 45, 1, 46, 4, 46, 461, 8, 46, 11, 46, 12, 46, 462, 2, 172, 186, 0, 47, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 1, 0, 9, 2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 3, 0, 10, 10, 13, 13, 34, 34, 1, 0, 48, 57, 1, 0, 32, 32, 2, 0, 95, 95, 97, 122, 3, 0, 48, 57, 95, 95, 97, 122, 5, 0, 45, 45, 48, 57, 65, 90, 95, 95, 97, 122, 2, 0, 47, 57, 65, 90, 485, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 3, 97, 1, 0, 0, 0, 5, 122, 1, 0, 0, 0, 7, 151, 1, 0, 0, 0, 9, 154, 1, 0, 0, 0, 11, 159, 1, 0, 0, 0, 13, 165, 1, 0, 0, 0, 15, 180, 1, 0, 0, 0, 17, 193, 1, 0, 0, 0, 19, 198, 1, 0, 0, 0, 21, 203, 1, 0, 0, 0, 23, 215, 1, 0, 0, 0, 25, 232, 1, 0, 0, 0, 27, 238, 1, 0, 0, 0, 29, 243, 1, 0, 0, 0, 31, 248, 1, 0, 0, 0, 33, 255, 1, 0, 0, 0, 35, 260, 1, 0, 0, 0, 37, 264, 1, 0, 0, 0, 39, 276, 1, 0, 0, 0, 41, 279, 1, 0, 0, 0, 43, 288, 1, 0, 0, 0, 45, 290, 1, 0, 0, 0, 47, 292, 1, 0, 0, 0, 49, 294, 1, 0, 0, 0, 51, 296, 1, 0, 0, 0, 53, 298, 1, 0, 0, 0, 55, 300, 1, 0, 0, 0, 57, 302, 1, 0, 0, 0, 59, 304, 1, 0, 0, 0, 61, 306, 1, 0, 0, 0, 63, 314, 1, 0, 0, 0, 65, 320, 1, 0, 0, 0, 67, 327, 1, 0, 0, 0, 69, 336, 1, 0, 0, 0, 71, 344, 1, 0, 0, 0, 73, 351, 1, 0, 0, 0, 75, 393, 1, 0, 0, 0, 77, 395, 1, 0, 0, 0, 79, 405, 1, 0, 0, 0, 81, 410, 1, 0, 0, 0, 83, 418, 1, 0, 0, 0, 85, 424, 1, 0, 0, 0, 87, 428, 1, 0, 0, 0, 89, 430, 1, 0, 0, 0, 91, 442, 1, 0, 0, 0, 93, 460, 1, 0, 0, 0, 95, 96, 5, 42, 0, 0, 96, 2, 1, 0, 0, 0, 97, 98, 5, 97, 0, 0, 98, 99, 5, 108, 0, 0, 99, 100, 5, 108, 0, 0, 100, 101, 5, 111, 0, 0, 101, 102, 5, 119, 0, 0, 102, 103, 5, 105, 0, 0, 103, 104, 5, 110, 0, 0, 104, 105, 5, 103, 0, 0, 105, 106, 5, 32, 0, 0, 106, 107, 5, 111, 0, 0, 107, 108, 5, 118, 0, 0, 108, 109, 5, 101, 0, 0, 109, 110, 5, 114, 0, 0, 110, 111, 5, 100, 0, 0, 111, 112, 5, 114, 0, 0, 112, 113, 5, 97, 0, 0, 113, 114, 5, 102, 0, 0, 114, 115, 5, 116, 0, 0, 115, 116, 5, 32, 0, 0, 116, 117, 5, 117, 0, 0, 117, 118, 5, 112, 0, 0, 118, 119, 5, 32, 0, 0, 119, 120, 5, 116, 0, 0, 120, 121, 5, 111, 0, 0, 121, 4, 1, 0, 0, 0, 122, 123, 5, 97, 0, 0, 123, 124, 5, 108, 0, 0, 124, 125, 5, 108, 0, 0, 125, 126, 5, 111, 0, 0, 126, 127, 5, 119, 0, 0, 127, 128, 5, 105, 0, 0, 128, 129, 5, 110, 0, 0, 129, 130, 5, 103, 0, 0, 130, 131, 5, 32, 0, 0, 131, 132, 5, 117, 0, 0, 132, 133, 5, 110, 0, 0, 133, 134, 5, 98, 0, 0, 134, 135, 5, 111, 0, 0, 135, 136, 5, 117, 0, 0, 136, 137, 5, 110, 0, 0, 137, 138, 5, 100, 0, 0, 138, 139, 5, 101, 0, 0, 139, 140, 5, 100, 0, 0, 140, 141, 5, 32, 0, 0, 141, 142, 5, 111, 0, 0, 142, 143, 5, 118, 0, 0, 143, 144, 5, 101, 0, 0, 144, 145, 5, 114, 0, 0, 145, 146, 5, 100, 0, 0, 146, 147, 5, 114, 0, 0, 147, 148, 5, 97, 0, 0, 148, 149, 5, 102, 0, 0, 149, 150, 5, 116, 0, 0, 150, 6, 1, 0, 0, 0, 151, 152, 5, 44, 0, 0, 152, 8, 1, 0, 0, 0, 153, 155, 7, 0, 0, 0, 154, 153, 1, 0, 0, 0, 155, 156, 1, 0, 0, 0, 156, 154, 1, 0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 10, 1, 0, 0, 0, 158, 160, 7, 1, 0, 0, 159, 158, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 159, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 6, 5, 0, 0, 164, 12, 1, 0, 0, 0, 165, 166, 5, 47, 0, 0, 166, 167, 5, 42, 0, 0, 167, 172, 1, 0, 0, 0, 168, 171, 3, 13, 6, 0, 169, 171, 9, 0, 0, 0, 170, 168, 1, 0, 0, 0, 170, 169, 1, 0, 0, 0, 171, 174, 1, 0, 0, 0, 172, 173, 1, 0, 0, 0, 172, 170, 1, 0, 0, 0, 173, 175, 1, 0, 0, 0, 174, 172, 1, 0, 0, 0, 175, 176, 5, 42, 0, 0, 176, 177, 5, 47, 0, 0, 177, 178, 1, 0, 0, 0, 178, 179, 6, 6, 0, 0, 179, 14, 1, 0, 0, 0, 180, 181, 5, 47, 0, 0, 181, 182, 5, 47, 0, 0, 182, 186, 1, 0, 0, 0, 183, 185, 9, 0, 0, 0, 184, 183, 1, 0, 0, 0, 185, 188, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 186, 184, 1, 0, 0, 0, 187, 189, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0, 189, 190, 3, 9, 4, 0, 190, 191, 1, 0, 0, 0, 191, 192, 6, 7, 0, 0, 192, 16, 1, 0, 0, 0, 193, 194, 5, 118, 0, 0, 194, 195, 5, 97, 0, 0, 195, 196, 5, 114, 0, 0, 196, 197, 5, 115, 0, 0, 197, 18, 1, 0, 0, 0, 198, 199, 5, 109, 0, 0, 199, 200, 5, 101, 0, 0, 200, 201, 5, 116, 0, 0, 201, 202, 5, 97, 0, 0, 202, 20, 1, 0, 0, 0, 203, 204, 5, 115, 0, 0, 204, 205, 5, 101, 0, 0, 205, 206, 5, 116, 0, 0, 206, 207, 5, 95, 0, 0, 207, 208, 5, 116, 0, 0, 208, 209, 5, 120, 0, 0, 209, 210, 5, 95, 0, 0, 210, 211, 5, 109, 0, 0, 211, 212, 5, 101, 0, 0, 212, 213, 5, 116, 0, 0, 213, 214, 5, 97, 0, 0, 214, 22, 1, 0, 0, 0, 215, 216, 5, 115, 0, 0, 216, 217, 5, 101, 0, 0, 217, 218, 5, 116, 0, 0, 218, 219, 5, 95, 0, 0, 219, 220, 5, 97, 0, 0, 220, 221, 5, 99, 0, 0, 221, 222, 5, 99, 0, 0, 222, 223, 5, 111, 0, 0, 223, 224, 5, 117, 0, 0, 224, 225, 5, 110, 0, 0, 225, 226, 5, 116, 0, 0, 226, 227, 5, 95, 0, 0, 227, 228, 5, 109, 0, 0, 228, 229, 5, 101, 0, 0, 229, 230, 5, 116, 0, 0, 230, 231, 5, 97, 0, 0, 231, 24, 1, 0, 0, 0, 232, 233, 5, 112, 0, 0, 233, 234, 5, 114, 0, 0, 234, 235, 5, 105, 0, 0, 235, 236, 5, 110, 0, 0, 236, 237, 5, 116, 0, 0, 237, 26, 1, 0, 0, 0, 238, 239, 5, 102, 0, 0, 239, 240, 5, 97, 0, 0, 240, 241, 5, 105, 0, 0, 241, 242, 5, 108, 0, 0, 242, 28, 1, 0, 0, 0, 243, 244, 5, 115, 0, 0, 244, 245, 5, 101, 0, 0, 245, 246, 5, 110, 0, 0, 246, 247, 5, 100, 0, 0, 247, 30, 1, 0, 0, 0, 248, 249, 5, 115, 0, 0, 249, 250, 5, 111, 0, 0, 250, 251, 5, 117, 0, 0, 251, 252, 5, 114, 0, 0, 252, 253, 5, 99, 0, 0, 253, 254, 5, 101, 0, 0, 254, 32, 1, 0, 0, 0, 255, 256, 5, 102, 0, 0, 256, 257, 5, 114, 0, 0, 257, 258, 5, 111, 0, 0, 258, 259, 5, 109, 0, 0, 259, 34, 1, 0, 0, 0, 260, 261, 5, 109, 0, 0, 261, 262, 5, 97, 0, 0, 262, 263, 5, 120, 0, 0, 263, 36, 1, 0, 0, 0, 264, 265, 5, 100, 0, 0, 265, 266, 5, 101, 0, 0, 266, 267, 5, 115, 0, 0, 267, 268, 5, 116, 0, 0, 268, 269, 5, 105, 0, 0, 269, 270, 5, 110, 0, 0, 270, 271, 5, 97, 0, 0, 271, 272, 5, 116, 0, 0, 272, 273, 5, 105, 0, 0, 273, 274, 5, 111, 0, 0, 274, 275, 5, 110, 0, 0, 275, 38, 1, 0, 0, 0, 276, 277, 5, 116, 0, 0, 277, 278, 5, 111, 0, 0, 278, 40, 1, 0, 0, 0, 279, 280, 5, 97, 0, 0, 280, 281, 5, 108, 0, 0, 281, 282, 5, 108, 0, 0, 282, 283, 5, 111, 0, 0, 283, 284, 5, 99, 0, 0, 284, 285, 5, 97, 0, 0, 285, 286, 5, 116, 0, 0, 286, 287, 5, 101, 0, 0, 287, 42, 1, 0, 0, 0, 288, 289, 5, 43, 0, 0, 289, 44, 1, 0, 0, 0, 290, 291, 5, 45, 0, 0, 291, 46, 1, 0, 0, 0, 292, 293, 5, 40, 0, 0, 293, 48, 1, 0, 0, 0, 294, 295, 5, 41, 0, 0, 295, 50, 1, 0, 0, 0, 296, 297, 5, 91, 0, 0, 297, 52, 1, 0, 0, 0, 298, 299, 5, 93, 0, 0, 299, 54, 1, 0, 0, 0, 300, 301, 5, 123, 0, 0, 301, 56, 1, 0, 0, 0, 302, 303, 5, 125, 0, 0, 303, 58, 1, 0, 0, 0, 304, 305, 5, 61, 0, 0, 305, 60, 1, 0, 0, 0, 306, 307, 5, 97, 0, 0, 307, 308, 5, 99, 0, 0, 308, 309, 5, 99, 0, 0, 309, 310, 5, 111, 0, 0, 310, 311, 5, 117, 0, 0, 311, 312, 5, 110, 0, 0, 312, 313, 5, 116, 0, 0, 313, 62, 1, 0, 0, 0, 314, 315, 5, 97, 0, 0, 315, 316, 5, 115, 0, 0, 316, 317, 5, 115, 0, 0, 317, 318, 5, 101, 0, 0, 318, 319, 5, 116, 0, 0, 319, 64, 1, 0, 0, 0, 320, 321, 5, 110, 0, 0, 321, 322, 5, 117, 0, 0, 322, 323, 5, 109, 0, 0, 323, 324, 5, 98, 0, 0, 324, 325, 5, 101, 0, 0, 325, 326, 5, 114, 0, 0, 326, 66, 1, 0, 0, 0, 327, 328, 5, 109, 0, 0, 328, 329, 5, 111, 0, 0, 329, 330, 5, 110, 0, 0, 330, 331, 5, 101, 0, 0, 331, 332, 5, 116, 0, 0, 332, 333, 5, 97, 0, 0, 333, 334, 5, 114, 0, 0, 334, 335, 5, 121, 0, 0, 335, 68, 1, 0, 0, 0, 336, 337, 5, 112, 0, 0, 337, 338, 5, 111, 0, 0, 338, 339, 5, 114, 0, 0, 339, 340, 5, 116, 0, 0, 340, 341, 5, 105, 0, 0, 341, 342, 5, 111, 0, 0, 342, 343, 5, 110, 0, 0, 343, 70, 1, 0, 0, 0, 344, 345, 5, 115, 0, 0, 345, 346, 5, 116, 0, 0, 346, 347, 5, 114, 0, 0, 347, 348, 5, 105, 0, 0, 348, 349, 5, 110, 0, 0, 349, 350, 5, 103, 0, 0, 350, 72, 1, 0, 0, 0, 351, 357, 5, 34, 0, 0, 352, 353, 5, 92, 0, 0, 353, 356, 5, 34, 0, 0, 354, 356, 8, 2, 0, 0, 355, 352, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, 355, 1, 0, 0, 0, 357, 358, 1, 0, 0, 0, 358, 360, 1, 0, 0, 0, 359, 357, 1, 0, 0, 0, 360, 361, 5, 34, 0, 0, 361, 74, 1, 0, 0, 0, 362, 364, 7, 3, 0, 0, 363, 362, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 363, 1, 0, 0, 0, 365, 366, 1, 0, 0, 0, 366, 368, 1, 0, 0, 0, 367, 369, 7, 4, 0, 0, 368, 367, 1, 0, 0, 0, 368, 369, 1, 0, 0, 0, 369, 370, 1, 0, 0, 0, 370, 372, 5, 47, 0, 0, 371, 373, 7, 4, 0, 0, 372, 371, 1, 0, 0, 0, 372, 373, 1, 0, 0, 0, 373, 375, 1, 0, 0, 0, 374, 376, 7, 3, 0, 0, 375, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 377, 378, 1, 0, 0, 0, 378, 394, 1, 0, 0, 0, 379, 381, 7, 3, 0, 0, 380, 379, 1, 0, 0, 0, 381, 382, 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 390, 1, 0, 0, 0, 384, 386, 5, 46, 0, 0, 385, 387, 7, 3, 0, 0, 386, 385, 1, 0, 0, 0, 387, 388, 1, 0, 0, 0, 388, 386, 1, 0, 0, 0, 388, 389, 1, 0, 0, 0, 389, 391, 1, 0, 0, 0, 390, 384, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 392, 1, 0, 0, 0, 392, 394, 5, 37, 0, 0, 393, 363, 1, 0, 0, 0, 393, 380, 1, 0, 0, 0, 394, 76, 1, 0, 0, 0, 395, 396, 5, 114, 0, 0, 396, 397, 5, 101, 0, 0, 397, 398, 5, 109, 0, 0, 398, 399, 5, 97, 0, 0, 399, 400, 5, 105, 0, 0, 400, 401, 5, 110, 0, 0, 401, 402, 5, 105, 0, 0, 402, 403, 5, 110, 0, 0, 403, 404, 5, 103, 0, 0, 404, 78, 1, 0, 0, 0, 405, 406, 5, 107, 0, 0, 406, 407, 5, 101, 0, 0, 407, 408, 5, 112, 0, 0, 408, 409, 5, 116, 0, 0, 409, 80, 1, 0, 0, 0, 410, 411, 5, 98, 0, 0, 411, 412, 5, 97, 0, 0, 412, 413, 5, 108, 0, 0, 413, 414, 5, 97, 0, 0, 414, 415, 5, 110, 0, 0, 415, 416, 5, 99, 0, 0, 416, 417, 5, 101, 0, 0, 417, 82, 1, 0, 0, 0, 418, 419, 5, 115, 0, 0, 419, 420, 5, 97, 0, 0, 420, 421, 5, 118, 0, 0, 421, 422, 5, 101, 0, 0, 422, 84, 1, 0, 0, 0, 423, 425, 7, 3, 0, 0, 424, 423, 1, 0, 0, 0, 425, 426, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, 427, 86, 1, 0, 0, 0, 428, 429, 5, 37, 0, 0, 429, 88, 1, 0, 0, 0, 430, 432, 5, 36, 0, 0, 431, 433, 7, 5, 0, 0, 432, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 439, 1, 0, 0, 0, 436, 438, 7, 6, 0, 0, 437, 436, 1, 0, 0, 0, 438, 441, 1, 0, 0, 0, 439, 437, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 90, 1, 0, 0, 0, 441, 439, 1, 0, 0, 0, 442, 444, 5, 64, 0, 0, 443, 445, 7, 7, 0, 0, 444, 443, 1, 0, 0, 0, 445, 446, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 456, 1, 0, 0, 0, 448, 450, 5, 58, 0, 0, 449, 451, 7, 7, 0, 0, 450, 449, 1, 0, 0, 0, 451, 452, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, 0, 0, 453, 455, 1, 0, 0, 0, 454, 448, 1, 0, 0, 0, 455, 458, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 456, 457, 1, 0, 0, 0, 457, 92, 1, 0, 0, 0, 458, 456, 1, 0, 0, 0, 459, 461, 7, 8, 0, 0, 460, 459, 1, 0, 0, 0, 461, 462, 1, 0, 0, 0, 462, 460, 1, 0, 0, 0, 462, 463, 1, 0, 0, 0, 463, 94, 1, 0, 0, 0, 23, 0, 156, 161, 170, 172, 186, 355, 357, 365, 368, 372, 377, 382, 388, 390, 393, 426, 434, 439, 446, 452, 456, 462, 1, 6, 0, 0] \ No newline at end of file diff --git a/components/ledger/internal/machine/script/parser/NumScriptLexer.tokens b/components/ledger/internal/machine/script/parser/NumScriptLexer.tokens deleted file mode 100644 index cb7dd35a28..0000000000 --- a/components/ledger/internal/machine/script/parser/NumScriptLexer.tokens +++ /dev/null @@ -1,84 +0,0 @@ -T__0=1 -T__1=2 -T__2=3 -T__3=4 -NEWLINE=5 -WHITESPACE=6 -MULTILINE_COMMENT=7 -LINE_COMMENT=8 -VARS=9 -META=10 -SET_TX_META=11 -SET_ACCOUNT_META=12 -PRINT=13 -FAIL=14 -SEND=15 -SOURCE=16 -FROM=17 -MAX=18 -DESTINATION=19 -TO=20 -ALLOCATE=21 -OP_ADD=22 -OP_SUB=23 -LPAREN=24 -RPAREN=25 -LBRACK=26 -RBRACK=27 -LBRACE=28 -RBRACE=29 -EQ=30 -TY_ACCOUNT=31 -TY_ASSET=32 -TY_NUMBER=33 -TY_MONETARY=34 -TY_PORTION=35 -TY_STRING=36 -STRING=37 -PORTION=38 -REMAINING=39 -KEPT=40 -BALANCE=41 -SAVE=42 -NUMBER=43 -PERCENT=44 -VARIABLE_NAME=45 -ACCOUNT=46 -ASSET=47 -'*'=1 -'allowing overdraft up to'=2 -'allowing unbounded overdraft'=3 -','=4 -'vars'=9 -'meta'=10 -'set_tx_meta'=11 -'set_account_meta'=12 -'print'=13 -'fail'=14 -'send'=15 -'source'=16 -'from'=17 -'max'=18 -'destination'=19 -'to'=20 -'allocate'=21 -'+'=22 -'-'=23 -'('=24 -')'=25 -'['=26 -']'=27 -'{'=28 -'}'=29 -'='=30 -'account'=31 -'asset'=32 -'number'=33 -'monetary'=34 -'portion'=35 -'string'=36 -'remaining'=39 -'kept'=40 -'balance'=41 -'save'=42 -'%'=44 diff --git a/components/ledger/internal/machine/script/parser/numscript_base_listener.go b/components/ledger/internal/machine/script/parser/numscript_base_listener.go deleted file mode 100644 index 5e4904df73..0000000000 --- a/components/ledger/internal/machine/script/parser/numscript_base_listener.go +++ /dev/null @@ -1,298 +0,0 @@ -// Code generated from NumScript.g4 by ANTLR 4.10.1. DO NOT EDIT. - -package parser // NumScript - -import "github.com/antlr/antlr4/runtime/Go/antlr" - -// BaseNumScriptListener is a complete listener for a parse tree produced by NumScriptParser. -type BaseNumScriptListener struct{} - -var _ NumScriptListener = &BaseNumScriptListener{} - -// VisitTerminal is called when a terminal node is visited. -func (s *BaseNumScriptListener) VisitTerminal(node antlr.TerminalNode) {} - -// VisitErrorNode is called when an error node is visited. -func (s *BaseNumScriptListener) VisitErrorNode(node antlr.ErrorNode) {} - -// EnterEveryRule is called when any rule is entered. -func (s *BaseNumScriptListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} - -// ExitEveryRule is called when any rule is exited. -func (s *BaseNumScriptListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} - -// EnterMonetary is called when production monetary is entered. -func (s *BaseNumScriptListener) EnterMonetary(ctx *MonetaryContext) {} - -// ExitMonetary is called when production monetary is exited. -func (s *BaseNumScriptListener) ExitMonetary(ctx *MonetaryContext) {} - -// EnterMonetaryAll is called when production monetaryAll is entered. -func (s *BaseNumScriptListener) EnterMonetaryAll(ctx *MonetaryAllContext) {} - -// ExitMonetaryAll is called when production monetaryAll is exited. -func (s *BaseNumScriptListener) ExitMonetaryAll(ctx *MonetaryAllContext) {} - -// EnterLitAccount is called when production LitAccount is entered. -func (s *BaseNumScriptListener) EnterLitAccount(ctx *LitAccountContext) {} - -// ExitLitAccount is called when production LitAccount is exited. -func (s *BaseNumScriptListener) ExitLitAccount(ctx *LitAccountContext) {} - -// EnterLitAsset is called when production LitAsset is entered. -func (s *BaseNumScriptListener) EnterLitAsset(ctx *LitAssetContext) {} - -// ExitLitAsset is called when production LitAsset is exited. -func (s *BaseNumScriptListener) ExitLitAsset(ctx *LitAssetContext) {} - -// EnterLitNumber is called when production LitNumber is entered. -func (s *BaseNumScriptListener) EnterLitNumber(ctx *LitNumberContext) {} - -// ExitLitNumber is called when production LitNumber is exited. -func (s *BaseNumScriptListener) ExitLitNumber(ctx *LitNumberContext) {} - -// EnterLitString is called when production LitString is entered. -func (s *BaseNumScriptListener) EnterLitString(ctx *LitStringContext) {} - -// ExitLitString is called when production LitString is exited. -func (s *BaseNumScriptListener) ExitLitString(ctx *LitStringContext) {} - -// EnterLitPortion is called when production LitPortion is entered. -func (s *BaseNumScriptListener) EnterLitPortion(ctx *LitPortionContext) {} - -// ExitLitPortion is called when production LitPortion is exited. -func (s *BaseNumScriptListener) ExitLitPortion(ctx *LitPortionContext) {} - -// EnterLitMonetary is called when production LitMonetary is entered. -func (s *BaseNumScriptListener) EnterLitMonetary(ctx *LitMonetaryContext) {} - -// ExitLitMonetary is called when production LitMonetary is exited. -func (s *BaseNumScriptListener) ExitLitMonetary(ctx *LitMonetaryContext) {} - -// EnterVariable is called when production variable is entered. -func (s *BaseNumScriptListener) EnterVariable(ctx *VariableContext) {} - -// ExitVariable is called when production variable is exited. -func (s *BaseNumScriptListener) ExitVariable(ctx *VariableContext) {} - -// EnterExprAddSub is called when production ExprAddSub is entered. -func (s *BaseNumScriptListener) EnterExprAddSub(ctx *ExprAddSubContext) {} - -// ExitExprAddSub is called when production ExprAddSub is exited. -func (s *BaseNumScriptListener) ExitExprAddSub(ctx *ExprAddSubContext) {} - -// EnterExprLiteral is called when production ExprLiteral is entered. -func (s *BaseNumScriptListener) EnterExprLiteral(ctx *ExprLiteralContext) {} - -// ExitExprLiteral is called when production ExprLiteral is exited. -func (s *BaseNumScriptListener) ExitExprLiteral(ctx *ExprLiteralContext) {} - -// EnterExprVariable is called when production ExprVariable is entered. -func (s *BaseNumScriptListener) EnterExprVariable(ctx *ExprVariableContext) {} - -// ExitExprVariable is called when production ExprVariable is exited. -func (s *BaseNumScriptListener) ExitExprVariable(ctx *ExprVariableContext) {} - -// EnterAllotmentPortionConst is called when production AllotmentPortionConst is entered. -func (s *BaseNumScriptListener) EnterAllotmentPortionConst(ctx *AllotmentPortionConstContext) {} - -// ExitAllotmentPortionConst is called when production AllotmentPortionConst is exited. -func (s *BaseNumScriptListener) ExitAllotmentPortionConst(ctx *AllotmentPortionConstContext) {} - -// EnterAllotmentPortionVar is called when production AllotmentPortionVar is entered. -func (s *BaseNumScriptListener) EnterAllotmentPortionVar(ctx *AllotmentPortionVarContext) {} - -// ExitAllotmentPortionVar is called when production AllotmentPortionVar is exited. -func (s *BaseNumScriptListener) ExitAllotmentPortionVar(ctx *AllotmentPortionVarContext) {} - -// EnterAllotmentPortionRemaining is called when production AllotmentPortionRemaining is entered. -func (s *BaseNumScriptListener) EnterAllotmentPortionRemaining(ctx *AllotmentPortionRemainingContext) { -} - -// ExitAllotmentPortionRemaining is called when production AllotmentPortionRemaining is exited. -func (s *BaseNumScriptListener) ExitAllotmentPortionRemaining(ctx *AllotmentPortionRemainingContext) { -} - -// EnterDestinationInOrder is called when production destinationInOrder is entered. -func (s *BaseNumScriptListener) EnterDestinationInOrder(ctx *DestinationInOrderContext) {} - -// ExitDestinationInOrder is called when production destinationInOrder is exited. -func (s *BaseNumScriptListener) ExitDestinationInOrder(ctx *DestinationInOrderContext) {} - -// EnterDestinationAllotment is called when production destinationAllotment is entered. -func (s *BaseNumScriptListener) EnterDestinationAllotment(ctx *DestinationAllotmentContext) {} - -// ExitDestinationAllotment is called when production destinationAllotment is exited. -func (s *BaseNumScriptListener) ExitDestinationAllotment(ctx *DestinationAllotmentContext) {} - -// EnterIsDestination is called when production IsDestination is entered. -func (s *BaseNumScriptListener) EnterIsDestination(ctx *IsDestinationContext) {} - -// ExitIsDestination is called when production IsDestination is exited. -func (s *BaseNumScriptListener) ExitIsDestination(ctx *IsDestinationContext) {} - -// EnterIsKept is called when production IsKept is entered. -func (s *BaseNumScriptListener) EnterIsKept(ctx *IsKeptContext) {} - -// ExitIsKept is called when production IsKept is exited. -func (s *BaseNumScriptListener) ExitIsKept(ctx *IsKeptContext) {} - -// EnterDestAccount is called when production DestAccount is entered. -func (s *BaseNumScriptListener) EnterDestAccount(ctx *DestAccountContext) {} - -// ExitDestAccount is called when production DestAccount is exited. -func (s *BaseNumScriptListener) ExitDestAccount(ctx *DestAccountContext) {} - -// EnterDestInOrder is called when production DestInOrder is entered. -func (s *BaseNumScriptListener) EnterDestInOrder(ctx *DestInOrderContext) {} - -// ExitDestInOrder is called when production DestInOrder is exited. -func (s *BaseNumScriptListener) ExitDestInOrder(ctx *DestInOrderContext) {} - -// EnterDestAllotment is called when production DestAllotment is entered. -func (s *BaseNumScriptListener) EnterDestAllotment(ctx *DestAllotmentContext) {} - -// ExitDestAllotment is called when production DestAllotment is exited. -func (s *BaseNumScriptListener) ExitDestAllotment(ctx *DestAllotmentContext) {} - -// EnterSrcAccountOverdraftSpecific is called when production SrcAccountOverdraftSpecific is entered. -func (s *BaseNumScriptListener) EnterSrcAccountOverdraftSpecific(ctx *SrcAccountOverdraftSpecificContext) { -} - -// ExitSrcAccountOverdraftSpecific is called when production SrcAccountOverdraftSpecific is exited. -func (s *BaseNumScriptListener) ExitSrcAccountOverdraftSpecific(ctx *SrcAccountOverdraftSpecificContext) { -} - -// EnterSrcAccountOverdraftUnbounded is called when production SrcAccountOverdraftUnbounded is entered. -func (s *BaseNumScriptListener) EnterSrcAccountOverdraftUnbounded(ctx *SrcAccountOverdraftUnboundedContext) { -} - -// ExitSrcAccountOverdraftUnbounded is called when production SrcAccountOverdraftUnbounded is exited. -func (s *BaseNumScriptListener) ExitSrcAccountOverdraftUnbounded(ctx *SrcAccountOverdraftUnboundedContext) { -} - -// EnterSourceAccount is called when production sourceAccount is entered. -func (s *BaseNumScriptListener) EnterSourceAccount(ctx *SourceAccountContext) {} - -// ExitSourceAccount is called when production sourceAccount is exited. -func (s *BaseNumScriptListener) ExitSourceAccount(ctx *SourceAccountContext) {} - -// EnterSourceInOrder is called when production sourceInOrder is entered. -func (s *BaseNumScriptListener) EnterSourceInOrder(ctx *SourceInOrderContext) {} - -// ExitSourceInOrder is called when production sourceInOrder is exited. -func (s *BaseNumScriptListener) ExitSourceInOrder(ctx *SourceInOrderContext) {} - -// EnterSourceMaxed is called when production sourceMaxed is entered. -func (s *BaseNumScriptListener) EnterSourceMaxed(ctx *SourceMaxedContext) {} - -// ExitSourceMaxed is called when production sourceMaxed is exited. -func (s *BaseNumScriptListener) ExitSourceMaxed(ctx *SourceMaxedContext) {} - -// EnterSrcAccount is called when production SrcAccount is entered. -func (s *BaseNumScriptListener) EnterSrcAccount(ctx *SrcAccountContext) {} - -// ExitSrcAccount is called when production SrcAccount is exited. -func (s *BaseNumScriptListener) ExitSrcAccount(ctx *SrcAccountContext) {} - -// EnterSrcMaxed is called when production SrcMaxed is entered. -func (s *BaseNumScriptListener) EnterSrcMaxed(ctx *SrcMaxedContext) {} - -// ExitSrcMaxed is called when production SrcMaxed is exited. -func (s *BaseNumScriptListener) ExitSrcMaxed(ctx *SrcMaxedContext) {} - -// EnterSrcInOrder is called when production SrcInOrder is entered. -func (s *BaseNumScriptListener) EnterSrcInOrder(ctx *SrcInOrderContext) {} - -// ExitSrcInOrder is called when production SrcInOrder is exited. -func (s *BaseNumScriptListener) ExitSrcInOrder(ctx *SrcInOrderContext) {} - -// EnterSourceAllotment is called when production sourceAllotment is entered. -func (s *BaseNumScriptListener) EnterSourceAllotment(ctx *SourceAllotmentContext) {} - -// ExitSourceAllotment is called when production sourceAllotment is exited. -func (s *BaseNumScriptListener) ExitSourceAllotment(ctx *SourceAllotmentContext) {} - -// EnterSrc is called when production Src is entered. -func (s *BaseNumScriptListener) EnterSrc(ctx *SrcContext) {} - -// ExitSrc is called when production Src is exited. -func (s *BaseNumScriptListener) ExitSrc(ctx *SrcContext) {} - -// EnterSrcAllotment is called when production SrcAllotment is entered. -func (s *BaseNumScriptListener) EnterSrcAllotment(ctx *SrcAllotmentContext) {} - -// ExitSrcAllotment is called when production SrcAllotment is exited. -func (s *BaseNumScriptListener) ExitSrcAllotment(ctx *SrcAllotmentContext) {} - -// EnterPrint is called when production Print is entered. -func (s *BaseNumScriptListener) EnterPrint(ctx *PrintContext) {} - -// ExitPrint is called when production Print is exited. -func (s *BaseNumScriptListener) ExitPrint(ctx *PrintContext) {} - -// EnterSaveFromAccount is called when production SaveFromAccount is entered. -func (s *BaseNumScriptListener) EnterSaveFromAccount(ctx *SaveFromAccountContext) {} - -// ExitSaveFromAccount is called when production SaveFromAccount is exited. -func (s *BaseNumScriptListener) ExitSaveFromAccount(ctx *SaveFromAccountContext) {} - -// EnterSetTxMeta is called when production SetTxMeta is entered. -func (s *BaseNumScriptListener) EnterSetTxMeta(ctx *SetTxMetaContext) {} - -// ExitSetTxMeta is called when production SetTxMeta is exited. -func (s *BaseNumScriptListener) ExitSetTxMeta(ctx *SetTxMetaContext) {} - -// EnterSetAccountMeta is called when production SetAccountMeta is entered. -func (s *BaseNumScriptListener) EnterSetAccountMeta(ctx *SetAccountMetaContext) {} - -// ExitSetAccountMeta is called when production SetAccountMeta is exited. -func (s *BaseNumScriptListener) ExitSetAccountMeta(ctx *SetAccountMetaContext) {} - -// EnterFail is called when production Fail is entered. -func (s *BaseNumScriptListener) EnterFail(ctx *FailContext) {} - -// ExitFail is called when production Fail is exited. -func (s *BaseNumScriptListener) ExitFail(ctx *FailContext) {} - -// EnterSend is called when production Send is entered. -func (s *BaseNumScriptListener) EnterSend(ctx *SendContext) {} - -// ExitSend is called when production Send is exited. -func (s *BaseNumScriptListener) ExitSend(ctx *SendContext) {} - -// EnterType_ is called when production type_ is entered. -func (s *BaseNumScriptListener) EnterType_(ctx *Type_Context) {} - -// ExitType_ is called when production type_ is exited. -func (s *BaseNumScriptListener) ExitType_(ctx *Type_Context) {} - -// EnterOriginAccountMeta is called when production OriginAccountMeta is entered. -func (s *BaseNumScriptListener) EnterOriginAccountMeta(ctx *OriginAccountMetaContext) {} - -// ExitOriginAccountMeta is called when production OriginAccountMeta is exited. -func (s *BaseNumScriptListener) ExitOriginAccountMeta(ctx *OriginAccountMetaContext) {} - -// EnterOriginAccountBalance is called when production OriginAccountBalance is entered. -func (s *BaseNumScriptListener) EnterOriginAccountBalance(ctx *OriginAccountBalanceContext) {} - -// ExitOriginAccountBalance is called when production OriginAccountBalance is exited. -func (s *BaseNumScriptListener) ExitOriginAccountBalance(ctx *OriginAccountBalanceContext) {} - -// EnterVarDecl is called when production varDecl is entered. -func (s *BaseNumScriptListener) EnterVarDecl(ctx *VarDeclContext) {} - -// ExitVarDecl is called when production varDecl is exited. -func (s *BaseNumScriptListener) ExitVarDecl(ctx *VarDeclContext) {} - -// EnterVarListDecl is called when production varListDecl is entered. -func (s *BaseNumScriptListener) EnterVarListDecl(ctx *VarListDeclContext) {} - -// ExitVarListDecl is called when production varListDecl is exited. -func (s *BaseNumScriptListener) ExitVarListDecl(ctx *VarListDeclContext) {} - -// EnterScript is called when production script is entered. -func (s *BaseNumScriptListener) EnterScript(ctx *ScriptContext) {} - -// ExitScript is called when production script is exited. -func (s *BaseNumScriptListener) ExitScript(ctx *ScriptContext) {} diff --git a/components/ledger/internal/machine/script/parser/numscript_lexer.go b/components/ledger/internal/machine/script/parser/numscript_lexer.go deleted file mode 100644 index 38cba91eca..0000000000 --- a/components/ledger/internal/machine/script/parser/numscript_lexer.go +++ /dev/null @@ -1,369 +0,0 @@ -// Code generated from NumScript.g4 by ANTLR 4.10.1. DO NOT EDIT. - -package parser - -import ( - "fmt" - "sync" - "unicode" - - "github.com/antlr/antlr4/runtime/Go/antlr" -) - -// Suppress unused import error -var _ = fmt.Printf -var _ = sync.Once{} -var _ = unicode.IsLetter - -type NumScriptLexer struct { - *antlr.BaseLexer - channelNames []string - modeNames []string - // TODO: EOF string -} - -var numscriptlexerLexerStaticData struct { - once sync.Once - serializedATN []int32 - channelNames []string - modeNames []string - literalNames []string - symbolicNames []string - ruleNames []string - predictionContextCache *antlr.PredictionContextCache - atn *antlr.ATN - decisionToDFA []*antlr.DFA -} - -func numscriptlexerLexerInit() { - staticData := &numscriptlexerLexerStaticData - staticData.channelNames = []string{ - "DEFAULT_TOKEN_CHANNEL", "HIDDEN", - } - staticData.modeNames = []string{ - "DEFAULT_MODE", - } - staticData.literalNames = []string{ - "", "'*'", "'allowing overdraft up to'", "'allowing unbounded overdraft'", - "','", "", "", "", "", "'vars'", "'meta'", "'set_tx_meta'", "'set_account_meta'", - "'print'", "'fail'", "'send'", "'source'", "'from'", "'max'", "'destination'", - "'to'", "'allocate'", "'+'", "'-'", "'('", "')'", "'['", "']'", "'{'", - "'}'", "'='", "'account'", "'asset'", "'number'", "'monetary'", "'portion'", - "'string'", "", "", "'remaining'", "'kept'", "'balance'", "'save'", - "", "'%'", - } - staticData.symbolicNames = []string{ - "", "", "", "", "", "NEWLINE", "WHITESPACE", "MULTILINE_COMMENT", "LINE_COMMENT", - "VARS", "META", "SET_TX_META", "SET_ACCOUNT_META", "PRINT", "FAIL", - "SEND", "SOURCE", "FROM", "MAX", "DESTINATION", "TO", "ALLOCATE", "OP_ADD", - "OP_SUB", "LPAREN", "RPAREN", "LBRACK", "RBRACK", "LBRACE", "RBRACE", - "EQ", "TY_ACCOUNT", "TY_ASSET", "TY_NUMBER", "TY_MONETARY", "TY_PORTION", - "TY_STRING", "STRING", "PORTION", "REMAINING", "KEPT", "BALANCE", "SAVE", - "NUMBER", "PERCENT", "VARIABLE_NAME", "ACCOUNT", "ASSET", - } - staticData.ruleNames = []string{ - "T__0", "T__1", "T__2", "T__3", "NEWLINE", "WHITESPACE", "MULTILINE_COMMENT", - "LINE_COMMENT", "VARS", "META", "SET_TX_META", "SET_ACCOUNT_META", "PRINT", - "FAIL", "SEND", "SOURCE", "FROM", "MAX", "DESTINATION", "TO", "ALLOCATE", - "OP_ADD", "OP_SUB", "LPAREN", "RPAREN", "LBRACK", "RBRACK", "LBRACE", - "RBRACE", "EQ", "TY_ACCOUNT", "TY_ASSET", "TY_NUMBER", "TY_MONETARY", - "TY_PORTION", "TY_STRING", "STRING", "PORTION", "REMAINING", "KEPT", - "BALANCE", "SAVE", "NUMBER", "PERCENT", "VARIABLE_NAME", "ACCOUNT", - "ASSET", - } - staticData.predictionContextCache = antlr.NewPredictionContextCache() - staticData.serializedATN = []int32{ - 4, 0, 47, 464, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, - 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, - 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, - 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, - 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, - 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, - 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, - 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, - 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, - 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 4, 4, - 155, 8, 4, 11, 4, 12, 4, 156, 1, 5, 4, 5, 160, 8, 5, 11, 5, 12, 5, 161, - 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 171, 8, 6, 10, 6, 12, 6, - 174, 9, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, - 185, 8, 7, 10, 7, 12, 7, 188, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, - 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, - 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, - 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, - 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, - 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, - 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, - 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, - 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 20, - 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, - 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, - 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, - 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, - 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, - 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, - 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, - 36, 1, 36, 5, 36, 356, 8, 36, 10, 36, 12, 36, 359, 9, 36, 1, 36, 1, 36, - 1, 37, 4, 37, 364, 8, 37, 11, 37, 12, 37, 365, 1, 37, 3, 37, 369, 8, 37, - 1, 37, 1, 37, 3, 37, 373, 8, 37, 1, 37, 4, 37, 376, 8, 37, 11, 37, 12, - 37, 377, 1, 37, 4, 37, 381, 8, 37, 11, 37, 12, 37, 382, 1, 37, 1, 37, 4, - 37, 387, 8, 37, 11, 37, 12, 37, 388, 3, 37, 391, 8, 37, 1, 37, 3, 37, 394, - 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, 38, 1, - 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 40, 1, 40, - 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 4, 42, 425, - 8, 42, 11, 42, 12, 42, 426, 1, 43, 1, 43, 1, 44, 1, 44, 4, 44, 433, 8, - 44, 11, 44, 12, 44, 434, 1, 44, 5, 44, 438, 8, 44, 10, 44, 12, 44, 441, - 9, 44, 1, 45, 1, 45, 4, 45, 445, 8, 45, 11, 45, 12, 45, 446, 1, 45, 1, - 45, 4, 45, 451, 8, 45, 11, 45, 12, 45, 452, 5, 45, 455, 8, 45, 10, 45, - 12, 45, 458, 9, 45, 1, 46, 4, 46, 461, 8, 46, 11, 46, 12, 46, 462, 2, 172, - 186, 0, 47, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, - 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, - 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, - 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, - 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 87, 44, 89, 45, 91, - 46, 93, 47, 1, 0, 9, 2, 0, 10, 10, 13, 13, 2, 0, 9, 9, 32, 32, 3, 0, 10, - 10, 13, 13, 34, 34, 1, 0, 48, 57, 1, 0, 32, 32, 2, 0, 95, 95, 97, 122, - 3, 0, 48, 57, 95, 95, 97, 122, 5, 0, 45, 45, 48, 57, 65, 90, 95, 95, 97, - 122, 2, 0, 47, 57, 65, 90, 485, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, - 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, - 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, - 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, - 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, - 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, - 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, - 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, - 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, - 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, - 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, - 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, - 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 93, 1, 0, 0, 0, 1, 95, 1, 0, 0, 0, 3, 97, - 1, 0, 0, 0, 5, 122, 1, 0, 0, 0, 7, 151, 1, 0, 0, 0, 9, 154, 1, 0, 0, 0, - 11, 159, 1, 0, 0, 0, 13, 165, 1, 0, 0, 0, 15, 180, 1, 0, 0, 0, 17, 193, - 1, 0, 0, 0, 19, 198, 1, 0, 0, 0, 21, 203, 1, 0, 0, 0, 23, 215, 1, 0, 0, - 0, 25, 232, 1, 0, 0, 0, 27, 238, 1, 0, 0, 0, 29, 243, 1, 0, 0, 0, 31, 248, - 1, 0, 0, 0, 33, 255, 1, 0, 0, 0, 35, 260, 1, 0, 0, 0, 37, 264, 1, 0, 0, - 0, 39, 276, 1, 0, 0, 0, 41, 279, 1, 0, 0, 0, 43, 288, 1, 0, 0, 0, 45, 290, - 1, 0, 0, 0, 47, 292, 1, 0, 0, 0, 49, 294, 1, 0, 0, 0, 51, 296, 1, 0, 0, - 0, 53, 298, 1, 0, 0, 0, 55, 300, 1, 0, 0, 0, 57, 302, 1, 0, 0, 0, 59, 304, - 1, 0, 0, 0, 61, 306, 1, 0, 0, 0, 63, 314, 1, 0, 0, 0, 65, 320, 1, 0, 0, - 0, 67, 327, 1, 0, 0, 0, 69, 336, 1, 0, 0, 0, 71, 344, 1, 0, 0, 0, 73, 351, - 1, 0, 0, 0, 75, 393, 1, 0, 0, 0, 77, 395, 1, 0, 0, 0, 79, 405, 1, 0, 0, - 0, 81, 410, 1, 0, 0, 0, 83, 418, 1, 0, 0, 0, 85, 424, 1, 0, 0, 0, 87, 428, - 1, 0, 0, 0, 89, 430, 1, 0, 0, 0, 91, 442, 1, 0, 0, 0, 93, 460, 1, 0, 0, - 0, 95, 96, 5, 42, 0, 0, 96, 2, 1, 0, 0, 0, 97, 98, 5, 97, 0, 0, 98, 99, - 5, 108, 0, 0, 99, 100, 5, 108, 0, 0, 100, 101, 5, 111, 0, 0, 101, 102, - 5, 119, 0, 0, 102, 103, 5, 105, 0, 0, 103, 104, 5, 110, 0, 0, 104, 105, - 5, 103, 0, 0, 105, 106, 5, 32, 0, 0, 106, 107, 5, 111, 0, 0, 107, 108, - 5, 118, 0, 0, 108, 109, 5, 101, 0, 0, 109, 110, 5, 114, 0, 0, 110, 111, - 5, 100, 0, 0, 111, 112, 5, 114, 0, 0, 112, 113, 5, 97, 0, 0, 113, 114, - 5, 102, 0, 0, 114, 115, 5, 116, 0, 0, 115, 116, 5, 32, 0, 0, 116, 117, - 5, 117, 0, 0, 117, 118, 5, 112, 0, 0, 118, 119, 5, 32, 0, 0, 119, 120, - 5, 116, 0, 0, 120, 121, 5, 111, 0, 0, 121, 4, 1, 0, 0, 0, 122, 123, 5, - 97, 0, 0, 123, 124, 5, 108, 0, 0, 124, 125, 5, 108, 0, 0, 125, 126, 5, - 111, 0, 0, 126, 127, 5, 119, 0, 0, 127, 128, 5, 105, 0, 0, 128, 129, 5, - 110, 0, 0, 129, 130, 5, 103, 0, 0, 130, 131, 5, 32, 0, 0, 131, 132, 5, - 117, 0, 0, 132, 133, 5, 110, 0, 0, 133, 134, 5, 98, 0, 0, 134, 135, 5, - 111, 0, 0, 135, 136, 5, 117, 0, 0, 136, 137, 5, 110, 0, 0, 137, 138, 5, - 100, 0, 0, 138, 139, 5, 101, 0, 0, 139, 140, 5, 100, 0, 0, 140, 141, 5, - 32, 0, 0, 141, 142, 5, 111, 0, 0, 142, 143, 5, 118, 0, 0, 143, 144, 5, - 101, 0, 0, 144, 145, 5, 114, 0, 0, 145, 146, 5, 100, 0, 0, 146, 147, 5, - 114, 0, 0, 147, 148, 5, 97, 0, 0, 148, 149, 5, 102, 0, 0, 149, 150, 5, - 116, 0, 0, 150, 6, 1, 0, 0, 0, 151, 152, 5, 44, 0, 0, 152, 8, 1, 0, 0, - 0, 153, 155, 7, 0, 0, 0, 154, 153, 1, 0, 0, 0, 155, 156, 1, 0, 0, 0, 156, - 154, 1, 0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 10, 1, 0, 0, 0, 158, 160, 7, - 1, 0, 0, 159, 158, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 159, 1, 0, 0, - 0, 161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, 6, 5, 0, 0, 164, - 12, 1, 0, 0, 0, 165, 166, 5, 47, 0, 0, 166, 167, 5, 42, 0, 0, 167, 172, - 1, 0, 0, 0, 168, 171, 3, 13, 6, 0, 169, 171, 9, 0, 0, 0, 170, 168, 1, 0, - 0, 0, 170, 169, 1, 0, 0, 0, 171, 174, 1, 0, 0, 0, 172, 173, 1, 0, 0, 0, - 172, 170, 1, 0, 0, 0, 173, 175, 1, 0, 0, 0, 174, 172, 1, 0, 0, 0, 175, - 176, 5, 42, 0, 0, 176, 177, 5, 47, 0, 0, 177, 178, 1, 0, 0, 0, 178, 179, - 6, 6, 0, 0, 179, 14, 1, 0, 0, 0, 180, 181, 5, 47, 0, 0, 181, 182, 5, 47, - 0, 0, 182, 186, 1, 0, 0, 0, 183, 185, 9, 0, 0, 0, 184, 183, 1, 0, 0, 0, - 185, 188, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 186, 184, 1, 0, 0, 0, 187, - 189, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0, 189, 190, 3, 9, 4, 0, 190, 191, - 1, 0, 0, 0, 191, 192, 6, 7, 0, 0, 192, 16, 1, 0, 0, 0, 193, 194, 5, 118, - 0, 0, 194, 195, 5, 97, 0, 0, 195, 196, 5, 114, 0, 0, 196, 197, 5, 115, - 0, 0, 197, 18, 1, 0, 0, 0, 198, 199, 5, 109, 0, 0, 199, 200, 5, 101, 0, - 0, 200, 201, 5, 116, 0, 0, 201, 202, 5, 97, 0, 0, 202, 20, 1, 0, 0, 0, - 203, 204, 5, 115, 0, 0, 204, 205, 5, 101, 0, 0, 205, 206, 5, 116, 0, 0, - 206, 207, 5, 95, 0, 0, 207, 208, 5, 116, 0, 0, 208, 209, 5, 120, 0, 0, - 209, 210, 5, 95, 0, 0, 210, 211, 5, 109, 0, 0, 211, 212, 5, 101, 0, 0, - 212, 213, 5, 116, 0, 0, 213, 214, 5, 97, 0, 0, 214, 22, 1, 0, 0, 0, 215, - 216, 5, 115, 0, 0, 216, 217, 5, 101, 0, 0, 217, 218, 5, 116, 0, 0, 218, - 219, 5, 95, 0, 0, 219, 220, 5, 97, 0, 0, 220, 221, 5, 99, 0, 0, 221, 222, - 5, 99, 0, 0, 222, 223, 5, 111, 0, 0, 223, 224, 5, 117, 0, 0, 224, 225, - 5, 110, 0, 0, 225, 226, 5, 116, 0, 0, 226, 227, 5, 95, 0, 0, 227, 228, - 5, 109, 0, 0, 228, 229, 5, 101, 0, 0, 229, 230, 5, 116, 0, 0, 230, 231, - 5, 97, 0, 0, 231, 24, 1, 0, 0, 0, 232, 233, 5, 112, 0, 0, 233, 234, 5, - 114, 0, 0, 234, 235, 5, 105, 0, 0, 235, 236, 5, 110, 0, 0, 236, 237, 5, - 116, 0, 0, 237, 26, 1, 0, 0, 0, 238, 239, 5, 102, 0, 0, 239, 240, 5, 97, - 0, 0, 240, 241, 5, 105, 0, 0, 241, 242, 5, 108, 0, 0, 242, 28, 1, 0, 0, - 0, 243, 244, 5, 115, 0, 0, 244, 245, 5, 101, 0, 0, 245, 246, 5, 110, 0, - 0, 246, 247, 5, 100, 0, 0, 247, 30, 1, 0, 0, 0, 248, 249, 5, 115, 0, 0, - 249, 250, 5, 111, 0, 0, 250, 251, 5, 117, 0, 0, 251, 252, 5, 114, 0, 0, - 252, 253, 5, 99, 0, 0, 253, 254, 5, 101, 0, 0, 254, 32, 1, 0, 0, 0, 255, - 256, 5, 102, 0, 0, 256, 257, 5, 114, 0, 0, 257, 258, 5, 111, 0, 0, 258, - 259, 5, 109, 0, 0, 259, 34, 1, 0, 0, 0, 260, 261, 5, 109, 0, 0, 261, 262, - 5, 97, 0, 0, 262, 263, 5, 120, 0, 0, 263, 36, 1, 0, 0, 0, 264, 265, 5, - 100, 0, 0, 265, 266, 5, 101, 0, 0, 266, 267, 5, 115, 0, 0, 267, 268, 5, - 116, 0, 0, 268, 269, 5, 105, 0, 0, 269, 270, 5, 110, 0, 0, 270, 271, 5, - 97, 0, 0, 271, 272, 5, 116, 0, 0, 272, 273, 5, 105, 0, 0, 273, 274, 5, - 111, 0, 0, 274, 275, 5, 110, 0, 0, 275, 38, 1, 0, 0, 0, 276, 277, 5, 116, - 0, 0, 277, 278, 5, 111, 0, 0, 278, 40, 1, 0, 0, 0, 279, 280, 5, 97, 0, - 0, 280, 281, 5, 108, 0, 0, 281, 282, 5, 108, 0, 0, 282, 283, 5, 111, 0, - 0, 283, 284, 5, 99, 0, 0, 284, 285, 5, 97, 0, 0, 285, 286, 5, 116, 0, 0, - 286, 287, 5, 101, 0, 0, 287, 42, 1, 0, 0, 0, 288, 289, 5, 43, 0, 0, 289, - 44, 1, 0, 0, 0, 290, 291, 5, 45, 0, 0, 291, 46, 1, 0, 0, 0, 292, 293, 5, - 40, 0, 0, 293, 48, 1, 0, 0, 0, 294, 295, 5, 41, 0, 0, 295, 50, 1, 0, 0, - 0, 296, 297, 5, 91, 0, 0, 297, 52, 1, 0, 0, 0, 298, 299, 5, 93, 0, 0, 299, - 54, 1, 0, 0, 0, 300, 301, 5, 123, 0, 0, 301, 56, 1, 0, 0, 0, 302, 303, - 5, 125, 0, 0, 303, 58, 1, 0, 0, 0, 304, 305, 5, 61, 0, 0, 305, 60, 1, 0, - 0, 0, 306, 307, 5, 97, 0, 0, 307, 308, 5, 99, 0, 0, 308, 309, 5, 99, 0, - 0, 309, 310, 5, 111, 0, 0, 310, 311, 5, 117, 0, 0, 311, 312, 5, 110, 0, - 0, 312, 313, 5, 116, 0, 0, 313, 62, 1, 0, 0, 0, 314, 315, 5, 97, 0, 0, - 315, 316, 5, 115, 0, 0, 316, 317, 5, 115, 0, 0, 317, 318, 5, 101, 0, 0, - 318, 319, 5, 116, 0, 0, 319, 64, 1, 0, 0, 0, 320, 321, 5, 110, 0, 0, 321, - 322, 5, 117, 0, 0, 322, 323, 5, 109, 0, 0, 323, 324, 5, 98, 0, 0, 324, - 325, 5, 101, 0, 0, 325, 326, 5, 114, 0, 0, 326, 66, 1, 0, 0, 0, 327, 328, - 5, 109, 0, 0, 328, 329, 5, 111, 0, 0, 329, 330, 5, 110, 0, 0, 330, 331, - 5, 101, 0, 0, 331, 332, 5, 116, 0, 0, 332, 333, 5, 97, 0, 0, 333, 334, - 5, 114, 0, 0, 334, 335, 5, 121, 0, 0, 335, 68, 1, 0, 0, 0, 336, 337, 5, - 112, 0, 0, 337, 338, 5, 111, 0, 0, 338, 339, 5, 114, 0, 0, 339, 340, 5, - 116, 0, 0, 340, 341, 5, 105, 0, 0, 341, 342, 5, 111, 0, 0, 342, 343, 5, - 110, 0, 0, 343, 70, 1, 0, 0, 0, 344, 345, 5, 115, 0, 0, 345, 346, 5, 116, - 0, 0, 346, 347, 5, 114, 0, 0, 347, 348, 5, 105, 0, 0, 348, 349, 5, 110, - 0, 0, 349, 350, 5, 103, 0, 0, 350, 72, 1, 0, 0, 0, 351, 357, 5, 34, 0, - 0, 352, 353, 5, 92, 0, 0, 353, 356, 5, 34, 0, 0, 354, 356, 8, 2, 0, 0, - 355, 352, 1, 0, 0, 0, 355, 354, 1, 0, 0, 0, 356, 359, 1, 0, 0, 0, 357, - 355, 1, 0, 0, 0, 357, 358, 1, 0, 0, 0, 358, 360, 1, 0, 0, 0, 359, 357, - 1, 0, 0, 0, 360, 361, 5, 34, 0, 0, 361, 74, 1, 0, 0, 0, 362, 364, 7, 3, - 0, 0, 363, 362, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 363, 1, 0, 0, 0, - 365, 366, 1, 0, 0, 0, 366, 368, 1, 0, 0, 0, 367, 369, 7, 4, 0, 0, 368, - 367, 1, 0, 0, 0, 368, 369, 1, 0, 0, 0, 369, 370, 1, 0, 0, 0, 370, 372, - 5, 47, 0, 0, 371, 373, 7, 4, 0, 0, 372, 371, 1, 0, 0, 0, 372, 373, 1, 0, - 0, 0, 373, 375, 1, 0, 0, 0, 374, 376, 7, 3, 0, 0, 375, 374, 1, 0, 0, 0, - 376, 377, 1, 0, 0, 0, 377, 375, 1, 0, 0, 0, 377, 378, 1, 0, 0, 0, 378, - 394, 1, 0, 0, 0, 379, 381, 7, 3, 0, 0, 380, 379, 1, 0, 0, 0, 381, 382, - 1, 0, 0, 0, 382, 380, 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 390, 1, 0, - 0, 0, 384, 386, 5, 46, 0, 0, 385, 387, 7, 3, 0, 0, 386, 385, 1, 0, 0, 0, - 387, 388, 1, 0, 0, 0, 388, 386, 1, 0, 0, 0, 388, 389, 1, 0, 0, 0, 389, - 391, 1, 0, 0, 0, 390, 384, 1, 0, 0, 0, 390, 391, 1, 0, 0, 0, 391, 392, - 1, 0, 0, 0, 392, 394, 5, 37, 0, 0, 393, 363, 1, 0, 0, 0, 393, 380, 1, 0, - 0, 0, 394, 76, 1, 0, 0, 0, 395, 396, 5, 114, 0, 0, 396, 397, 5, 101, 0, - 0, 397, 398, 5, 109, 0, 0, 398, 399, 5, 97, 0, 0, 399, 400, 5, 105, 0, - 0, 400, 401, 5, 110, 0, 0, 401, 402, 5, 105, 0, 0, 402, 403, 5, 110, 0, - 0, 403, 404, 5, 103, 0, 0, 404, 78, 1, 0, 0, 0, 405, 406, 5, 107, 0, 0, - 406, 407, 5, 101, 0, 0, 407, 408, 5, 112, 0, 0, 408, 409, 5, 116, 0, 0, - 409, 80, 1, 0, 0, 0, 410, 411, 5, 98, 0, 0, 411, 412, 5, 97, 0, 0, 412, - 413, 5, 108, 0, 0, 413, 414, 5, 97, 0, 0, 414, 415, 5, 110, 0, 0, 415, - 416, 5, 99, 0, 0, 416, 417, 5, 101, 0, 0, 417, 82, 1, 0, 0, 0, 418, 419, - 5, 115, 0, 0, 419, 420, 5, 97, 0, 0, 420, 421, 5, 118, 0, 0, 421, 422, - 5, 101, 0, 0, 422, 84, 1, 0, 0, 0, 423, 425, 7, 3, 0, 0, 424, 423, 1, 0, - 0, 0, 425, 426, 1, 0, 0, 0, 426, 424, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, - 427, 86, 1, 0, 0, 0, 428, 429, 5, 37, 0, 0, 429, 88, 1, 0, 0, 0, 430, 432, - 5, 36, 0, 0, 431, 433, 7, 5, 0, 0, 432, 431, 1, 0, 0, 0, 433, 434, 1, 0, - 0, 0, 434, 432, 1, 0, 0, 0, 434, 435, 1, 0, 0, 0, 435, 439, 1, 0, 0, 0, - 436, 438, 7, 6, 0, 0, 437, 436, 1, 0, 0, 0, 438, 441, 1, 0, 0, 0, 439, - 437, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 90, 1, 0, 0, 0, 441, 439, 1, - 0, 0, 0, 442, 444, 5, 64, 0, 0, 443, 445, 7, 7, 0, 0, 444, 443, 1, 0, 0, - 0, 445, 446, 1, 0, 0, 0, 446, 444, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, - 456, 1, 0, 0, 0, 448, 450, 5, 58, 0, 0, 449, 451, 7, 7, 0, 0, 450, 449, - 1, 0, 0, 0, 451, 452, 1, 0, 0, 0, 452, 450, 1, 0, 0, 0, 452, 453, 1, 0, - 0, 0, 453, 455, 1, 0, 0, 0, 454, 448, 1, 0, 0, 0, 455, 458, 1, 0, 0, 0, - 456, 454, 1, 0, 0, 0, 456, 457, 1, 0, 0, 0, 457, 92, 1, 0, 0, 0, 458, 456, - 1, 0, 0, 0, 459, 461, 7, 8, 0, 0, 460, 459, 1, 0, 0, 0, 461, 462, 1, 0, - 0, 0, 462, 460, 1, 0, 0, 0, 462, 463, 1, 0, 0, 0, 463, 94, 1, 0, 0, 0, - 23, 0, 156, 161, 170, 172, 186, 355, 357, 365, 368, 372, 377, 382, 388, - 390, 393, 426, 434, 439, 446, 452, 456, 462, 1, 6, 0, 0, - } - deserializer := antlr.NewATNDeserializer(nil) - staticData.atn = deserializer.Deserialize(staticData.serializedATN) - atn := staticData.atn - staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) - decisionToDFA := staticData.decisionToDFA - for index, state := range atn.DecisionToState { - decisionToDFA[index] = antlr.NewDFA(state, index) - } -} - -// NumScriptLexerInit initializes any static state used to implement NumScriptLexer. By default the -// static state used to implement the lexer is lazily initialized during the first call to -// NewNumScriptLexer(). You can call this function if you wish to initialize the static state ahead -// of time. -func NumScriptLexerInit() { - staticData := &numscriptlexerLexerStaticData - staticData.once.Do(numscriptlexerLexerInit) -} - -// NewNumScriptLexer produces a new lexer instance for the optional input antlr.CharStream. -func NewNumScriptLexer(input antlr.CharStream) *NumScriptLexer { - NumScriptLexerInit() - l := new(NumScriptLexer) - l.BaseLexer = antlr.NewBaseLexer(input) - staticData := &numscriptlexerLexerStaticData - l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.predictionContextCache) - l.channelNames = staticData.channelNames - l.modeNames = staticData.modeNames - l.RuleNames = staticData.ruleNames - l.LiteralNames = staticData.literalNames - l.SymbolicNames = staticData.symbolicNames - l.GrammarFileName = "NumScript.g4" - // TODO: l.EOF = antlr.TokenEOF - - return l -} - -// NumScriptLexer tokens. -const ( - NumScriptLexerT__0 = 1 - NumScriptLexerT__1 = 2 - NumScriptLexerT__2 = 3 - NumScriptLexerT__3 = 4 - NumScriptLexerNEWLINE = 5 - NumScriptLexerWHITESPACE = 6 - NumScriptLexerMULTILINE_COMMENT = 7 - NumScriptLexerLINE_COMMENT = 8 - NumScriptLexerVARS = 9 - NumScriptLexerMETA = 10 - NumScriptLexerSET_TX_META = 11 - NumScriptLexerSET_ACCOUNT_META = 12 - NumScriptLexerPRINT = 13 - NumScriptLexerFAIL = 14 - NumScriptLexerSEND = 15 - NumScriptLexerSOURCE = 16 - NumScriptLexerFROM = 17 - NumScriptLexerMAX = 18 - NumScriptLexerDESTINATION = 19 - NumScriptLexerTO = 20 - NumScriptLexerALLOCATE = 21 - NumScriptLexerOP_ADD = 22 - NumScriptLexerOP_SUB = 23 - NumScriptLexerLPAREN = 24 - NumScriptLexerRPAREN = 25 - NumScriptLexerLBRACK = 26 - NumScriptLexerRBRACK = 27 - NumScriptLexerLBRACE = 28 - NumScriptLexerRBRACE = 29 - NumScriptLexerEQ = 30 - NumScriptLexerTY_ACCOUNT = 31 - NumScriptLexerTY_ASSET = 32 - NumScriptLexerTY_NUMBER = 33 - NumScriptLexerTY_MONETARY = 34 - NumScriptLexerTY_PORTION = 35 - NumScriptLexerTY_STRING = 36 - NumScriptLexerSTRING = 37 - NumScriptLexerPORTION = 38 - NumScriptLexerREMAINING = 39 - NumScriptLexerKEPT = 40 - NumScriptLexerBALANCE = 41 - NumScriptLexerSAVE = 42 - NumScriptLexerNUMBER = 43 - NumScriptLexerPERCENT = 44 - NumScriptLexerVARIABLE_NAME = 45 - NumScriptLexerACCOUNT = 46 - NumScriptLexerASSET = 47 -) diff --git a/components/ledger/internal/machine/script/parser/numscript_listener.go b/components/ledger/internal/machine/script/parser/numscript_listener.go deleted file mode 100644 index bf9a31377f..0000000000 --- a/components/ledger/internal/machine/script/parser/numscript_listener.go +++ /dev/null @@ -1,280 +0,0 @@ -// Code generated from NumScript.g4 by ANTLR 4.10.1. DO NOT EDIT. - -package parser // NumScript - -import "github.com/antlr/antlr4/runtime/Go/antlr" - -// NumScriptListener is a complete listener for a parse tree produced by NumScriptParser. -type NumScriptListener interface { - antlr.ParseTreeListener - - // EnterMonetary is called when entering the monetary production. - EnterMonetary(c *MonetaryContext) - - // EnterMonetaryAll is called when entering the monetaryAll production. - EnterMonetaryAll(c *MonetaryAllContext) - - // EnterLitAccount is called when entering the LitAccount production. - EnterLitAccount(c *LitAccountContext) - - // EnterLitAsset is called when entering the LitAsset production. - EnterLitAsset(c *LitAssetContext) - - // EnterLitNumber is called when entering the LitNumber production. - EnterLitNumber(c *LitNumberContext) - - // EnterLitString is called when entering the LitString production. - EnterLitString(c *LitStringContext) - - // EnterLitPortion is called when entering the LitPortion production. - EnterLitPortion(c *LitPortionContext) - - // EnterLitMonetary is called when entering the LitMonetary production. - EnterLitMonetary(c *LitMonetaryContext) - - // EnterVariable is called when entering the variable production. - EnterVariable(c *VariableContext) - - // EnterExprAddSub is called when entering the ExprAddSub production. - EnterExprAddSub(c *ExprAddSubContext) - - // EnterExprLiteral is called when entering the ExprLiteral production. - EnterExprLiteral(c *ExprLiteralContext) - - // EnterExprVariable is called when entering the ExprVariable production. - EnterExprVariable(c *ExprVariableContext) - - // EnterAllotmentPortionConst is called when entering the AllotmentPortionConst production. - EnterAllotmentPortionConst(c *AllotmentPortionConstContext) - - // EnterAllotmentPortionVar is called when entering the AllotmentPortionVar production. - EnterAllotmentPortionVar(c *AllotmentPortionVarContext) - - // EnterAllotmentPortionRemaining is called when entering the AllotmentPortionRemaining production. - EnterAllotmentPortionRemaining(c *AllotmentPortionRemainingContext) - - // EnterDestinationInOrder is called when entering the destinationInOrder production. - EnterDestinationInOrder(c *DestinationInOrderContext) - - // EnterDestinationAllotment is called when entering the destinationAllotment production. - EnterDestinationAllotment(c *DestinationAllotmentContext) - - // EnterIsDestination is called when entering the IsDestination production. - EnterIsDestination(c *IsDestinationContext) - - // EnterIsKept is called when entering the IsKept production. - EnterIsKept(c *IsKeptContext) - - // EnterDestAccount is called when entering the DestAccount production. - EnterDestAccount(c *DestAccountContext) - - // EnterDestInOrder is called when entering the DestInOrder production. - EnterDestInOrder(c *DestInOrderContext) - - // EnterDestAllotment is called when entering the DestAllotment production. - EnterDestAllotment(c *DestAllotmentContext) - - // EnterSrcAccountOverdraftSpecific is called when entering the SrcAccountOverdraftSpecific production. - EnterSrcAccountOverdraftSpecific(c *SrcAccountOverdraftSpecificContext) - - // EnterSrcAccountOverdraftUnbounded is called when entering the SrcAccountOverdraftUnbounded production. - EnterSrcAccountOverdraftUnbounded(c *SrcAccountOverdraftUnboundedContext) - - // EnterSourceAccount is called when entering the sourceAccount production. - EnterSourceAccount(c *SourceAccountContext) - - // EnterSourceInOrder is called when entering the sourceInOrder production. - EnterSourceInOrder(c *SourceInOrderContext) - - // EnterSourceMaxed is called when entering the sourceMaxed production. - EnterSourceMaxed(c *SourceMaxedContext) - - // EnterSrcAccount is called when entering the SrcAccount production. - EnterSrcAccount(c *SrcAccountContext) - - // EnterSrcMaxed is called when entering the SrcMaxed production. - EnterSrcMaxed(c *SrcMaxedContext) - - // EnterSrcInOrder is called when entering the SrcInOrder production. - EnterSrcInOrder(c *SrcInOrderContext) - - // EnterSourceAllotment is called when entering the sourceAllotment production. - EnterSourceAllotment(c *SourceAllotmentContext) - - // EnterSrc is called when entering the Src production. - EnterSrc(c *SrcContext) - - // EnterSrcAllotment is called when entering the SrcAllotment production. - EnterSrcAllotment(c *SrcAllotmentContext) - - // EnterPrint is called when entering the Print production. - EnterPrint(c *PrintContext) - - // EnterSaveFromAccount is called when entering the SaveFromAccount production. - EnterSaveFromAccount(c *SaveFromAccountContext) - - // EnterSetTxMeta is called when entering the SetTxMeta production. - EnterSetTxMeta(c *SetTxMetaContext) - - // EnterSetAccountMeta is called when entering the SetAccountMeta production. - EnterSetAccountMeta(c *SetAccountMetaContext) - - // EnterFail is called when entering the Fail production. - EnterFail(c *FailContext) - - // EnterSend is called when entering the Send production. - EnterSend(c *SendContext) - - // EnterType_ is called when entering the type_ production. - EnterType_(c *Type_Context) - - // EnterOriginAccountMeta is called when entering the OriginAccountMeta production. - EnterOriginAccountMeta(c *OriginAccountMetaContext) - - // EnterOriginAccountBalance is called when entering the OriginAccountBalance production. - EnterOriginAccountBalance(c *OriginAccountBalanceContext) - - // EnterVarDecl is called when entering the varDecl production. - EnterVarDecl(c *VarDeclContext) - - // EnterVarListDecl is called when entering the varListDecl production. - EnterVarListDecl(c *VarListDeclContext) - - // EnterScript is called when entering the script production. - EnterScript(c *ScriptContext) - - // ExitMonetary is called when exiting the monetary production. - ExitMonetary(c *MonetaryContext) - - // ExitMonetaryAll is called when exiting the monetaryAll production. - ExitMonetaryAll(c *MonetaryAllContext) - - // ExitLitAccount is called when exiting the LitAccount production. - ExitLitAccount(c *LitAccountContext) - - // ExitLitAsset is called when exiting the LitAsset production. - ExitLitAsset(c *LitAssetContext) - - // ExitLitNumber is called when exiting the LitNumber production. - ExitLitNumber(c *LitNumberContext) - - // ExitLitString is called when exiting the LitString production. - ExitLitString(c *LitStringContext) - - // ExitLitPortion is called when exiting the LitPortion production. - ExitLitPortion(c *LitPortionContext) - - // ExitLitMonetary is called when exiting the LitMonetary production. - ExitLitMonetary(c *LitMonetaryContext) - - // ExitVariable is called when exiting the variable production. - ExitVariable(c *VariableContext) - - // ExitExprAddSub is called when exiting the ExprAddSub production. - ExitExprAddSub(c *ExprAddSubContext) - - // ExitExprLiteral is called when exiting the ExprLiteral production. - ExitExprLiteral(c *ExprLiteralContext) - - // ExitExprVariable is called when exiting the ExprVariable production. - ExitExprVariable(c *ExprVariableContext) - - // ExitAllotmentPortionConst is called when exiting the AllotmentPortionConst production. - ExitAllotmentPortionConst(c *AllotmentPortionConstContext) - - // ExitAllotmentPortionVar is called when exiting the AllotmentPortionVar production. - ExitAllotmentPortionVar(c *AllotmentPortionVarContext) - - // ExitAllotmentPortionRemaining is called when exiting the AllotmentPortionRemaining production. - ExitAllotmentPortionRemaining(c *AllotmentPortionRemainingContext) - - // ExitDestinationInOrder is called when exiting the destinationInOrder production. - ExitDestinationInOrder(c *DestinationInOrderContext) - - // ExitDestinationAllotment is called when exiting the destinationAllotment production. - ExitDestinationAllotment(c *DestinationAllotmentContext) - - // ExitIsDestination is called when exiting the IsDestination production. - ExitIsDestination(c *IsDestinationContext) - - // ExitIsKept is called when exiting the IsKept production. - ExitIsKept(c *IsKeptContext) - - // ExitDestAccount is called when exiting the DestAccount production. - ExitDestAccount(c *DestAccountContext) - - // ExitDestInOrder is called when exiting the DestInOrder production. - ExitDestInOrder(c *DestInOrderContext) - - // ExitDestAllotment is called when exiting the DestAllotment production. - ExitDestAllotment(c *DestAllotmentContext) - - // ExitSrcAccountOverdraftSpecific is called when exiting the SrcAccountOverdraftSpecific production. - ExitSrcAccountOverdraftSpecific(c *SrcAccountOverdraftSpecificContext) - - // ExitSrcAccountOverdraftUnbounded is called when exiting the SrcAccountOverdraftUnbounded production. - ExitSrcAccountOverdraftUnbounded(c *SrcAccountOverdraftUnboundedContext) - - // ExitSourceAccount is called when exiting the sourceAccount production. - ExitSourceAccount(c *SourceAccountContext) - - // ExitSourceInOrder is called when exiting the sourceInOrder production. - ExitSourceInOrder(c *SourceInOrderContext) - - // ExitSourceMaxed is called when exiting the sourceMaxed production. - ExitSourceMaxed(c *SourceMaxedContext) - - // ExitSrcAccount is called when exiting the SrcAccount production. - ExitSrcAccount(c *SrcAccountContext) - - // ExitSrcMaxed is called when exiting the SrcMaxed production. - ExitSrcMaxed(c *SrcMaxedContext) - - // ExitSrcInOrder is called when exiting the SrcInOrder production. - ExitSrcInOrder(c *SrcInOrderContext) - - // ExitSourceAllotment is called when exiting the sourceAllotment production. - ExitSourceAllotment(c *SourceAllotmentContext) - - // ExitSrc is called when exiting the Src production. - ExitSrc(c *SrcContext) - - // ExitSrcAllotment is called when exiting the SrcAllotment production. - ExitSrcAllotment(c *SrcAllotmentContext) - - // ExitPrint is called when exiting the Print production. - ExitPrint(c *PrintContext) - - // ExitSaveFromAccount is called when exiting the SaveFromAccount production. - ExitSaveFromAccount(c *SaveFromAccountContext) - - // ExitSetTxMeta is called when exiting the SetTxMeta production. - ExitSetTxMeta(c *SetTxMetaContext) - - // ExitSetAccountMeta is called when exiting the SetAccountMeta production. - ExitSetAccountMeta(c *SetAccountMetaContext) - - // ExitFail is called when exiting the Fail production. - ExitFail(c *FailContext) - - // ExitSend is called when exiting the Send production. - ExitSend(c *SendContext) - - // ExitType_ is called when exiting the type_ production. - ExitType_(c *Type_Context) - - // ExitOriginAccountMeta is called when exiting the OriginAccountMeta production. - ExitOriginAccountMeta(c *OriginAccountMetaContext) - - // ExitOriginAccountBalance is called when exiting the OriginAccountBalance production. - ExitOriginAccountBalance(c *OriginAccountBalanceContext) - - // ExitVarDecl is called when exiting the varDecl production. - ExitVarDecl(c *VarDeclContext) - - // ExitVarListDecl is called when exiting the varListDecl production. - ExitVarListDecl(c *VarListDeclContext) - - // ExitScript is called when exiting the script production. - ExitScript(c *ScriptContext) -} diff --git a/components/ledger/internal/machine/script/parser/numscript_parser.go b/components/ledger/internal/machine/script/parser/numscript_parser.go deleted file mode 100644 index bc1b1a0fdf..0000000000 --- a/components/ledger/internal/machine/script/parser/numscript_parser.go +++ /dev/null @@ -1,6148 +0,0 @@ -// Code generated from NumScript.g4 by ANTLR 4.10.1. DO NOT EDIT. - -package parser // NumScript - -import ( - "fmt" - "strconv" - "sync" - - "github.com/antlr/antlr4/runtime/Go/antlr" -) - -// Suppress unused import errors -var _ = fmt.Printf -var _ = strconv.Itoa -var _ = sync.Once{} - -type NumScriptParser struct { - *antlr.BaseParser -} - -var numscriptParserStaticData struct { - once sync.Once - serializedATN []int32 - literalNames []string - symbolicNames []string - ruleNames []string - predictionContextCache *antlr.PredictionContextCache - atn *antlr.ATN - decisionToDFA []*antlr.DFA -} - -func numscriptParserInit() { - staticData := &numscriptParserStaticData - staticData.literalNames = []string{ - "", "'*'", "'allowing overdraft up to'", "'allowing unbounded overdraft'", - "','", "", "", "", "", "'vars'", "'meta'", "'set_tx_meta'", "'set_account_meta'", - "'print'", "'fail'", "'send'", "'source'", "'from'", "'max'", "'destination'", - "'to'", "'allocate'", "'+'", "'-'", "'('", "')'", "'['", "']'", "'{'", - "'}'", "'='", "'account'", "'asset'", "'number'", "'monetary'", "'portion'", - "'string'", "", "", "'remaining'", "'kept'", "'balance'", "'save'", - "", "'%'", - } - staticData.symbolicNames = []string{ - "", "", "", "", "", "NEWLINE", "WHITESPACE", "MULTILINE_COMMENT", "LINE_COMMENT", - "VARS", "META", "SET_TX_META", "SET_ACCOUNT_META", "PRINT", "FAIL", - "SEND", "SOURCE", "FROM", "MAX", "DESTINATION", "TO", "ALLOCATE", "OP_ADD", - "OP_SUB", "LPAREN", "RPAREN", "LBRACK", "RBRACK", "LBRACE", "RBRACE", - "EQ", "TY_ACCOUNT", "TY_ASSET", "TY_NUMBER", "TY_MONETARY", "TY_PORTION", - "TY_STRING", "STRING", "PORTION", "REMAINING", "KEPT", "BALANCE", "SAVE", - "NUMBER", "PERCENT", "VARIABLE_NAME", "ACCOUNT", "ASSET", - } - staticData.ruleNames = []string{ - "monetary", "monetaryAll", "literal", "variable", "expression", "allotmentPortion", - "destinationInOrder", "destinationAllotment", "keptOrDestination", "destination", - "sourceAccountOverdraft", "sourceAccount", "sourceInOrder", "sourceMaxed", - "source", "sourceAllotment", "valueAwareSource", "statement", "type_", - "origin", "varDecl", "varListDecl", "script", - } - staticData.predictionContextCache = antlr.NewPredictionContextCache() - staticData.serializedATN = []int32{ - 4, 1, 47, 292, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, - 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, - 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, - 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, - 21, 7, 21, 2, 22, 7, 22, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 63, 8, 2, 1, 3, 1, - 3, 1, 4, 1, 4, 1, 4, 3, 4, 70, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 75, 8, 4, - 10, 4, 12, 4, 78, 9, 4, 1, 5, 1, 5, 1, 5, 3, 5, 83, 8, 5, 1, 6, 1, 6, 1, - 6, 1, 6, 1, 6, 1, 6, 1, 6, 4, 6, 92, 8, 6, 11, 6, 12, 6, 93, 1, 6, 1, 6, - 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 4, 7, 107, 8, 7, - 11, 7, 12, 7, 108, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 3, 8, 116, 8, 8, 1, 9, - 1, 9, 1, 9, 3, 9, 121, 8, 9, 1, 10, 1, 10, 1, 10, 3, 10, 126, 8, 10, 1, - 11, 1, 11, 3, 11, 130, 8, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 4, 12, - 137, 8, 12, 11, 12, 12, 12, 138, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, - 13, 1, 13, 1, 14, 1, 14, 1, 14, 3, 14, 151, 8, 14, 1, 15, 1, 15, 1, 15, - 1, 15, 1, 15, 1, 15, 1, 15, 4, 15, 160, 8, 15, 11, 15, 12, 15, 161, 1, - 15, 1, 15, 1, 16, 1, 16, 3, 16, 168, 8, 16, 1, 17, 1, 17, 1, 17, 1, 17, - 1, 17, 3, 17, 175, 8, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, - 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, - 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 200, 8, 17, 1, 17, 1, - 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, - 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 220, 8, 17, 1, 17, 1, - 17, 1, 17, 3, 17, 225, 8, 17, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, - 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, - 19, 243, 8, 19, 1, 20, 1, 20, 1, 20, 1, 20, 3, 20, 249, 8, 20, 1, 21, 1, - 21, 1, 21, 1, 21, 1, 21, 4, 21, 256, 8, 21, 11, 21, 12, 21, 257, 4, 21, - 260, 8, 21, 11, 21, 12, 21, 261, 1, 21, 1, 21, 1, 21, 1, 22, 5, 22, 268, - 8, 22, 10, 22, 12, 22, 271, 9, 22, 1, 22, 3, 22, 274, 8, 22, 1, 22, 1, - 22, 1, 22, 5, 22, 279, 8, 22, 10, 22, 12, 22, 282, 9, 22, 1, 22, 5, 22, - 285, 8, 22, 10, 22, 12, 22, 288, 9, 22, 1, 22, 1, 22, 1, 22, 0, 1, 8, 23, - 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, - 38, 40, 42, 44, 0, 2, 1, 0, 22, 23, 1, 0, 31, 36, 305, 0, 46, 1, 0, 0, - 0, 2, 51, 1, 0, 0, 0, 4, 62, 1, 0, 0, 0, 6, 64, 1, 0, 0, 0, 8, 69, 1, 0, - 0, 0, 10, 82, 1, 0, 0, 0, 12, 84, 1, 0, 0, 0, 14, 100, 1, 0, 0, 0, 16, - 115, 1, 0, 0, 0, 18, 120, 1, 0, 0, 0, 20, 125, 1, 0, 0, 0, 22, 127, 1, - 0, 0, 0, 24, 131, 1, 0, 0, 0, 26, 142, 1, 0, 0, 0, 28, 150, 1, 0, 0, 0, - 30, 152, 1, 0, 0, 0, 32, 167, 1, 0, 0, 0, 34, 224, 1, 0, 0, 0, 36, 226, - 1, 0, 0, 0, 38, 242, 1, 0, 0, 0, 40, 244, 1, 0, 0, 0, 42, 250, 1, 0, 0, - 0, 44, 269, 1, 0, 0, 0, 46, 47, 5, 26, 0, 0, 47, 48, 3, 8, 4, 0, 48, 49, - 5, 43, 0, 0, 49, 50, 5, 27, 0, 0, 50, 1, 1, 0, 0, 0, 51, 52, 5, 26, 0, - 0, 52, 53, 3, 8, 4, 0, 53, 54, 5, 1, 0, 0, 54, 55, 5, 27, 0, 0, 55, 3, - 1, 0, 0, 0, 56, 63, 5, 46, 0, 0, 57, 63, 5, 47, 0, 0, 58, 63, 5, 43, 0, - 0, 59, 63, 5, 37, 0, 0, 60, 63, 5, 38, 0, 0, 61, 63, 3, 0, 0, 0, 62, 56, - 1, 0, 0, 0, 62, 57, 1, 0, 0, 0, 62, 58, 1, 0, 0, 0, 62, 59, 1, 0, 0, 0, - 62, 60, 1, 0, 0, 0, 62, 61, 1, 0, 0, 0, 63, 5, 1, 0, 0, 0, 64, 65, 5, 45, - 0, 0, 65, 7, 1, 0, 0, 0, 66, 67, 6, 4, -1, 0, 67, 70, 3, 4, 2, 0, 68, 70, - 3, 6, 3, 0, 69, 66, 1, 0, 0, 0, 69, 68, 1, 0, 0, 0, 70, 76, 1, 0, 0, 0, - 71, 72, 10, 3, 0, 0, 72, 73, 7, 0, 0, 0, 73, 75, 3, 8, 4, 4, 74, 71, 1, - 0, 0, 0, 75, 78, 1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 76, 77, 1, 0, 0, 0, 77, - 9, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 79, 83, 5, 38, 0, 0, 80, 83, 3, 6, 3, - 0, 81, 83, 5, 39, 0, 0, 82, 79, 1, 0, 0, 0, 82, 80, 1, 0, 0, 0, 82, 81, - 1, 0, 0, 0, 83, 11, 1, 0, 0, 0, 84, 85, 5, 28, 0, 0, 85, 91, 5, 5, 0, 0, - 86, 87, 5, 18, 0, 0, 87, 88, 3, 8, 4, 0, 88, 89, 3, 16, 8, 0, 89, 90, 5, - 5, 0, 0, 90, 92, 1, 0, 0, 0, 91, 86, 1, 0, 0, 0, 92, 93, 1, 0, 0, 0, 93, - 91, 1, 0, 0, 0, 93, 94, 1, 0, 0, 0, 94, 95, 1, 0, 0, 0, 95, 96, 5, 39, - 0, 0, 96, 97, 3, 16, 8, 0, 97, 98, 5, 5, 0, 0, 98, 99, 5, 29, 0, 0, 99, - 13, 1, 0, 0, 0, 100, 101, 5, 28, 0, 0, 101, 106, 5, 5, 0, 0, 102, 103, - 3, 10, 5, 0, 103, 104, 3, 16, 8, 0, 104, 105, 5, 5, 0, 0, 105, 107, 1, - 0, 0, 0, 106, 102, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 106, 1, 0, 0, - 0, 108, 109, 1, 0, 0, 0, 109, 110, 1, 0, 0, 0, 110, 111, 5, 29, 0, 0, 111, - 15, 1, 0, 0, 0, 112, 113, 5, 20, 0, 0, 113, 116, 3, 18, 9, 0, 114, 116, - 5, 40, 0, 0, 115, 112, 1, 0, 0, 0, 115, 114, 1, 0, 0, 0, 116, 17, 1, 0, - 0, 0, 117, 121, 3, 8, 4, 0, 118, 121, 3, 12, 6, 0, 119, 121, 3, 14, 7, - 0, 120, 117, 1, 0, 0, 0, 120, 118, 1, 0, 0, 0, 120, 119, 1, 0, 0, 0, 121, - 19, 1, 0, 0, 0, 122, 123, 5, 2, 0, 0, 123, 126, 3, 8, 4, 0, 124, 126, 5, - 3, 0, 0, 125, 122, 1, 0, 0, 0, 125, 124, 1, 0, 0, 0, 126, 21, 1, 0, 0, - 0, 127, 129, 3, 8, 4, 0, 128, 130, 3, 20, 10, 0, 129, 128, 1, 0, 0, 0, - 129, 130, 1, 0, 0, 0, 130, 23, 1, 0, 0, 0, 131, 132, 5, 28, 0, 0, 132, - 136, 5, 5, 0, 0, 133, 134, 3, 28, 14, 0, 134, 135, 5, 5, 0, 0, 135, 137, - 1, 0, 0, 0, 136, 133, 1, 0, 0, 0, 137, 138, 1, 0, 0, 0, 138, 136, 1, 0, - 0, 0, 138, 139, 1, 0, 0, 0, 139, 140, 1, 0, 0, 0, 140, 141, 5, 29, 0, 0, - 141, 25, 1, 0, 0, 0, 142, 143, 5, 18, 0, 0, 143, 144, 3, 8, 4, 0, 144, - 145, 5, 17, 0, 0, 145, 146, 3, 28, 14, 0, 146, 27, 1, 0, 0, 0, 147, 151, - 3, 22, 11, 0, 148, 151, 3, 26, 13, 0, 149, 151, 3, 24, 12, 0, 150, 147, - 1, 0, 0, 0, 150, 148, 1, 0, 0, 0, 150, 149, 1, 0, 0, 0, 151, 29, 1, 0, - 0, 0, 152, 153, 5, 28, 0, 0, 153, 159, 5, 5, 0, 0, 154, 155, 3, 10, 5, - 0, 155, 156, 5, 17, 0, 0, 156, 157, 3, 28, 14, 0, 157, 158, 5, 5, 0, 0, - 158, 160, 1, 0, 0, 0, 159, 154, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, - 159, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 163, 1, 0, 0, 0, 163, 164, - 5, 29, 0, 0, 164, 31, 1, 0, 0, 0, 165, 168, 3, 28, 14, 0, 166, 168, 3, - 30, 15, 0, 167, 165, 1, 0, 0, 0, 167, 166, 1, 0, 0, 0, 168, 33, 1, 0, 0, - 0, 169, 170, 5, 13, 0, 0, 170, 225, 3, 8, 4, 0, 171, 174, 5, 42, 0, 0, - 172, 175, 3, 8, 4, 0, 173, 175, 3, 2, 1, 0, 174, 172, 1, 0, 0, 0, 174, - 173, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 5, 17, 0, 0, 177, 178, - 3, 8, 4, 0, 178, 225, 1, 0, 0, 0, 179, 180, 5, 11, 0, 0, 180, 181, 5, 24, - 0, 0, 181, 182, 5, 37, 0, 0, 182, 183, 5, 4, 0, 0, 183, 184, 3, 8, 4, 0, - 184, 185, 5, 25, 0, 0, 185, 225, 1, 0, 0, 0, 186, 187, 5, 12, 0, 0, 187, - 188, 5, 24, 0, 0, 188, 189, 3, 8, 4, 0, 189, 190, 5, 4, 0, 0, 190, 191, - 5, 37, 0, 0, 191, 192, 5, 4, 0, 0, 192, 193, 3, 8, 4, 0, 193, 194, 5, 25, - 0, 0, 194, 225, 1, 0, 0, 0, 195, 225, 5, 14, 0, 0, 196, 199, 5, 15, 0, - 0, 197, 200, 3, 8, 4, 0, 198, 200, 3, 2, 1, 0, 199, 197, 1, 0, 0, 0, 199, - 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 202, 5, 24, 0, 0, 202, 219, - 5, 5, 0, 0, 203, 204, 5, 16, 0, 0, 204, 205, 5, 30, 0, 0, 205, 206, 3, - 32, 16, 0, 206, 207, 5, 5, 0, 0, 207, 208, 5, 19, 0, 0, 208, 209, 5, 30, - 0, 0, 209, 210, 3, 18, 9, 0, 210, 220, 1, 0, 0, 0, 211, 212, 5, 19, 0, - 0, 212, 213, 5, 30, 0, 0, 213, 214, 3, 18, 9, 0, 214, 215, 5, 5, 0, 0, - 215, 216, 5, 16, 0, 0, 216, 217, 5, 30, 0, 0, 217, 218, 3, 32, 16, 0, 218, - 220, 1, 0, 0, 0, 219, 203, 1, 0, 0, 0, 219, 211, 1, 0, 0, 0, 220, 221, - 1, 0, 0, 0, 221, 222, 5, 5, 0, 0, 222, 223, 5, 25, 0, 0, 223, 225, 1, 0, - 0, 0, 224, 169, 1, 0, 0, 0, 224, 171, 1, 0, 0, 0, 224, 179, 1, 0, 0, 0, - 224, 186, 1, 0, 0, 0, 224, 195, 1, 0, 0, 0, 224, 196, 1, 0, 0, 0, 225, - 35, 1, 0, 0, 0, 226, 227, 7, 1, 0, 0, 227, 37, 1, 0, 0, 0, 228, 229, 5, - 10, 0, 0, 229, 230, 5, 24, 0, 0, 230, 231, 3, 8, 4, 0, 231, 232, 5, 4, - 0, 0, 232, 233, 5, 37, 0, 0, 233, 234, 5, 25, 0, 0, 234, 243, 1, 0, 0, - 0, 235, 236, 5, 41, 0, 0, 236, 237, 5, 24, 0, 0, 237, 238, 3, 8, 4, 0, - 238, 239, 5, 4, 0, 0, 239, 240, 3, 8, 4, 0, 240, 241, 5, 25, 0, 0, 241, - 243, 1, 0, 0, 0, 242, 228, 1, 0, 0, 0, 242, 235, 1, 0, 0, 0, 243, 39, 1, - 0, 0, 0, 244, 245, 3, 36, 18, 0, 245, 248, 3, 6, 3, 0, 246, 247, 5, 30, - 0, 0, 247, 249, 3, 38, 19, 0, 248, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, - 0, 249, 41, 1, 0, 0, 0, 250, 251, 5, 9, 0, 0, 251, 252, 5, 28, 0, 0, 252, - 259, 5, 5, 0, 0, 253, 255, 3, 40, 20, 0, 254, 256, 5, 5, 0, 0, 255, 254, - 1, 0, 0, 0, 256, 257, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 257, 258, 1, 0, - 0, 0, 258, 260, 1, 0, 0, 0, 259, 253, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, - 261, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, - 264, 5, 29, 0, 0, 264, 265, 5, 5, 0, 0, 265, 43, 1, 0, 0, 0, 266, 268, - 5, 5, 0, 0, 267, 266, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, - 0, 0, 269, 270, 1, 0, 0, 0, 270, 273, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, - 272, 274, 3, 42, 21, 0, 273, 272, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, - 275, 1, 0, 0, 0, 275, 280, 3, 34, 17, 0, 276, 277, 5, 5, 0, 0, 277, 279, - 3, 34, 17, 0, 278, 276, 1, 0, 0, 0, 279, 282, 1, 0, 0, 0, 280, 278, 1, - 0, 0, 0, 280, 281, 1, 0, 0, 0, 281, 286, 1, 0, 0, 0, 282, 280, 1, 0, 0, - 0, 283, 285, 5, 5, 0, 0, 284, 283, 1, 0, 0, 0, 285, 288, 1, 0, 0, 0, 286, - 284, 1, 0, 0, 0, 286, 287, 1, 0, 0, 0, 287, 289, 1, 0, 0, 0, 288, 286, - 1, 0, 0, 0, 289, 290, 5, 0, 0, 1, 290, 45, 1, 0, 0, 0, 26, 62, 69, 76, - 82, 93, 108, 115, 120, 125, 129, 138, 150, 161, 167, 174, 199, 219, 224, - 242, 248, 257, 261, 269, 273, 280, 286, - } - deserializer := antlr.NewATNDeserializer(nil) - staticData.atn = deserializer.Deserialize(staticData.serializedATN) - atn := staticData.atn - staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) - decisionToDFA := staticData.decisionToDFA - for index, state := range atn.DecisionToState { - decisionToDFA[index] = antlr.NewDFA(state, index) - } -} - -// NumScriptParserInit initializes any static state used to implement NumScriptParser. By default the -// static state used to implement the parser is lazily initialized during the first call to -// NewNumScriptParser(). You can call this function if you wish to initialize the static state ahead -// of time. -func NumScriptParserInit() { - staticData := &numscriptParserStaticData - staticData.once.Do(numscriptParserInit) -} - -// NewNumScriptParser produces a new parser instance for the optional input antlr.TokenStream. -func NewNumScriptParser(input antlr.TokenStream) *NumScriptParser { - NumScriptParserInit() - this := new(NumScriptParser) - this.BaseParser = antlr.NewBaseParser(input) - staticData := &numscriptParserStaticData - this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.predictionContextCache) - this.RuleNames = staticData.ruleNames - this.LiteralNames = staticData.literalNames - this.SymbolicNames = staticData.symbolicNames - this.GrammarFileName = "NumScript.g4" - - return this -} - -// NumScriptParser tokens. -const ( - NumScriptParserEOF = antlr.TokenEOF - NumScriptParserT__0 = 1 - NumScriptParserT__1 = 2 - NumScriptParserT__2 = 3 - NumScriptParserT__3 = 4 - NumScriptParserNEWLINE = 5 - NumScriptParserWHITESPACE = 6 - NumScriptParserMULTILINE_COMMENT = 7 - NumScriptParserLINE_COMMENT = 8 - NumScriptParserVARS = 9 - NumScriptParserMETA = 10 - NumScriptParserSET_TX_META = 11 - NumScriptParserSET_ACCOUNT_META = 12 - NumScriptParserPRINT = 13 - NumScriptParserFAIL = 14 - NumScriptParserSEND = 15 - NumScriptParserSOURCE = 16 - NumScriptParserFROM = 17 - NumScriptParserMAX = 18 - NumScriptParserDESTINATION = 19 - NumScriptParserTO = 20 - NumScriptParserALLOCATE = 21 - NumScriptParserOP_ADD = 22 - NumScriptParserOP_SUB = 23 - NumScriptParserLPAREN = 24 - NumScriptParserRPAREN = 25 - NumScriptParserLBRACK = 26 - NumScriptParserRBRACK = 27 - NumScriptParserLBRACE = 28 - NumScriptParserRBRACE = 29 - NumScriptParserEQ = 30 - NumScriptParserTY_ACCOUNT = 31 - NumScriptParserTY_ASSET = 32 - NumScriptParserTY_NUMBER = 33 - NumScriptParserTY_MONETARY = 34 - NumScriptParserTY_PORTION = 35 - NumScriptParserTY_STRING = 36 - NumScriptParserSTRING = 37 - NumScriptParserPORTION = 38 - NumScriptParserREMAINING = 39 - NumScriptParserKEPT = 40 - NumScriptParserBALANCE = 41 - NumScriptParserSAVE = 42 - NumScriptParserNUMBER = 43 - NumScriptParserPERCENT = 44 - NumScriptParserVARIABLE_NAME = 45 - NumScriptParserACCOUNT = 46 - NumScriptParserASSET = 47 -) - -// NumScriptParser rules. -const ( - NumScriptParserRULE_monetary = 0 - NumScriptParserRULE_monetaryAll = 1 - NumScriptParserRULE_literal = 2 - NumScriptParserRULE_variable = 3 - NumScriptParserRULE_expression = 4 - NumScriptParserRULE_allotmentPortion = 5 - NumScriptParserRULE_destinationInOrder = 6 - NumScriptParserRULE_destinationAllotment = 7 - NumScriptParserRULE_keptOrDestination = 8 - NumScriptParserRULE_destination = 9 - NumScriptParserRULE_sourceAccountOverdraft = 10 - NumScriptParserRULE_sourceAccount = 11 - NumScriptParserRULE_sourceInOrder = 12 - NumScriptParserRULE_sourceMaxed = 13 - NumScriptParserRULE_source = 14 - NumScriptParserRULE_sourceAllotment = 15 - NumScriptParserRULE_valueAwareSource = 16 - NumScriptParserRULE_statement = 17 - NumScriptParserRULE_type_ = 18 - NumScriptParserRULE_origin = 19 - NumScriptParserRULE_varDecl = 20 - NumScriptParserRULE_varListDecl = 21 - NumScriptParserRULE_script = 22 -) - -// IMonetaryContext is an interface to support dynamic dispatch. -type IMonetaryContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // GetAmt returns the amt token. - GetAmt() antlr.Token - - // SetAmt sets the amt token. - SetAmt(antlr.Token) - - // GetAsset returns the asset rule contexts. - GetAsset() IExpressionContext - - // SetAsset sets the asset rule contexts. - SetAsset(IExpressionContext) - - // IsMonetaryContext differentiates from other interfaces. - IsMonetaryContext() -} - -type MonetaryContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser - asset IExpressionContext - amt antlr.Token -} - -func NewEmptyMonetaryContext() *MonetaryContext { - var p = new(MonetaryContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_monetary - return p -} - -func (*MonetaryContext) IsMonetaryContext() {} - -func NewMonetaryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MonetaryContext { - var p = new(MonetaryContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_monetary - - return p -} - -func (s *MonetaryContext) GetParser() antlr.Parser { return s.parser } - -func (s *MonetaryContext) GetAmt() antlr.Token { return s.amt } - -func (s *MonetaryContext) SetAmt(v antlr.Token) { s.amt = v } - -func (s *MonetaryContext) GetAsset() IExpressionContext { return s.asset } - -func (s *MonetaryContext) SetAsset(v IExpressionContext) { s.asset = v } - -func (s *MonetaryContext) LBRACK() antlr.TerminalNode { - return s.GetToken(NumScriptParserLBRACK, 0) -} - -func (s *MonetaryContext) RBRACK() antlr.TerminalNode { - return s.GetToken(NumScriptParserRBRACK, 0) -} - -func (s *MonetaryContext) Expression() IExpressionContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IExpressionContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(IExpressionContext) -} - -func (s *MonetaryContext) NUMBER() antlr.TerminalNode { - return s.GetToken(NumScriptParserNUMBER, 0) -} - -func (s *MonetaryContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *MonetaryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -func (s *MonetaryContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterMonetary(s) - } -} - -func (s *MonetaryContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitMonetary(s) - } -} - -func (p *NumScriptParser) Monetary() (localctx IMonetaryContext) { - this := p - _ = this - - localctx = NewMonetaryContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 0, NumScriptParserRULE_monetary) - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.EnterOuterAlt(localctx, 1) - { - p.SetState(46) - p.Match(NumScriptParserLBRACK) - } - { - p.SetState(47) - - var _x = p.expression(0) - - localctx.(*MonetaryContext).asset = _x - } - { - p.SetState(48) - - var _m = p.Match(NumScriptParserNUMBER) - - localctx.(*MonetaryContext).amt = _m - } - { - p.SetState(49) - p.Match(NumScriptParserRBRACK) - } - - return localctx -} - -// IMonetaryAllContext is an interface to support dynamic dispatch. -type IMonetaryAllContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // GetAsset returns the asset rule contexts. - GetAsset() IExpressionContext - - // SetAsset sets the asset rule contexts. - SetAsset(IExpressionContext) - - // IsMonetaryAllContext differentiates from other interfaces. - IsMonetaryAllContext() -} - -type MonetaryAllContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser - asset IExpressionContext -} - -func NewEmptyMonetaryAllContext() *MonetaryAllContext { - var p = new(MonetaryAllContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_monetaryAll - return p -} - -func (*MonetaryAllContext) IsMonetaryAllContext() {} - -func NewMonetaryAllContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MonetaryAllContext { - var p = new(MonetaryAllContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_monetaryAll - - return p -} - -func (s *MonetaryAllContext) GetParser() antlr.Parser { return s.parser } - -func (s *MonetaryAllContext) GetAsset() IExpressionContext { return s.asset } - -func (s *MonetaryAllContext) SetAsset(v IExpressionContext) { s.asset = v } - -func (s *MonetaryAllContext) LBRACK() antlr.TerminalNode { - return s.GetToken(NumScriptParserLBRACK, 0) -} - -func (s *MonetaryAllContext) RBRACK() antlr.TerminalNode { - return s.GetToken(NumScriptParserRBRACK, 0) -} - -func (s *MonetaryAllContext) Expression() IExpressionContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IExpressionContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(IExpressionContext) -} - -func (s *MonetaryAllContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *MonetaryAllContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -func (s *MonetaryAllContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterMonetaryAll(s) - } -} - -func (s *MonetaryAllContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitMonetaryAll(s) - } -} - -func (p *NumScriptParser) MonetaryAll() (localctx IMonetaryAllContext) { - this := p - _ = this - - localctx = NewMonetaryAllContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 2, NumScriptParserRULE_monetaryAll) - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.EnterOuterAlt(localctx, 1) - { - p.SetState(51) - p.Match(NumScriptParserLBRACK) - } - { - p.SetState(52) - - var _x = p.expression(0) - - localctx.(*MonetaryAllContext).asset = _x - } - { - p.SetState(53) - p.Match(NumScriptParserT__0) - } - { - p.SetState(54) - p.Match(NumScriptParserRBRACK) - } - - return localctx -} - -// ILiteralContext is an interface to support dynamic dispatch. -type ILiteralContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // IsLiteralContext differentiates from other interfaces. - IsLiteralContext() -} - -type LiteralContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser -} - -func NewEmptyLiteralContext() *LiteralContext { - var p = new(LiteralContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_literal - return p -} - -func (*LiteralContext) IsLiteralContext() {} - -func NewLiteralContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *LiteralContext { - var p = new(LiteralContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_literal - - return p -} - -func (s *LiteralContext) GetParser() antlr.Parser { return s.parser } - -func (s *LiteralContext) CopyFrom(ctx *LiteralContext) { - s.BaseParserRuleContext.CopyFrom(ctx.BaseParserRuleContext) -} - -func (s *LiteralContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LiteralContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -type LitPortionContext struct { - *LiteralContext -} - -func NewLitPortionContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitPortionContext { - var p = new(LitPortionContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitPortionContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitPortionContext) PORTION() antlr.TerminalNode { - return s.GetToken(NumScriptParserPORTION, 0) -} - -func (s *LitPortionContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitPortion(s) - } -} - -func (s *LitPortionContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitPortion(s) - } -} - -type LitStringContext struct { - *LiteralContext -} - -func NewLitStringContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitStringContext { - var p = new(LitStringContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitStringContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitStringContext) STRING() antlr.TerminalNode { - return s.GetToken(NumScriptParserSTRING, 0) -} - -func (s *LitStringContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitString(s) - } -} - -func (s *LitStringContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitString(s) - } -} - -type LitAccountContext struct { - *LiteralContext -} - -func NewLitAccountContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitAccountContext { - var p = new(LitAccountContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitAccountContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitAccountContext) ACCOUNT() antlr.TerminalNode { - return s.GetToken(NumScriptParserACCOUNT, 0) -} - -func (s *LitAccountContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitAccount(s) - } -} - -func (s *LitAccountContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitAccount(s) - } -} - -type LitAssetContext struct { - *LiteralContext -} - -func NewLitAssetContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitAssetContext { - var p = new(LitAssetContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitAssetContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitAssetContext) ASSET() antlr.TerminalNode { - return s.GetToken(NumScriptParserASSET, 0) -} - -func (s *LitAssetContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitAsset(s) - } -} - -func (s *LitAssetContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitAsset(s) - } -} - -type LitMonetaryContext struct { - *LiteralContext -} - -func NewLitMonetaryContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitMonetaryContext { - var p = new(LitMonetaryContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitMonetaryContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitMonetaryContext) Monetary() IMonetaryContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IMonetaryContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(IMonetaryContext) -} - -func (s *LitMonetaryContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitMonetary(s) - } -} - -func (s *LitMonetaryContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitMonetary(s) - } -} - -type LitNumberContext struct { - *LiteralContext -} - -func NewLitNumberContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *LitNumberContext { - var p = new(LitNumberContext) - - p.LiteralContext = NewEmptyLiteralContext() - p.parser = parser - p.CopyFrom(ctx.(*LiteralContext)) - - return p -} - -func (s *LitNumberContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *LitNumberContext) NUMBER() antlr.TerminalNode { - return s.GetToken(NumScriptParserNUMBER, 0) -} - -func (s *LitNumberContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterLitNumber(s) - } -} - -func (s *LitNumberContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitLitNumber(s) - } -} - -func (p *NumScriptParser) Literal() (localctx ILiteralContext) { - this := p - _ = this - - localctx = NewLiteralContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 4, NumScriptParserRULE_literal) - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.SetState(62) - p.GetErrorHandler().Sync(p) - - switch p.GetTokenStream().LA(1) { - case NumScriptParserACCOUNT: - localctx = NewLitAccountContext(p, localctx) - p.EnterOuterAlt(localctx, 1) - { - p.SetState(56) - p.Match(NumScriptParserACCOUNT) - } - - case NumScriptParserASSET: - localctx = NewLitAssetContext(p, localctx) - p.EnterOuterAlt(localctx, 2) - { - p.SetState(57) - p.Match(NumScriptParserASSET) - } - - case NumScriptParserNUMBER: - localctx = NewLitNumberContext(p, localctx) - p.EnterOuterAlt(localctx, 3) - { - p.SetState(58) - p.Match(NumScriptParserNUMBER) - } - - case NumScriptParserSTRING: - localctx = NewLitStringContext(p, localctx) - p.EnterOuterAlt(localctx, 4) - { - p.SetState(59) - p.Match(NumScriptParserSTRING) - } - - case NumScriptParserPORTION: - localctx = NewLitPortionContext(p, localctx) - p.EnterOuterAlt(localctx, 5) - { - p.SetState(60) - p.Match(NumScriptParserPORTION) - } - - case NumScriptParserLBRACK: - localctx = NewLitMonetaryContext(p, localctx) - p.EnterOuterAlt(localctx, 6) - { - p.SetState(61) - p.Monetary() - } - - default: - panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) - } - - return localctx -} - -// IVariableContext is an interface to support dynamic dispatch. -type IVariableContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // IsVariableContext differentiates from other interfaces. - IsVariableContext() -} - -type VariableContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser -} - -func NewEmptyVariableContext() *VariableContext { - var p = new(VariableContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_variable - return p -} - -func (*VariableContext) IsVariableContext() {} - -func NewVariableContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *VariableContext { - var p = new(VariableContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_variable - - return p -} - -func (s *VariableContext) GetParser() antlr.Parser { return s.parser } - -func (s *VariableContext) VARIABLE_NAME() antlr.TerminalNode { - return s.GetToken(NumScriptParserVARIABLE_NAME, 0) -} - -func (s *VariableContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *VariableContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -func (s *VariableContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterVariable(s) - } -} - -func (s *VariableContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitVariable(s) - } -} - -func (p *NumScriptParser) Variable() (localctx IVariableContext) { - this := p - _ = this - - localctx = NewVariableContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 6, NumScriptParserRULE_variable) - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.EnterOuterAlt(localctx, 1) - { - p.SetState(64) - p.Match(NumScriptParserVARIABLE_NAME) - } - - return localctx -} - -// IExpressionContext is an interface to support dynamic dispatch. -type IExpressionContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // IsExpressionContext differentiates from other interfaces. - IsExpressionContext() -} - -type ExpressionContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser -} - -func NewEmptyExpressionContext() *ExpressionContext { - var p = new(ExpressionContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_expression - return p -} - -func (*ExpressionContext) IsExpressionContext() {} - -func NewExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExpressionContext { - var p = new(ExpressionContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_expression - - return p -} - -func (s *ExpressionContext) GetParser() antlr.Parser { return s.parser } - -func (s *ExpressionContext) CopyFrom(ctx *ExpressionContext) { - s.BaseParserRuleContext.CopyFrom(ctx.BaseParserRuleContext) -} - -func (s *ExpressionContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *ExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -type ExprAddSubContext struct { - *ExpressionContext - lhs IExpressionContext - op antlr.Token - rhs IExpressionContext -} - -func NewExprAddSubContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ExprAddSubContext { - var p = new(ExprAddSubContext) - - p.ExpressionContext = NewEmptyExpressionContext() - p.parser = parser - p.CopyFrom(ctx.(*ExpressionContext)) - - return p -} - -func (s *ExprAddSubContext) GetOp() antlr.Token { return s.op } - -func (s *ExprAddSubContext) SetOp(v antlr.Token) { s.op = v } - -func (s *ExprAddSubContext) GetLhs() IExpressionContext { return s.lhs } - -func (s *ExprAddSubContext) GetRhs() IExpressionContext { return s.rhs } - -func (s *ExprAddSubContext) SetLhs(v IExpressionContext) { s.lhs = v } - -func (s *ExprAddSubContext) SetRhs(v IExpressionContext) { s.rhs = v } - -func (s *ExprAddSubContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *ExprAddSubContext) AllExpression() []IExpressionContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(IExpressionContext); ok { - len++ - } - } - - tst := make([]IExpressionContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(IExpressionContext); ok { - tst[i] = t.(IExpressionContext) - i++ - } - } - - return tst -} - -func (s *ExprAddSubContext) Expression(i int) IExpressionContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IExpressionContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(IExpressionContext) -} - -func (s *ExprAddSubContext) OP_ADD() antlr.TerminalNode { - return s.GetToken(NumScriptParserOP_ADD, 0) -} - -func (s *ExprAddSubContext) OP_SUB() antlr.TerminalNode { - return s.GetToken(NumScriptParserOP_SUB, 0) -} - -func (s *ExprAddSubContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterExprAddSub(s) - } -} - -func (s *ExprAddSubContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitExprAddSub(s) - } -} - -type ExprLiteralContext struct { - *ExpressionContext - lit ILiteralContext -} - -func NewExprLiteralContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ExprLiteralContext { - var p = new(ExprLiteralContext) - - p.ExpressionContext = NewEmptyExpressionContext() - p.parser = parser - p.CopyFrom(ctx.(*ExpressionContext)) - - return p -} - -func (s *ExprLiteralContext) GetLit() ILiteralContext { return s.lit } - -func (s *ExprLiteralContext) SetLit(v ILiteralContext) { s.lit = v } - -func (s *ExprLiteralContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *ExprLiteralContext) Literal() ILiteralContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(ILiteralContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(ILiteralContext) -} - -func (s *ExprLiteralContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterExprLiteral(s) - } -} - -func (s *ExprLiteralContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitExprLiteral(s) - } -} - -type ExprVariableContext struct { - *ExpressionContext - var_ IVariableContext -} - -func NewExprVariableContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *ExprVariableContext { - var p = new(ExprVariableContext) - - p.ExpressionContext = NewEmptyExpressionContext() - p.parser = parser - p.CopyFrom(ctx.(*ExpressionContext)) - - return p -} - -func (s *ExprVariableContext) GetVar_() IVariableContext { return s.var_ } - -func (s *ExprVariableContext) SetVar_(v IVariableContext) { s.var_ = v } - -func (s *ExprVariableContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *ExprVariableContext) Variable() IVariableContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IVariableContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(IVariableContext) -} - -func (s *ExprVariableContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterExprVariable(s) - } -} - -func (s *ExprVariableContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitExprVariable(s) - } -} - -func (p *NumScriptParser) Expression() (localctx IExpressionContext) { - return p.expression(0) -} - -func (p *NumScriptParser) expression(_p int) (localctx IExpressionContext) { - this := p - _ = this - - var _parentctx antlr.ParserRuleContext = p.GetParserRuleContext() - _parentState := p.GetState() - localctx = NewExpressionContext(p, p.GetParserRuleContext(), _parentState) - var _prevctx IExpressionContext = localctx - var _ antlr.ParserRuleContext = _prevctx // TODO: To prevent unused variable warning. - _startState := 8 - p.EnterRecursionRule(localctx, 8, NumScriptParserRULE_expression, _p) - var _la int - - defer func() { - p.UnrollRecursionContexts(_parentctx) - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - var _alt int - - p.EnterOuterAlt(localctx, 1) - p.SetState(69) - p.GetErrorHandler().Sync(p) - - switch p.GetTokenStream().LA(1) { - case NumScriptParserLBRACK, NumScriptParserSTRING, NumScriptParserPORTION, NumScriptParserNUMBER, NumScriptParserACCOUNT, NumScriptParserASSET: - localctx = NewExprLiteralContext(p, localctx) - p.SetParserRuleContext(localctx) - _prevctx = localctx - - { - p.SetState(67) - - var _x = p.Literal() - - localctx.(*ExprLiteralContext).lit = _x - } - - case NumScriptParserVARIABLE_NAME: - localctx = NewExprVariableContext(p, localctx) - p.SetParserRuleContext(localctx) - _prevctx = localctx - { - p.SetState(68) - - var _x = p.Variable() - - localctx.(*ExprVariableContext).var_ = _x - } - - default: - panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) - } - p.GetParserRuleContext().SetStop(p.GetTokenStream().LT(-1)) - p.SetState(76) - p.GetErrorHandler().Sync(p) - _alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 2, p.GetParserRuleContext()) - - for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { - if _alt == 1 { - if p.GetParseListeners() != nil { - p.TriggerExitRuleEvent() - } - _prevctx = localctx - localctx = NewExprAddSubContext(p, NewExpressionContext(p, _parentctx, _parentState)) - localctx.(*ExprAddSubContext).lhs = _prevctx - - p.PushNewRecursionContext(localctx, _startState, NumScriptParserRULE_expression) - p.SetState(71) - - if !(p.Precpred(p.GetParserRuleContext(), 3)) { - panic(antlr.NewFailedPredicateException(p, "p.Precpred(p.GetParserRuleContext(), 3)", "")) - } - { - p.SetState(72) - - var _lt = p.GetTokenStream().LT(1) - - localctx.(*ExprAddSubContext).op = _lt - - _la = p.GetTokenStream().LA(1) - - if !(_la == NumScriptParserOP_ADD || _la == NumScriptParserOP_SUB) { - var _ri = p.GetErrorHandler().RecoverInline(p) - - localctx.(*ExprAddSubContext).op = _ri - } else { - p.GetErrorHandler().ReportMatch(p) - p.Consume() - } - } - { - p.SetState(73) - - var _x = p.expression(4) - - localctx.(*ExprAddSubContext).rhs = _x - } - - } - p.SetState(78) - p.GetErrorHandler().Sync(p) - _alt = p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 2, p.GetParserRuleContext()) - } - - return localctx -} - -// IAllotmentPortionContext is an interface to support dynamic dispatch. -type IAllotmentPortionContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // IsAllotmentPortionContext differentiates from other interfaces. - IsAllotmentPortionContext() -} - -type AllotmentPortionContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser -} - -func NewEmptyAllotmentPortionContext() *AllotmentPortionContext { - var p = new(AllotmentPortionContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_allotmentPortion - return p -} - -func (*AllotmentPortionContext) IsAllotmentPortionContext() {} - -func NewAllotmentPortionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AllotmentPortionContext { - var p = new(AllotmentPortionContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_allotmentPortion - - return p -} - -func (s *AllotmentPortionContext) GetParser() antlr.Parser { return s.parser } - -func (s *AllotmentPortionContext) CopyFrom(ctx *AllotmentPortionContext) { - s.BaseParserRuleContext.CopyFrom(ctx.BaseParserRuleContext) -} - -func (s *AllotmentPortionContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *AllotmentPortionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -type AllotmentPortionRemainingContext struct { - *AllotmentPortionContext -} - -func NewAllotmentPortionRemainingContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AllotmentPortionRemainingContext { - var p = new(AllotmentPortionRemainingContext) - - p.AllotmentPortionContext = NewEmptyAllotmentPortionContext() - p.parser = parser - p.CopyFrom(ctx.(*AllotmentPortionContext)) - - return p -} - -func (s *AllotmentPortionRemainingContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *AllotmentPortionRemainingContext) REMAINING() antlr.TerminalNode { - return s.GetToken(NumScriptParserREMAINING, 0) -} - -func (s *AllotmentPortionRemainingContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterAllotmentPortionRemaining(s) - } -} - -func (s *AllotmentPortionRemainingContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitAllotmentPortionRemaining(s) - } -} - -type AllotmentPortionVarContext struct { - *AllotmentPortionContext - por IVariableContext -} - -func NewAllotmentPortionVarContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AllotmentPortionVarContext { - var p = new(AllotmentPortionVarContext) - - p.AllotmentPortionContext = NewEmptyAllotmentPortionContext() - p.parser = parser - p.CopyFrom(ctx.(*AllotmentPortionContext)) - - return p -} - -func (s *AllotmentPortionVarContext) GetPor() IVariableContext { return s.por } - -func (s *AllotmentPortionVarContext) SetPor(v IVariableContext) { s.por = v } - -func (s *AllotmentPortionVarContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *AllotmentPortionVarContext) Variable() IVariableContext { - var t antlr.RuleContext - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IVariableContext); ok { - t = ctx.(antlr.RuleContext) - break - } - } - - if t == nil { - return nil - } - - return t.(IVariableContext) -} - -func (s *AllotmentPortionVarContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterAllotmentPortionVar(s) - } -} - -func (s *AllotmentPortionVarContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitAllotmentPortionVar(s) - } -} - -type AllotmentPortionConstContext struct { - *AllotmentPortionContext -} - -func NewAllotmentPortionConstContext(parser antlr.Parser, ctx antlr.ParserRuleContext) *AllotmentPortionConstContext { - var p = new(AllotmentPortionConstContext) - - p.AllotmentPortionContext = NewEmptyAllotmentPortionContext() - p.parser = parser - p.CopyFrom(ctx.(*AllotmentPortionContext)) - - return p -} - -func (s *AllotmentPortionConstContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *AllotmentPortionConstContext) PORTION() antlr.TerminalNode { - return s.GetToken(NumScriptParserPORTION, 0) -} - -func (s *AllotmentPortionConstContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterAllotmentPortionConst(s) - } -} - -func (s *AllotmentPortionConstContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitAllotmentPortionConst(s) - } -} - -func (p *NumScriptParser) AllotmentPortion() (localctx IAllotmentPortionContext) { - this := p - _ = this - - localctx = NewAllotmentPortionContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 10, NumScriptParserRULE_allotmentPortion) - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.SetState(82) - p.GetErrorHandler().Sync(p) - - switch p.GetTokenStream().LA(1) { - case NumScriptParserPORTION: - localctx = NewAllotmentPortionConstContext(p, localctx) - p.EnterOuterAlt(localctx, 1) - { - p.SetState(79) - p.Match(NumScriptParserPORTION) - } - - case NumScriptParserVARIABLE_NAME: - localctx = NewAllotmentPortionVarContext(p, localctx) - p.EnterOuterAlt(localctx, 2) - { - p.SetState(80) - - var _x = p.Variable() - - localctx.(*AllotmentPortionVarContext).por = _x - } - - case NumScriptParserREMAINING: - localctx = NewAllotmentPortionRemainingContext(p, localctx) - p.EnterOuterAlt(localctx, 3) - { - p.SetState(81) - p.Match(NumScriptParserREMAINING) - } - - default: - panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) - } - - return localctx -} - -// IDestinationInOrderContext is an interface to support dynamic dispatch. -type IDestinationInOrderContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // Get_expression returns the _expression rule contexts. - Get_expression() IExpressionContext - - // Get_keptOrDestination returns the _keptOrDestination rule contexts. - Get_keptOrDestination() IKeptOrDestinationContext - - // GetRemainingDest returns the remainingDest rule contexts. - GetRemainingDest() IKeptOrDestinationContext - - // Set_expression sets the _expression rule contexts. - Set_expression(IExpressionContext) - - // Set_keptOrDestination sets the _keptOrDestination rule contexts. - Set_keptOrDestination(IKeptOrDestinationContext) - - // SetRemainingDest sets the remainingDest rule contexts. - SetRemainingDest(IKeptOrDestinationContext) - - // GetAmounts returns the amounts rule context list. - GetAmounts() []IExpressionContext - - // GetDests returns the dests rule context list. - GetDests() []IKeptOrDestinationContext - - // SetAmounts sets the amounts rule context list. - SetAmounts([]IExpressionContext) - - // SetDests sets the dests rule context list. - SetDests([]IKeptOrDestinationContext) - - // IsDestinationInOrderContext differentiates from other interfaces. - IsDestinationInOrderContext() -} - -type DestinationInOrderContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser - _expression IExpressionContext - amounts []IExpressionContext - _keptOrDestination IKeptOrDestinationContext - dests []IKeptOrDestinationContext - remainingDest IKeptOrDestinationContext -} - -func NewEmptyDestinationInOrderContext() *DestinationInOrderContext { - var p = new(DestinationInOrderContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_destinationInOrder - return p -} - -func (*DestinationInOrderContext) IsDestinationInOrderContext() {} - -func NewDestinationInOrderContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DestinationInOrderContext { - var p = new(DestinationInOrderContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_destinationInOrder - - return p -} - -func (s *DestinationInOrderContext) GetParser() antlr.Parser { return s.parser } - -func (s *DestinationInOrderContext) Get_expression() IExpressionContext { return s._expression } - -func (s *DestinationInOrderContext) Get_keptOrDestination() IKeptOrDestinationContext { - return s._keptOrDestination -} - -func (s *DestinationInOrderContext) GetRemainingDest() IKeptOrDestinationContext { - return s.remainingDest -} - -func (s *DestinationInOrderContext) Set_expression(v IExpressionContext) { s._expression = v } - -func (s *DestinationInOrderContext) Set_keptOrDestination(v IKeptOrDestinationContext) { - s._keptOrDestination = v -} - -func (s *DestinationInOrderContext) SetRemainingDest(v IKeptOrDestinationContext) { - s.remainingDest = v -} - -func (s *DestinationInOrderContext) GetAmounts() []IExpressionContext { return s.amounts } - -func (s *DestinationInOrderContext) GetDests() []IKeptOrDestinationContext { return s.dests } - -func (s *DestinationInOrderContext) SetAmounts(v []IExpressionContext) { s.amounts = v } - -func (s *DestinationInOrderContext) SetDests(v []IKeptOrDestinationContext) { s.dests = v } - -func (s *DestinationInOrderContext) LBRACE() antlr.TerminalNode { - return s.GetToken(NumScriptParserLBRACE, 0) -} - -func (s *DestinationInOrderContext) AllNEWLINE() []antlr.TerminalNode { - return s.GetTokens(NumScriptParserNEWLINE) -} - -func (s *DestinationInOrderContext) NEWLINE(i int) antlr.TerminalNode { - return s.GetToken(NumScriptParserNEWLINE, i) -} - -func (s *DestinationInOrderContext) REMAINING() antlr.TerminalNode { - return s.GetToken(NumScriptParserREMAINING, 0) -} - -func (s *DestinationInOrderContext) RBRACE() antlr.TerminalNode { - return s.GetToken(NumScriptParserRBRACE, 0) -} - -func (s *DestinationInOrderContext) AllKeptOrDestination() []IKeptOrDestinationContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(IKeptOrDestinationContext); ok { - len++ - } - } - - tst := make([]IKeptOrDestinationContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(IKeptOrDestinationContext); ok { - tst[i] = t.(IKeptOrDestinationContext) - i++ - } - } - - return tst -} - -func (s *DestinationInOrderContext) KeptOrDestination(i int) IKeptOrDestinationContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IKeptOrDestinationContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(IKeptOrDestinationContext) -} - -func (s *DestinationInOrderContext) AllMAX() []antlr.TerminalNode { - return s.GetTokens(NumScriptParserMAX) -} - -func (s *DestinationInOrderContext) MAX(i int) antlr.TerminalNode { - return s.GetToken(NumScriptParserMAX, i) -} - -func (s *DestinationInOrderContext) AllExpression() []IExpressionContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(IExpressionContext); ok { - len++ - } - } - - tst := make([]IExpressionContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(IExpressionContext); ok { - tst[i] = t.(IExpressionContext) - i++ - } - } - - return tst -} - -func (s *DestinationInOrderContext) Expression(i int) IExpressionContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IExpressionContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(IExpressionContext) -} - -func (s *DestinationInOrderContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *DestinationInOrderContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -func (s *DestinationInOrderContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterDestinationInOrder(s) - } -} - -func (s *DestinationInOrderContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitDestinationInOrder(s) - } -} - -func (p *NumScriptParser) DestinationInOrder() (localctx IDestinationInOrderContext) { - this := p - _ = this - - localctx = NewDestinationInOrderContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 12, NumScriptParserRULE_destinationInOrder) - var _la int - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.EnterOuterAlt(localctx, 1) - { - p.SetState(84) - p.Match(NumScriptParserLBRACE) - } - { - p.SetState(85) - p.Match(NumScriptParserNEWLINE) - } - p.SetState(91) - p.GetErrorHandler().Sync(p) - _la = p.GetTokenStream().LA(1) - - for ok := true; ok; ok = _la == NumScriptParserMAX { - { - p.SetState(86) - p.Match(NumScriptParserMAX) - } - { - p.SetState(87) - - var _x = p.expression(0) - - localctx.(*DestinationInOrderContext)._expression = _x - } - localctx.(*DestinationInOrderContext).amounts = append(localctx.(*DestinationInOrderContext).amounts, localctx.(*DestinationInOrderContext)._expression) - { - p.SetState(88) - - var _x = p.KeptOrDestination() - - localctx.(*DestinationInOrderContext)._keptOrDestination = _x - } - localctx.(*DestinationInOrderContext).dests = append(localctx.(*DestinationInOrderContext).dests, localctx.(*DestinationInOrderContext)._keptOrDestination) - { - p.SetState(89) - p.Match(NumScriptParserNEWLINE) - } - - p.SetState(93) - p.GetErrorHandler().Sync(p) - _la = p.GetTokenStream().LA(1) - } - { - p.SetState(95) - p.Match(NumScriptParserREMAINING) - } - { - p.SetState(96) - - var _x = p.KeptOrDestination() - - localctx.(*DestinationInOrderContext).remainingDest = _x - } - { - p.SetState(97) - p.Match(NumScriptParserNEWLINE) - } - { - p.SetState(98) - p.Match(NumScriptParserRBRACE) - } - - return localctx -} - -// IDestinationAllotmentContext is an interface to support dynamic dispatch. -type IDestinationAllotmentContext interface { - antlr.ParserRuleContext - - // GetParser returns the parser. - GetParser() antlr.Parser - - // Get_allotmentPortion returns the _allotmentPortion rule contexts. - Get_allotmentPortion() IAllotmentPortionContext - - // Get_keptOrDestination returns the _keptOrDestination rule contexts. - Get_keptOrDestination() IKeptOrDestinationContext - - // Set_allotmentPortion sets the _allotmentPortion rule contexts. - Set_allotmentPortion(IAllotmentPortionContext) - - // Set_keptOrDestination sets the _keptOrDestination rule contexts. - Set_keptOrDestination(IKeptOrDestinationContext) - - // GetPortions returns the portions rule context list. - GetPortions() []IAllotmentPortionContext - - // GetDests returns the dests rule context list. - GetDests() []IKeptOrDestinationContext - - // SetPortions sets the portions rule context list. - SetPortions([]IAllotmentPortionContext) - - // SetDests sets the dests rule context list. - SetDests([]IKeptOrDestinationContext) - - // IsDestinationAllotmentContext differentiates from other interfaces. - IsDestinationAllotmentContext() -} - -type DestinationAllotmentContext struct { - *antlr.BaseParserRuleContext - parser antlr.Parser - _allotmentPortion IAllotmentPortionContext - portions []IAllotmentPortionContext - _keptOrDestination IKeptOrDestinationContext - dests []IKeptOrDestinationContext -} - -func NewEmptyDestinationAllotmentContext() *DestinationAllotmentContext { - var p = new(DestinationAllotmentContext) - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1) - p.RuleIndex = NumScriptParserRULE_destinationAllotment - return p -} - -func (*DestinationAllotmentContext) IsDestinationAllotmentContext() {} - -func NewDestinationAllotmentContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DestinationAllotmentContext { - var p = new(DestinationAllotmentContext) - - p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState) - - p.parser = parser - p.RuleIndex = NumScriptParserRULE_destinationAllotment - - return p -} - -func (s *DestinationAllotmentContext) GetParser() antlr.Parser { return s.parser } - -func (s *DestinationAllotmentContext) Get_allotmentPortion() IAllotmentPortionContext { - return s._allotmentPortion -} - -func (s *DestinationAllotmentContext) Get_keptOrDestination() IKeptOrDestinationContext { - return s._keptOrDestination -} - -func (s *DestinationAllotmentContext) Set_allotmentPortion(v IAllotmentPortionContext) { - s._allotmentPortion = v -} - -func (s *DestinationAllotmentContext) Set_keptOrDestination(v IKeptOrDestinationContext) { - s._keptOrDestination = v -} - -func (s *DestinationAllotmentContext) GetPortions() []IAllotmentPortionContext { return s.portions } - -func (s *DestinationAllotmentContext) GetDests() []IKeptOrDestinationContext { return s.dests } - -func (s *DestinationAllotmentContext) SetPortions(v []IAllotmentPortionContext) { s.portions = v } - -func (s *DestinationAllotmentContext) SetDests(v []IKeptOrDestinationContext) { s.dests = v } - -func (s *DestinationAllotmentContext) LBRACE() antlr.TerminalNode { - return s.GetToken(NumScriptParserLBRACE, 0) -} - -func (s *DestinationAllotmentContext) AllNEWLINE() []antlr.TerminalNode { - return s.GetTokens(NumScriptParserNEWLINE) -} - -func (s *DestinationAllotmentContext) NEWLINE(i int) antlr.TerminalNode { - return s.GetToken(NumScriptParserNEWLINE, i) -} - -func (s *DestinationAllotmentContext) RBRACE() antlr.TerminalNode { - return s.GetToken(NumScriptParserRBRACE, 0) -} - -func (s *DestinationAllotmentContext) AllAllotmentPortion() []IAllotmentPortionContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(IAllotmentPortionContext); ok { - len++ - } - } - - tst := make([]IAllotmentPortionContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(IAllotmentPortionContext); ok { - tst[i] = t.(IAllotmentPortionContext) - i++ - } - } - - return tst -} - -func (s *DestinationAllotmentContext) AllotmentPortion(i int) IAllotmentPortionContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IAllotmentPortionContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(IAllotmentPortionContext) -} - -func (s *DestinationAllotmentContext) AllKeptOrDestination() []IKeptOrDestinationContext { - children := s.GetChildren() - len := 0 - for _, ctx := range children { - if _, ok := ctx.(IKeptOrDestinationContext); ok { - len++ - } - } - - tst := make([]IKeptOrDestinationContext, len) - i := 0 - for _, ctx := range children { - if t, ok := ctx.(IKeptOrDestinationContext); ok { - tst[i] = t.(IKeptOrDestinationContext) - i++ - } - } - - return tst -} - -func (s *DestinationAllotmentContext) KeptOrDestination(i int) IKeptOrDestinationContext { - var t antlr.RuleContext - j := 0 - for _, ctx := range s.GetChildren() { - if _, ok := ctx.(IKeptOrDestinationContext); ok { - if j == i { - t = ctx.(antlr.RuleContext) - break - } - j++ - } - } - - if t == nil { - return nil - } - - return t.(IKeptOrDestinationContext) -} - -func (s *DestinationAllotmentContext) GetRuleContext() antlr.RuleContext { - return s -} - -func (s *DestinationAllotmentContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { - return antlr.TreesStringTree(s, ruleNames, recog) -} - -func (s *DestinationAllotmentContext) EnterRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.EnterDestinationAllotment(s) - } -} - -func (s *DestinationAllotmentContext) ExitRule(listener antlr.ParseTreeListener) { - if listenerT, ok := listener.(NumScriptListener); ok { - listenerT.ExitDestinationAllotment(s) - } -} - -func (p *NumScriptParser) DestinationAllotment() (localctx IDestinationAllotmentContext) { - this := p - _ = this - - localctx = NewDestinationAllotmentContext(p, p.GetParserRuleContext(), p.GetState()) - p.EnterRule(localctx, 14, NumScriptParserRULE_destinationAllotment) - var _la int - - defer func() { - p.ExitRule() - }() - - defer func() { - if err := recover(); err != nil { - if v, ok := err.(antlr.RecognitionException); ok { - localctx.SetException(v) - p.GetErrorHandler().ReportError(p, v) - p.GetErrorHandler().Recover(p, v) - } else { - panic(err) - } - } - }() - - p.EnterOuterAlt(localctx, 1) - { - p.SetState(100) - p.Match(NumScriptParserLBRACE) - } - { - p.SetState(101) - p.Match(NumScriptParserNEWLINE) - } - p.SetState(106) - p.GetErrorHandler().Sync(p) - _la = p.GetTokenStream().LA(1) - - for ok := true; ok; ok = (((_la-38)&-(0x1f+1)) == 0 && ((1<= len(m.Resources) { - return nil, false - } - return &m.Resources[a], true -} - -func (m *Machine) withdrawAll(account machine.AccountAddress, asset machine.Asset, overdraft *machine.MonetaryInt) (*machine.Funding, error) { - if accBalances, ok := m.Balances[account]; ok { - if balance, ok := accBalances[asset]; ok { - amountTaken := machine.Zero - balanceWithOverdraft := balance.Add(overdraft) - if balanceWithOverdraft.Gt(machine.Zero) { - amountTaken = balanceWithOverdraft - accBalances[asset] = overdraft.Neg() - } - - return &machine.Funding{ - Asset: asset, - Parts: []machine.FundingPart{{ - Account: account, - Amount: amountTaken, - }}, - }, nil - } - } - return nil, fmt.Errorf("missing %v balance from %v", asset, account) -} - -func (m *Machine) withdrawAlways(account machine.AccountAddress, mon machine.Monetary) (*machine.Funding, error) { - if accBalance, ok := m.Balances[account]; ok { - if balance, ok := accBalance[mon.Asset]; ok { - accBalance[mon.Asset] = balance.Sub(mon.Amount) - } - } - - return &machine.Funding{ - Asset: mon.Asset, - Parts: []machine.FundingPart{{ - Account: account, - Amount: mon.Amount, - }}, - }, nil -} - -func (m *Machine) credit(account machine.AccountAddress, funding machine.Funding) { - if account == "world" { - return - } - if accBalance, ok := m.Balances[account]; ok { - if _, ok := accBalance[funding.Asset]; ok { - for _, part := range funding.Parts { - balance := accBalance[funding.Asset] - accBalance[funding.Asset] = balance.Add(part.Amount) - } - } - } -} - -func (m *Machine) repay(funding machine.Funding) { - for _, part := range funding.Parts { - if part.Account == "world" { - continue - } - accountBalance, ok := m.Balances[part.Account] - if !ok { - // no asset: the source has to be an unbounded source - // which NEVER appears as bounded - // this means we don't need to track it's balance - continue - } - - balance := accountBalance[funding.Asset] - accountBalance[funding.Asset] = balance.Add(part.Amount) - } -} - -func (m *Machine) tick() (bool, error) { - op := m.Program.Instructions[m.P] - - if m.Debug { - fmt.Println("STATE ---------------------------------------------------------------------") - fmt.Printf(" %v\n", aurora.Blue(m.Stack)) - fmt.Printf(" %v\n", aurora.Cyan(m.Balances)) - fmt.Printf(" %v\n", program.OpcodeName(op)) - } - - switch op { - case program.OP_APUSH: - bytes := m.Program.Instructions[m.P+1 : m.P+3] - v, ok := m.getResource(machine.Address(binary.LittleEndian.Uint16(bytes))) - if !ok { - return true, machine.ErrResourceNotFound - } - m.Stack = append(m.Stack, *v) - m.P += 2 - - case program.OP_BUMP: - n := big.Int(*pop[machine.Number](m)) - idx := len(m.Stack) - int(n.Uint64()) - 1 - v := m.Stack[idx] - m.Stack = append(m.Stack[:idx], m.Stack[idx+1:]...) - m.Stack = append(m.Stack, v) - - case program.OP_DELETE: - n := m.popValue() - if n.GetType() == machine.TypeFunding { - return true, machine.NewErrInvalidScript("wrong type: want: %v, got: %v", n.GetType(), machine.TypeFunding) - } - - case program.OP_IADD: - b := pop[machine.Number](m) - a := pop[machine.Number](m) - m.pushValue(a.Add(b)) - - case program.OP_ISUB: - b := pop[machine.Number](m) - a := pop[machine.Number](m) - m.pushValue(a.Sub(b)) - - case program.OP_PRINT: - a := m.popValue() - m.printChan <- a - - case program.OP_FAIL: - return true, machine.ErrScriptFailed - - case program.OP_ASSET: - v := m.popValue() - switch v := v.(type) { - case machine.Asset: - m.pushValue(v) - case machine.Monetary: - m.pushValue(v.Asset) - case machine.Funding: - m.pushValue(v.Asset) - default: - return true, machine.NewErrInvalidScript("wrong type for op asset: %v", v.GetType()) - } - - case program.OP_MONETARY_NEW: - amount := pop[machine.Number](m) - asset := pop[machine.Asset](m) - m.pushValue(machine.Monetary{ - Asset: asset, - Amount: amount, - }) - - case program.OP_MONETARY_ADD: - b := pop[machine.Monetary](m) - a := pop[machine.Monetary](m) - if a.Asset != b.Asset { - return true, machine.NewErrInvalidScript("cannot add different assets: %v and %v", a.Asset, b.Asset) - } - m.pushValue(machine.Monetary{ - Asset: a.Asset, - Amount: a.Amount.Add(b.Amount), - }) - - case program.OP_MONETARY_SUB: - b := pop[machine.Monetary](m) - a := pop[machine.Monetary](m) - if a.Asset != b.Asset { - return true, fmt.Errorf("%s", program.OpcodeName(op)) - } - m.pushValue(machine.Monetary{ - Asset: a.Asset, - Amount: a.Amount.Sub(b.Amount), - }) - - case program.OP_MAKE_ALLOTMENT: - n := pop[machine.Number](m) - portions := make([]machine.Portion, n.Uint64()) - for i := uint64(0); i < n.Uint64(); i++ { - p := pop[machine.Portion](m) - portions[i] = p - } - allotment, err := machine.NewAllotment(portions) - if err != nil { - return true, machine.NewErrInvalidScript(err.Error()) - } - m.pushValue(*allotment) - - case program.OP_TAKE_ALL: - overdraft := pop[machine.Monetary](m) - account := pop[machine.AccountAddress](m) - funding, err := m.withdrawAll(account, overdraft.Asset, overdraft.Amount) - if err != nil { - return true, machine.NewErrInvalidScript(err.Error()) - } - m.pushValue(*funding) - - case program.OP_TAKE_ALWAYS: - mon := pop[machine.Monetary](m) - account := pop[machine.AccountAddress](m) - funding, err := m.withdrawAlways(account, mon) - if err != nil { - return true, machine.NewErrInvalidScript(err.Error()) - } - m.pushValue(*funding) - - case program.OP_TAKE: - mon := pop[machine.Monetary](m) - funding := pop[machine.Funding](m) - if funding.Asset != mon.Asset { - return true, machine.NewErrInvalidScript("cannot take from different assets: %v and %v", funding.Asset, mon.Asset) - } - result, remainder, err := funding.Take(mon.Amount) - if err != nil { - return true, machine.NewErrInsufficientFund(err.Error()) - } - m.pushValue(remainder) - m.pushValue(result) - - case program.OP_TAKE_MAX: - mon := pop[machine.Monetary](m) - if mon.Amount.Ltz() { - return true, fmt.Errorf( - "cannot send a monetary with a negative amount: [%s %s]", - string(mon.Asset), mon.Amount) - } - funding := pop[machine.Funding](m) - if funding.Asset != mon.Asset { - return true, machine.NewErrInvalidScript("cannot take from different assets: %v and %v", funding.Asset, mon.Asset) - } - missing := machine.Zero - total := funding.Total() - if mon.Amount.Gt(total) { - missing = mon.Amount.Sub(total) - } - m.pushValue(machine.Monetary{ - Asset: mon.Asset, - Amount: missing, - }) - result, remainder := funding.TakeMax(mon.Amount) - m.pushValue(remainder) - m.pushValue(result) - - case program.OP_FUNDING_ASSEMBLE: - num := pop[machine.Number](m) - n := int(num.Uint64()) - if n == 0 { - return true, machine.NewErrInvalidScript("cannot assemble zero fundings") - } - first := pop[machine.Funding](m) - result := machine.Funding{ - Asset: first.Asset, - } - fundings_rev := make([]machine.Funding, n) - fundings_rev[0] = first - for i := 1; i < n; i++ { - f := pop[machine.Funding](m) - if f.Asset != result.Asset { - return true, machine.NewErrInvalidScript("cannot assemble different assets: %v and %v", f.Asset, result.Asset) - } - fundings_rev[i] = f - } - for i := 0; i < n; i++ { - res, err := result.Concat(fundings_rev[n-1-i]) - if err != nil { - return true, machine.NewErrInvalidScript(err.Error()) - } - result = res - } - m.pushValue(result) - - case program.OP_FUNDING_SUM: - funding := pop[machine.Funding](m) - sum := funding.Total() - m.pushValue(funding) - m.pushValue(machine.Monetary{ - Asset: funding.Asset, - Amount: sum, - }) - - case program.OP_FUNDING_REVERSE: - funding := pop[machine.Funding](m) - result := funding.Reverse() - m.pushValue(result) - - case program.OP_ALLOC: - allotment := pop[machine.Allotment](m) - monetary := pop[machine.Monetary](m) - total := monetary.Amount - parts := allotment.Allocate(total) - for i := len(parts) - 1; i >= 0; i-- { - m.pushValue(machine.Monetary{ - Asset: monetary.Asset, - Amount: parts[i], - }) - } - - case program.OP_REPAY: - m.repay(pop[machine.Funding](m)) - - case program.OP_SEND: - dest := pop[machine.AccountAddress](m) - funding := pop[machine.Funding](m) - m.credit(dest, funding) - for _, part := range funding.Parts { - src := part.Account - amt := part.Amount - m.Postings = append(m.Postings, Posting{ - Source: string(src), - Destination: string(dest), - Asset: string(funding.Asset), - Amount: amt, - }) - } - - case program.OP_TX_META: - k := pop[machine.String](m) - v := m.popValue() - m.TxMeta[string(k)] = v - - case program.OP_ACCOUNT_META: - a := pop[machine.AccountAddress](m) - k := pop[machine.String](m) - v := m.popValue() - if m.AccountsMeta[a] == nil { - m.AccountsMeta[a] = map[string]machine.Value{} - } - m.AccountsMeta[a][string(k)] = v - - case program.OP_SAVE: - a := pop[machine.AccountAddress](m) - v := m.popValue() - switch v := v.(type) { - case machine.Asset: - m.Balances[a][v] = machine.Zero - case machine.Monetary: - m.Balances[a][v.Asset] = m.Balances[a][v.Asset].Sub(v.Amount) - default: - panic(fmt.Errorf("invalid value type: %T", v)) - } - - default: - return true, machine.NewErrInvalidScript("invalid opcode: %v", op) - } - - m.P += 1 - - if int(m.P) >= len(m.Program.Instructions) { - return true, nil - } - - return false, nil -} - -func (m *Machine) Execute() error { - go m.Printer(m.printChan) - defer close(m.printChan) - - if len(m.Resources) != len(m.UnresolvedResources) { - return machine.ErrResourcesNotInitialized - } else if m.Balances == nil { - return machine.ErrBalancesNotInitialized - } - - for { - finished, err := m.tick() - if finished { - if err == nil && len(m.Stack) != 0 { - panic("stack not empty after execution") - } else { - return err - } - } - } -} - -type BalanceRequest struct { - Account string - Asset string - Response chan *machine.MonetaryInt - Error error -} - -func (m *Machine) ResolveBalances(ctx context.Context, store Store) error { - - m.Balances = make(map[machine.AccountAddress]map[machine.Asset]*machine.MonetaryInt) - - for address, resourceIndex := range m.UnresolvedResourceBalances { - monetary := m.Resources[resourceIndex].(machine.Monetary) - balance, err := store.GetBalance(ctx, address, string(monetary.Asset)) - if err != nil { - return err - } - if balance.Cmp(ledger.Zero) < 0 { - return machine.NewErrNegativeAmount("tried to request the balance of account %s for asset %s: received %s: monetary amounts must be non-negative", - address, monetary.Asset, balance) - } - monetary.Amount = machine.NewMonetaryIntFromBigInt(balance) - m.Resources[resourceIndex] = monetary - } - - // for every account that we need balances of, check if it's there - for addr, neededAssets := range m.Program.NeededBalances { - account, ok := m.getResource(addr) - if !ok { - return errors.New("invalid program (resolve balances: invalid address of account)") - } - accountAddress := (*account).(machine.AccountAddress) - - if _, ok := m.Balances[accountAddress]; !ok { - m.Balances[accountAddress] = make(map[machine.Asset]*machine.MonetaryInt) - } - // for every asset, send request - for addr := range neededAssets { - mon, ok := m.getResource(addr) - if !ok { - return errors.New("invalid program (resolve balances: invalid address of monetary)") - } - - asset := (*mon).(machine.HasAsset).GetAsset() - if string(accountAddress) == "world" { - m.Balances[accountAddress][asset] = machine.Zero - continue - } - - balance, err := store.GetBalance(ctx, string(accountAddress), string(asset)) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("could not get balance for account %q", addr)) - } - - m.Balances[accountAddress][asset] = machine.NewMonetaryIntFromBigInt(balance) - } - } - return nil -} - -func (m *Machine) ResolveResources(ctx context.Context, store Store) ([]string, []string, error) { - //TODO(gfyrag): Is that really required? Feel like defensive programming. - if m.resolveCalled { - return nil, nil, errors.New("tried to call ResolveResources twice") - } - - m.resolveCalled = true - involvedAccountsMap := make(map[machine.Address]string) - for len(m.Resources) != len(m.UnresolvedResources) { - idx := len(m.Resources) - res := m.UnresolvedResources[idx] - var val machine.Value - switch res := res.(type) { - case program.Constant: - val = res.Inner - if val.GetType() == machine.TypeAccount { - involvedAccountsMap[machine.Address(idx)] = string(val.(machine.AccountAddress)) - } - case program.Variable: - var ok bool - val, ok = m.Vars[res.Name] - if !ok { - return nil, nil, fmt.Errorf("missing variable '%s'", res.Name) - } - if val.GetType() == machine.TypeAccount { - involvedAccountsMap[machine.Address(idx)] = string(val.(machine.AccountAddress)) - } - case program.VariableAccountMetadata: - acc, _ := m.getResource(res.Account) - addr := string((*acc).(machine.AccountAddress)) - - account, err := store.GetAccount(ctx, addr) - if err != nil { - return nil, nil, err - } - - metadata, ok := account.Metadata[res.Key] - if !ok { - return nil, nil, machine.NewErrMissingMetadata("missing key %v in metadata for account %s", res.Key, addr) - } - - val, err = machine.NewValueFromString(res.Typ, metadata) - if err != nil { - return nil, nil, err - } - if val.GetType() == machine.TypeAccount { - involvedAccountsMap[machine.Address(idx)] = string(val.(machine.AccountAddress)) - } - case program.VariableAccountBalance: - acc, _ := m.getResource(res.Account) - address := string((*acc).(machine.AccountAddress)) - involvedAccountsMap[machine.Address(idx)] = address - m.UnresolvedResourceBalances[address] = idx - - ass, ok := m.getResource(res.Asset) - if !ok { - return nil, nil, fmt.Errorf( - "variable '%s': tried to request account balance of an asset which has not yet been solved", - res.Name) - } - if (*ass).GetType() != machine.TypeAsset { - return nil, nil, fmt.Errorf( - "variable '%s': tried to request account balance for an asset on wrong entity: %v instead of asset", - res.Name, (*ass).GetType()) - } - - val = machine.Monetary{ - Asset: (*ass).(machine.Asset), - } - case program.Monetary: - ass, _ := m.getResource(res.Asset) - val = machine.Monetary{ - Asset: (*ass).(machine.Asset), - Amount: res.Amount, - } - default: - panic(fmt.Errorf("type %T not implemented", res)) - } - m.Resources = append(m.Resources, val) - } - - readLockAccounts := make([]string, 0) - for _, accountAddress := range m.Program.ReadLockAccounts { - readLockAccounts = append(readLockAccounts, involvedAccountsMap[accountAddress]) - } - - writeLockAccounts := make([]string, 0) - for _, machineAddress := range m.Program.WriteLockAccounts { - writeLockAccounts = append(writeLockAccounts, involvedAccountsMap[machineAddress]) - } - - slices.Sort(readLockAccounts) - slices.Sort(writeLockAccounts) - return readLockAccounts, writeLockAccounts, nil -} - -func (m *Machine) SetVarsFromJSON(vars map[string]string) error { - v, err := m.Program.ParseVariablesJSON(vars) - if err != nil { - return machine.NewErrInvalidVars(err.Error()) - } - m.Vars = v - return nil -} diff --git a/components/ledger/internal/machine/vm/machine_kept_test.go b/components/ledger/internal/machine/vm/machine_kept_test.go deleted file mode 100644 index 364c63e25e..0000000000 --- a/components/ledger/internal/machine/vm/machine_kept_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package vm - -import ( - "testing" - - "github.com/formancehq/ledger/internal/machine" -) - -func TestKeptDestinationAllotment(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = { - @a - @world - } - destination = { - 50% kept - 25% to @x - 25% to @y - } - )`) - tc.setBalance("a", "GEM", 1) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1), - Source: "a", - Destination: "x", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(24), - Source: "world", - Destination: "x", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(25), - Source: "world", - Destination: "y", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestKeptComplex(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = { - @foo - @bar - @baz - } - destination = { - 50% to { - max [GEM 8] to { - 50% kept - 25% to @arst - 25% kept - } - remaining to @thing - } - 20% to @qux - 5% kept - remaining to @quz - } - )`) - tc.setBalance("foo", "GEM", 20) - tc.setBalance("bar", "GEM", 40) - tc.setBalance("baz", "GEM", 40) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(2), - Source: "foo", - Destination: "arst", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(18), - Source: "foo", - Destination: "thing", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(24), - Source: "bar", - Destination: "thing", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(16), - Source: "bar", - Destination: "qux", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(4), - Source: "baz", - Destination: "qux", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(25), - Source: "baz", - Destination: "quz", - }, - }, - Error: nil, - } - test(t, tc) -} diff --git a/components/ledger/internal/machine/vm/machine_overdraft_test.go b/components/ledger/internal/machine/vm/machine_overdraft_test.go deleted file mode 100644 index c7d846b0d9..0000000000 --- a/components/ledger/internal/machine/vm/machine_overdraft_test.go +++ /dev/null @@ -1,271 +0,0 @@ -package vm - -import ( - "testing" - - "github.com/formancehq/ledger/internal/machine" -) - -func TestOverdraftNotEnough(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = @foo allowing overdraft up to [GEM 10] - destination = @world - )`) - tc.setBalance("foo", "GEM", 89) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInsufficientFund{}, - } - test(t, tc) -} - -func TestOverdraftEnough(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = @foo allowing overdraft up to [GEM 10] - destination = @world - )`) - tc.setBalance("foo", "GEM", 90) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(100), - Source: "foo", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestOverdraftUnbounded(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 1000] ( - source = @foo allowing unbounded overdraft - destination = @world - )`) - tc.setBalance("foo", "GEM", 90) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1000), - Source: "foo", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestOverdraftSourceAllotmentSuccess(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = { - 50% from @foo allowing overdraft up to [GEM 10] - 50% from { - @bar allowing overdraft up to [GEM 20] - @baz allowing unbounded overdraft - } - } - destination = @world - )`) - tc.setBalance("foo", "GEM", 40) - tc.setBalance("bar", "GEM", 20) - tc.setBalance("baz", "GEM", 0) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(50), - Source: "foo", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(40), - Source: "bar", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(10), - Source: "baz", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestOverdraftSourceInOrderSuccess(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = { - max [GEM 50] from { - @foo allowing overdraft up to [GEM 10] - @bar allowing overdraft up to [GEM 20] - @baz allowing unbounded overdraft - } - @qux allowing unbounded overdraft - } - destination = @world - )`) - tc.setBalance("foo", "GEM", 0) - tc.setBalance("bar", "GEM", 0) - tc.setBalance("baz", "GEM", 0) - tc.setBalance("qux", "GEM", 0) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(10), - Source: "foo", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(20), - Source: "bar", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(20), - Source: "baz", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(50), - Source: "qux", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestOverdraftBalanceTracking(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = @foo allowing unbounded overdraft - destination = @world - ) - send [GEM 200] ( - source = @foo allowing overdraft up to [GEM 300] - destination = @world - ) - send [GEM 300] ( - source = @foo allowing unbounded overdraft - destination = @world - ) - `) - tc.setBalance("foo", "GEM", 0) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(100), - Source: "foo", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(200), - Source: "foo", - Destination: "world", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(300), - Source: "foo", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestWorldIsUnbounded(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = @world - destination = @foo - ) - send [GEM 200] ( - source = @world - destination = @foo - ) - `) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(100), - Source: "world", - Destination: "foo", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(200), - Source: "world", - Destination: "foo", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestOverdraftComplexFailure(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = { - 50% from @foo allowing overdraft up to [GEM 10] - 50% from { - @bar allowing overdraft up to [GEM 20] - @baz - } - } - destination = @world - )`) - tc.setBalance("foo", "GEM", 40) - tc.setBalance("bar", "GEM", 20) - tc.setBalance("baz", "GEM", 0) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInsufficientFund{}, - } - test(t, tc) -} - -func TestNegativeBalance(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 100] ( - source = @foo - destination = @world - )`) - tc.setBalance("foo", "GEM", -50) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInsufficientFund{}, - } - test(t, tc) -} diff --git a/components/ledger/internal/machine/vm/machine_test.go b/components/ledger/internal/machine/vm/machine_test.go deleted file mode 100644 index 5df051f434..0000000000 --- a/components/ledger/internal/machine/vm/machine_test.go +++ /dev/null @@ -1,2417 +0,0 @@ -package vm - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "slices" - "sync" - "testing" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/machine/script/compiler" - "github.com/formancehq/ledger/internal/machine/vm/program" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - DEBUG bool = false -) - -type CaseResult struct { - Printed []machine.Value - Postings []Posting - Metadata map[string]machine.Value - Error error - ErrorContains string -} - -type TestCase struct { - program *program.Program - vars map[string]string - meta map[string]metadata.Metadata - balances map[string]map[string]*machine.MonetaryInt - expected CaseResult -} - -func NewTestCase() TestCase { - return TestCase{ - vars: make(map[string]string), - meta: make(map[string]metadata.Metadata), - balances: make(map[string]map[string]*machine.MonetaryInt), - expected: CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Metadata: make(map[string]machine.Value), - Error: nil, - }, - } -} - -func (c *TestCase) compile(t *testing.T, code string) { - p, err := compiler.Compile(code) - if err != nil { - t.Fatalf("compile error: %v", err) - return - } - c.program = p -} - -func (c *TestCase) setVarsFromJSON(t *testing.T, str string) { - var jsonVars map[string]string - err := json.Unmarshal([]byte(str), &jsonVars) - require.NoError(t, err) - c.vars = jsonVars -} - -func (c *TestCase) setBalance(account, asset string, amount int64) { - if _, ok := c.balances[account]; !ok { - c.balances[account] = make(map[string]*machine.MonetaryInt) - } - c.balances[account][asset] = machine.NewMonetaryInt(amount) -} - -func test(t *testing.T, testCase TestCase) { - testImpl(t, testCase.program, testCase.expected, func(m *Machine) error { - if err := m.SetVarsFromJSON(testCase.vars); err != nil { - return err - } - - store := StaticStore{} - for account, balances := range testCase.balances { - store[account] = &AccountWithBalances{ - Account: ledger.Account{ - Address: account, - Metadata: testCase.meta[account], - }, - Balances: func() map[string]*big.Int { - ret := make(map[string]*big.Int) - for asset, balance := range balances { - ret[asset] = (*big.Int)(balance) - } - return ret - }(), - } - } - - _, _, err := m.ResolveResources(context.Background(), store) - if err != nil { - return err - } - - err = m.ResolveBalances(context.Background(), store) - if err != nil { - return err - } - - return m.Execute() - }) -} - -func testImpl(t *testing.T, prog *program.Program, expected CaseResult, exec func(*Machine) error) { - printed := []machine.Value{} - - var wg sync.WaitGroup - wg.Add(1) - - require.NotNil(t, prog) - - m := NewMachine(*prog) - m.Debug = DEBUG - m.Printer = func(c chan machine.Value) { - for v := range c { - printed = append(printed, v) - } - wg.Done() - } - - err := exec(m) - if expected.Error != nil { - require.True(t, errors.Is(err, expected.Error), "got wrong error, want: %v, got: %v", expected.Error, err) - if expected.ErrorContains != "" { - require.ErrorContains(t, err, expected.ErrorContains) - } - } else { - require.NoError(t, err) - } - if err != nil { - return - } - - if expected.Postings == nil { - expected.Postings = make([]Posting, 0) - } - if expected.Metadata == nil { - expected.Metadata = make(map[string]machine.Value) - } - - assert.Equalf(t, expected.Postings, m.Postings, "unexpected postings output: %v", m.Postings) - assert.Equalf(t, expected.Metadata, m.TxMeta, "unexpected metadata output: %v", m.TxMeta) - - wg.Wait() - - assert.Equalf(t, expected.Printed, printed, "unexpected metadata output: %v", printed) -} - -func TestFail(t *testing.T) { - tc := NewTestCase() - tc.compile(t, "fail") - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: machine.ErrScriptFailed, - } - test(t, tc) -} - -func TestPrint(t *testing.T) { - tc := NewTestCase() - tc.compile(t, "print 29 + 15 - 2") - mi := machine.MonetaryInt(*big.NewInt(42)) - tc.expected = CaseResult{ - Printed: []machine.Value{&mi}, - Postings: []Posting{}, - Error: nil, - } - test(t, tc) -} - -func TestSend(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [EUR/2 100] ( - source=@alice - destination=@bob - )`) - tc.setBalance("alice", "EUR/2", 100) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "EUR/2", - Amount: machine.NewMonetaryInt(100), - Source: "alice", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestVariables(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $rider - account $driver - string $description - number $nb - asset $ass - } - send [$ass 999] ( - source=$rider - destination=$driver - ) - set_tx_meta("description", $description) - set_tx_meta("ride", $nb)`) - tc.vars = map[string]string{ - "rider": "users:001", - "driver": "users:002", - "description": "midnight ride", - "nb": "1", - "ass": "EUR/2", - } - tc.setBalance("users:001", "EUR/2", 1000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "EUR/2", - Amount: machine.NewMonetaryInt(999), - Source: "users:001", - Destination: "users:002", - }, - }, - Metadata: map[string]machine.Value{ - "description": machine.String("midnight ride"), - "ride": machine.NewMonetaryInt(1), - }, - Error: nil, - } - test(t, tc) -} - -func TestVariablesJSON(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $rider - account $driver - string $description - number $nb - asset $ass - } - send [$ass 999] ( - source=$rider - destination=$driver - ) - set_tx_meta("description", $description) - set_tx_meta("ride", $nb)`) - tc.setVarsFromJSON(t, `{ - "rider": "users:001", - "driver": "users:002", - "description": "midnight ride", - "nb": "1", - "ass": "EUR/2" - }`) - tc.setBalance("users:001", "EUR/2", 1000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "EUR/2", - Amount: machine.NewMonetaryInt(999), - Source: "users:001", - Destination: "users:002", - }, - }, - Metadata: map[string]machine.Value{ - "description": machine.String("midnight ride"), - "ride": machine.NewMonetaryInt(1), - }, - Error: nil, - } - test(t, tc) -} - -func TestSource(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $balance - account $payment - account $seller - } - send [GEM 15] ( - source = { - $balance - $payment - } - destination = $seller - )`) - tc.setVarsFromJSON(t, `{ - "balance": "users:001", - "payment": "payments:001", - "seller": "users:002" - }`) - tc.setBalance("users:001", "GEM", 3) - tc.setBalance("payments:001", "GEM", 12) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(3), - Source: "users:001", - Destination: "users:002", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(12), - Source: "payments:001", - Destination: "users:002", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestAllocation(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $rider - account $driver - } - send [GEM 15] ( - source = $rider - destination = { - 80% to $driver - 8% to @a - 12% to @b - } - )`) - tc.setVarsFromJSON(t, `{ - "rider": "users:001", - "driver": "users:002" - }`) - tc.setBalance("users:001", "GEM", 15) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(13), - Source: "users:001", - Destination: "users:002", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1), - Source: "users:001", - Destination: "a", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1), - Source: "users:001", - Destination: "b", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestDynamicAllocation(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - portion $p - } - send [GEM 15] ( - source = @a - destination = { - 80% to @b - $p to @c - remaining to @d - } - )`) - tc.setVarsFromJSON(t, `{ - "p": "15%" - }`) - tc.setBalance("a", "GEM", 15) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(13), - Source: "a", - Destination: "b", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(2), - Source: "a", - Destination: "c", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSendAll(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [USD/2 *] ( - source = @users:001 - destination = @platform - )`) - tc.setBalance("users:001", "USD/2", 17) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(17), - Source: "users:001", - Destination: "platform", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSendAllMulti(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [USD/2 *] ( - source = { - @users:001:wallet - @users:001:credit - } - destination = @platform - ) - `) - tc.setBalance("users:001:wallet", "USD/2", 19) - tc.setBalance("users:001:credit", "USD/2", 22) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(19), - Source: "users:001:wallet", - Destination: "platform", - }, - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(22), - Source: "users:001:credit", - Destination: "platform", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestInsufficientFunds(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $balance - account $payment - account $seller - } - send [GEM 16] ( - source = { - $balance - $payment - } - destination = $seller - )`) - tc.setVarsFromJSON(t, `{ - "balance": "users:001", - "payment": "payments:001", - "seller": "users:002" - }`) - tc.setBalance("users:001", "GEM", 3) - tc.setBalance("payments:001", "GEM", 12) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInsufficientFund{}, - } - test(t, tc) -} - -func TestWorldSource(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 15] ( - source = { - @a - @world - } - destination = @b - )`) - tc.setBalance("a", "GEM", 1) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1), - Source: "a", - Destination: "b", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(14), - Source: "world", - Destination: "b", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestUnboundedSourceSimple(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 15] ( - source = @unbounded allowing unbounded overdraft - destination = @b - )`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(15), - Source: "unbounded", - Destination: "b", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestUnboundedSourceInorder(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 15] ( - source = { - @a - @unbounded allowing unbounded overdraft - } - destination = @b - )`) - tc.setBalance("a", "GEM", 1) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(1), - Source: "a", - Destination: "b", - }, - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(14), - Source: "unbounded", - Destination: "b", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestNoEmptyPostings(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM 2] ( - source = @world - destination = { - 90% to @a - 10% to @b - } - )`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "GEM", - Amount: machine.NewMonetaryInt(2), - Source: "world", - Destination: "a", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestEmptyPostings(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [GEM *] ( - source = @foo - destination = @bar - )`) - tc.setBalance("foo", "GEM", 0) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Source: "foo", - Destination: "bar", - Amount: machine.NewMonetaryInt(0), - Asset: "GEM", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestAllocateDontTakeTooMuch(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [CREDIT 200] ( - source = { - @users:001 - @users:002 - } - destination = { - 1/2 to @foo - 1/2 to @bar - } - )`) - tc.setBalance("users:001", "CREDIT", 100) - tc.setBalance("users:002", "CREDIT", 110) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "CREDIT", - Amount: machine.NewMonetaryInt(100), - Source: "users:001", - Destination: "foo", - }, - { - Asset: "CREDIT", - Amount: machine.NewMonetaryInt(100), - Source: "users:002", - Destination: "bar", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestMetadata(t *testing.T) { - //commission, _ := machine.NewPortionSpecific(*big.NewRat(125, 1000)) - tc := NewTestCase() - tc.compile(t, `vars { - account $sale - account $seller = meta($sale, "seller") - portion $commission = meta($seller, "commission") - } - send [EUR/2 100] ( - source = $sale - destination = { - remaining to $seller - $commission to @platform - } - )`) - tc.setVarsFromJSON(t, `{ - "sale": "sales:042" - }`) - tc.meta = map[string]metadata.Metadata{ - "sales:042": { - "seller": "users:053", - }, - "users:053": { - "commission": "12.5%", - }, - } - tc.setBalance("sales:042", "EUR/2", 2500) - tc.setBalance("users:053", "EUR/2", 500) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "EUR/2", - Amount: machine.NewMonetaryInt(88), - Source: "sales:042", - Destination: "users:053", - }, - { - Asset: "EUR/2", - Amount: machine.NewMonetaryInt(12), - Source: "sales:042", - Destination: "platform", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestTrackBalances(t *testing.T) { - tc := NewTestCase() - tc.compile(t, ` - send [COIN 50] ( - source = @world - destination = @a - ) - send [COIN 100] ( - source = @a - destination = @b - )`) - tc.setBalance("a", "COIN", 50) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(50), - Source: "world", - Destination: "a", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(100), - Source: "a", - Destination: "b", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestTrackBalances2(t *testing.T) { - tc := NewTestCase() - tc.compile(t, ` - send [COIN 50] ( - source = @a - destination = @z - ) - send [COIN 50] ( - source = @a - destination = @z - )`) - tc.setBalance("a", "COIN", 60) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInsufficientFund{}, - } - test(t, tc) -} - -func TestTrackBalances3(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [COIN *] ( - source = @foo - destination = { - max [COIN 1000] to @bar - remaining kept - } - ) - send [COIN *] ( - source = @foo - destination = @bar - )`) - tc.setBalance("foo", "COIN", 2000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(1000), - Source: "foo", - Destination: "bar", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(1000), - Source: "foo", - Destination: "bar", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSourceAllotment(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [COIN 100] ( - source = { - 60% from @a - 35.5% from @b - 4.5% from @c - } - destination = @d - )`) - tc.setBalance("a", "COIN", 100) - tc.setBalance("b", "COIN", 100) - tc.setBalance("c", "COIN", 100) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(61), - Source: "a", - Destination: "d", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(35), - Source: "b", - Destination: "d", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(4), - Source: "c", - Destination: "d", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSourceOverlapping(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [COIN 99] ( - source = { - 15% from { - @b - @a - } - 30% from @a - remaining from @a - } - destination = @world - )`) - tc.setBalance("a", "COIN", 99) - tc.setBalance("b", "COIN", 3) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(3), - Source: "b", - Destination: "world", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(96), - Source: "a", - Destination: "world", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSourceComplex(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - monetary $max - } - send [COIN 200] ( - source = { - 50% from { - max [COIN 4] from @a - @b - @c - } - remaining from max $max from @d - } - destination = @platform - )`) - tc.setVarsFromJSON(t, `{ - "max": "COIN 120" - }`) - tc.setBalance("a", "COIN", 1000) - tc.setBalance("b", "COIN", 40) - tc.setBalance("c", "COIN", 1000) - tc.setBalance("d", "COIN", 1000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(4), - Source: "a", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(40), - Source: "b", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(56), - Source: "c", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(100), - Source: "d", - Destination: "platform", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestDestinationComplex(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `send [COIN 100] ( - source = @world - destination = { - 20% to @a - 20% kept - 60% to { - max [COIN 10] to @b - remaining to @c - } - } - )`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(20), - Source: "world", - Destination: "a", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(10), - Source: "world", - Destination: "b", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(50), - Source: "world", - Destination: "c", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestNeededBalances(t *testing.T) { - p, err := compiler.Compile(`vars { - account $a - } - send [GEM 15] ( - source = { - // normal accounts are tracked - $a - @b - - // we don't want to track world, as it is an unbounded account - max [GEM 1] from @world - - // we want to lock bounded overdrafts account - @bounded allowing overdraft up to [GEM 1] - - // we don't want to lock unbounded overdrafts account - @unb allowing unbounded overdraft - } - destination = { - max [GEM 1] to @c - remaining to @world - } - )`) - - if err != nil { - t.Fatalf("did not expect error on Compile, got: %v", err) - } - - m := NewMachine(*p) - - err = m.SetVarsFromJSON(map[string]string{ - "a": "a", - }) - if err != nil { - t.Fatalf("did not expect error on SetVars, got: %v", err) - } - readLockAccounts, writeLockAccounts, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - require.Equalf(t, []string{"c"}, readLockAccounts, "readlock") - require.Equalf(t, []string{"a", "b", "bounded"}, writeLockAccounts, "writelock") - - store := mockStore{} - err = m.ResolveBalances(context.Background(), &store) - require.NoError(t, err) - - require.Equal(t, []string{"a", "b", "bounded"}, store.GetRequestedAccounts()) -} - -func TestNeededBalances2(t *testing.T) { - p, err := compiler.Compile(` - send [GEM 15] ( - source = { - // we want to track a balance even if it appears later on - // as an unbounded overdraft - max [GEM 1] from @a - @a allowing unbounded overdraft - } - destination = @c - )`) - - if err != nil { - t.Fatalf("did not expect error on Compile, got: %v", err) - } - - m := NewMachine(*p) - _, involvedSources, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - require.Equal(t, []string{"a"}, involvedSources) - -} - -func TestNeededBalancesBalanceFn(t *testing.T) { - p, err := compiler.Compile(`vars { - monetary $balance = balance(@acc, COIN) -} - -send $balance ( - source = @a - destination = @b -)`) - - if err != nil { - t.Fatalf("did not expect error on Compile, got: %v", err) - } - - m := NewMachine(*p) - rlAccounts, wlAccounts, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - require.Equal(t, []string{"a"}, wlAccounts) - require.Equal(t, []string{"acc", "b"}, rlAccounts) - - store := mockStore{} - err = m.ResolveBalances(context.Background(), &store) - require.NoError(t, err) - require.Equal(t, []string{"a", "acc"}, store.GetRequestedAccounts()) -} - -func TestNeededBalancesBalanceOfMeta(t *testing.T) { - p, err := compiler.Compile(`vars { - account $src = meta(@x, "k") -} - -send [COIN 1] ( - source = $src - destination = @dest -)`) - - if err != nil { - t.Fatalf("did not expect error on Compile, got: %v", err) - } - m := NewMachine(*p) - - staticStore := StaticStore{ - "x": &AccountWithBalances{ - Account: ledger.Account{ - Address: "x", - Metadata: metadata.Metadata{ - "k": "src", - }, - }, - Balances: map[string]*big.Int{}, - }, - } - rlAccounts, wlAccounts, err := m.ResolveResources(context.Background(), staticStore) - require.NoError(t, err) - require.Equal(t, []string{"src"}, wlAccounts) - require.Equal(t, []string{"dest"}, rlAccounts) - - store := mockStore{} - err = m.ResolveBalances(context.Background(), &store) - require.NoError(t, err) - require.Equal(t, []string{"src"}, store.GetRequestedAccounts()) -} - -func TestSetTxMeta(t *testing.T) { - p, err := compiler.Compile(` - set_tx_meta("aaa", @platform) - set_tx_meta("bbb", GEM) - set_tx_meta("ccc", 45) - set_tx_meta("ddd", "hello") - set_tx_meta("eee", [COIN 30]) - set_tx_meta("fff", 15%) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - err = m.ResolveBalances(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.Execute() - require.NoError(t, err) - - expectedMeta := map[string]string{ - "aaa": "platform", - "bbb": "GEM", - "ccc": "45", - "ddd": "hello", - "eee": "COIN 30", - "fff": "3/20", - } - - resMeta := m.GetTxMetaJSON() - assert.Equal(t, 6, len(resMeta)) - - for key, val := range resMeta { - assert.Equal(t, string(expectedMeta[key]), val) - } -} - -func TestSetAccountMeta(t *testing.T) { - t.Run("all types", func(t *testing.T) { - p, err := compiler.Compile(` - set_account_meta(@platform, "aaa", @platform) - set_account_meta(@platform, "bbb", GEM) - set_account_meta(@platform, "ccc", 45) - set_account_meta(@platform, "ddd", "hello") - set_account_meta(@platform, "eee", [COIN 30]) - set_account_meta(@platform, "fff", 15%) - set_account_meta(@platform, "string/with{very:}complicated \"useless\" chars", "string/with{very:}complicated \"useless\" chars")`, - ) - require.NoError(t, err) - - m := NewMachine(*p) - - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.ResolveBalances(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.Execute() - require.NoError(t, err) - - expectedMeta := metadata.Metadata{ - "aaa": "platform", - "bbb": "GEM", - "ccc": "45", - "ddd": "hello", - "eee": "COIN 30", - "fff": "3/20", - `string/with{very:}complicated "useless" chars`: `string/with{very:}complicated "useless" chars`, - } - - resMeta := m.GetAccountsMetaJSON() - assert.Equal(t, 1, len(resMeta)) - - for acc, meta := range resMeta { - assert.Equal(t, "platform", acc) - assert.Equal(t, 7, len(meta)) - for key, val := range meta { - assert.Equal(t, expectedMeta[key], val) - } - } - }) - - t.Run("with vars", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - account $acc - } - send [EUR/2 100] ( - source = @world - destination = $acc - ) - set_account_meta($acc, "fees", 1%) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "test", - })) - - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.ResolveBalances(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.Execute() - require.NoError(t, err) - - expectedMeta := map[string]json.RawMessage{ - "fees": json.RawMessage("1/100"), - } - - resMeta := m.GetAccountsMetaJSON() - assert.Equal(t, 1, len(resMeta)) - - for acc, meta := range resMeta { - assert.Equal(t, "test", acc) - assert.Equal(t, 1, len(meta)) - for key, val := range meta { - assert.Equal(t, string(expectedMeta[key]), val) - } - } - }) -} - -func TestVariableBalance(t *testing.T) { - script := ` - vars { - monetary $initial = balance(@A, USD/2) - } - send [USD/2 100] ( - source = { - @A - @C - } - destination = { - max $initial to @B - remaining to @D - } - )` - - t.Run("1", func(t *testing.T) { - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("A", "USD/2", 40) - tc.setBalance("C", "USD/2", 90) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(40), - Source: "A", - Destination: "B", - }, - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(60), - Source: "C", - Destination: "D", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("2", func(t *testing.T) { - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("A", "USD/2", 400) - tc.setBalance("C", "USD/2", 90) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(100), - Source: "A", - Destination: "B", - }, - }, - Error: nil, - } - test(t, tc) - }) - - script = ` - vars { - account $acc - monetary $initial = balance($acc, USD/2) - } - send [USD/2 100] ( - source = { - $acc - @C - } - destination = { - max $initial to @B - remaining to @D - } - )` - - t.Run("3", func(t *testing.T) { - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("A", "USD/2", 40) - tc.setBalance("C", "USD/2", 90) - tc.setVarsFromJSON(t, `{"acc": "A"}`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(40), - Source: "A", - Destination: "B", - }, - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(60), - Source: "C", - Destination: "D", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("4", func(t *testing.T) { - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("A", "USD/2", 400) - tc.setBalance("C", "USD/2", 90) - tc.setVarsFromJSON(t, `{"acc": "A"}`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD/2", - Amount: machine.NewMonetaryInt(100), - Source: "A", - Destination: "B", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("5", func(t *testing.T) { - tc := NewTestCase() - tc.compile(t, ` - vars { - monetary $max = balance(@maxAcc, COIN) - } - send [COIN 200] ( - source = { - 50% from { - max [COIN 4] from @a - @b - @c - } - remaining from max $max from @d - } - destination = @platform - )`) - tc.setBalance("maxAcc", "COIN", 120) - tc.setBalance("a", "COIN", 1000) - tc.setBalance("b", "COIN", 40) - tc.setBalance("c", "COIN", 1000) - tc.setBalance("d", "COIN", 1000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(4), - Source: "a", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(40), - Source: "b", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(56), - Source: "c", - Destination: "platform", - }, - { - Asset: "COIN", - Amount: machine.NewMonetaryInt(100), - Source: "d", - Destination: "platform", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("send negative monetary", func(t *testing.T) { - tc := NewTestCase() - script = ` - vars { - monetary $amount = balance(@world, USD/2) - } - send $amount ( - source = @A - destination = @B - )` - tc.compile(t, script) - tc.setBalance("world", "USD/2", -40) - tc.expected = CaseResult{ - Error: &machine.ErrNegativeAmount{}, - ErrorContains: "must be non-negative", - } - test(t, tc) - }) -} - -func TestVariablesParsing(t *testing.T) { - t.Run("account", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - account $acc - } - set_tx_meta("account", $acc) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "valid:acc", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "valid-acc", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "account:valid-acc", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "account:valid--acc", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "valid:acc", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "valid--acc", - })) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "-valid--acc", - })) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "-valid--acc-", - })) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "valid--acc-", - })) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "-", - })) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "acc": "---------", - })) - }) - - t.Run("asset", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - asset $ass - } - set_tx_meta("asset", $ass) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "ass": "USD/2", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "ass": "USD-2", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "ass": "USD/2", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "ass": "USD-2", - })) - }) - - t.Run("monetary", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - monetary $mon - } - set_tx_meta("monetary", $mon) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "mon": "EUR/2 100", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "mon": "invalid-asset 100", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "mon": "EUR/2", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "mon": "EUR/2 100", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "mon": "invalid-asset 100", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "mon": "EUR/2 null", - })) - }) - - t.Run("portion", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - portion $por - } - set_tx_meta("portion", $por) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "por": "1/2", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "por": "", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "por": "1/2", - })) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "por": "50%", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "por": "3/2", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "por": "200%", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "por": "", - })) - }) - - t.Run("string", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - string $str - } - set_tx_meta("string", $str) - `) - require.NoError(t, err) - - m := NewMachine(*p) - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "str": "valid string", - })) - }) - - t.Run("number", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - number $nbr - } - set_tx_meta("number", $nbr) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.NoError(t, m.SetVarsFromJSON(map[string]string{ - "nbr": "100", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "nbr": "string", - })) - - require.Error(t, m.SetVarsFromJSON(map[string]string{ - "nbr": `nil`, - })) - }) - - t.Run("missing variable", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - number $nbr - string $str - } - set_tx_meta("number", $nbr) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.ErrorContains(t, m.SetVarsFromJSON(map[string]string{ - "nbr": "100", - }), "missing variable $str") - }) - - t.Run("extraneous variable SetVars", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - number $nbr - } - set_tx_meta("number", $nbr) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.ErrorContains(t, m.SetVarsFromJSON(map[string]string{ - "nbr": "100", - "nbr2": "100", - }), "extraneous variable $nbr2") - }) - - t.Run("extraneous variable SetVarsFromJSON", func(t *testing.T) { - p, err := compiler.Compile(` - vars { - number $nbr - } - set_tx_meta("number", $nbr) - `) - require.NoError(t, err) - - m := NewMachine(*p) - - require.ErrorContains(t, m.SetVarsFromJSON(map[string]string{ - "nbr": `100`, - "nbr2": `100`, - }), "extraneous variable $nbr2") - }) -} - -func TestVariablesErrors(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - monetary $mon - } - send $mon ( - source = @alice - destination = @bob - )`) - tc.setBalance("alice", "COIN", 10) - tc.vars = map[string]string{ - "mon": "COIN -1", - } - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrInvalidVars{}, - ErrorContains: "negative amount", - } - test(t, tc) -} - -func TestSetVarsFromJSON(t *testing.T) { - - type testCase struct { - name string - script string - expectedError error - vars map[string]string - } - for _, tc := range []testCase{ - { - name: "missing var", - script: `vars { - account $dest - } - send [COIN 99] ( - source = @world - destination = $dest - )`, - expectedError: fmt.Errorf("missing variable $dest"), - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - p, err := compiler.Compile(tc.script) - require.NoError(t, err) - - m := NewMachine(*p) - err = m.SetVarsFromJSON(tc.vars) - if tc.expectedError != nil { - require.Error(t, err) - //TODO(gfyrag): refine error handling of SetVars/ResolveResources/ResolveBalances - require.Equal(t, tc.expectedError.Error(), err.Error()) - } else { - require.Nil(t, err) - } - }) - } -} - -func TestResolveResources(t *testing.T) { - - type testCase struct { - name string - script string - expectedError error - vars map[string]string - } - for _, tc := range []testCase{ - { - name: "missing metadata", - script: `vars { - account $sale - account $seller = meta($sale, "seller") - } - send [COIN *] ( - source = $sale - destination = $seller - )`, - vars: map[string]string{ - "sale": "sales:042", - }, - expectedError: &machine.ErrMissingMetadata{}, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - p, err := compiler.Compile(tc.script) - require.NoError(t, err) - - m := NewMachine(*p) - require.NoError(t, m.SetVarsFromJSON(tc.vars)) - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - if tc.expectedError != nil { - require.Error(t, err) - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestResolveBalances(t *testing.T) { - - type testCase struct { - name string - script string - expectedError error - vars map[string]string - store Store - } - for _, tc := range []testCase{ - { - name: "balance function with negative balance", - store: StaticStore{ - "users:001": &AccountWithBalances{ - Account: ledger.Account{ - Address: "users:001", - Metadata: metadata.Metadata{}, - }, - Balances: map[string]*big.Int{ - "COIN": big.NewInt(-100), - }, - }, - }, - script: ` - vars { - monetary $bal = balance(@users:001, COIN) - } - send $bal ( - source = @users:001 - destination = @world - )`, - expectedError: &machine.ErrNegativeAmount{}, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - p, err := compiler.Compile(tc.script) - require.NoError(t, err) - - m := NewMachine(*p) - require.NoError(t, m.SetVarsFromJSON(tc.vars)) - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - store := tc.store - if store == nil { - store = EmptyStore - } - - err = m.ResolveBalances(context.Background(), store) - if tc.expectedError != nil { - require.Error(t, err) - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestMachine(t *testing.T) { - p, err := compiler.Compile(` - vars { - account $dest - } - send [COIN 99] ( - source = @world - destination = $dest - )`) - require.NoError(t, err) - - t.Run("with debug", func(t *testing.T) { - m := NewMachine(*p) - m.Debug = true - - err = m.SetVarsFromJSON(map[string]string{ - "dest": "charlie", - }) - require.NoError(t, err) - - _, _, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.ResolveBalances(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.Execute() - require.NoError(t, err) - }) - - t.Run("err resources", func(t *testing.T) { - m := NewMachine(*p) - err := m.Execute() - require.True(t, errors.Is(err, machine.ErrResourcesNotInitialized)) - }) - - t.Run("err balances not initialized", func(t *testing.T) { - m := NewMachine(*p) - - err = m.SetVarsFromJSON(map[string]string{ - "dest": "charlie", - }) - require.NoError(t, err) - - _, _, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - err = m.Execute() - require.True(t, errors.Is(err, machine.ErrBalancesNotInitialized)) - }) - - t.Run("err resolve resources twice", func(t *testing.T) { - m := NewMachine(*p) - - err = m.SetVarsFromJSON(map[string]string{ - "dest": "charlie", - }) - require.NoError(t, err) - - _, _, err := m.ResolveResources(context.Background(), EmptyStore) - require.NoError(t, err) - - _, _, err = m.ResolveResources(context.Background(), EmptyStore) - require.ErrorContains(t, err, "tried to call ResolveResources twice") - }) - - t.Run("err missing var", func(t *testing.T) { - m := NewMachine(*p) - - _, _, err := m.ResolveResources(context.Background(), EmptyStore) - require.Error(t, err) - }) -} - -func TestVariableAsset(t *testing.T) { - script := ` - vars { - asset $ass - monetary $bal = balance(@alice, $ass) - } - - send [$ass 15] ( - source = { - @alice - @bob - } - destination = @swap - ) - - send [$ass *] ( - source = @swap - destination = { - max $bal to @alice_2 - remaining to @bob_2 - } - )` - - tc := NewTestCase() - tc.compile(t, script) - tc.vars = map[string]string{ - "ass": "USD", - } - tc.setBalance("alice", "USD", 10) - tc.setBalance("bob", "USD", 10) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "alice", - Destination: "swap", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(5), - Source: "bob", - Destination: "swap", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "swap", - Destination: "alice_2", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(5), - Source: "swap", - Destination: "bob_2", - }, - }, - Error: nil, - } - test(t, tc) -} - -func TestSaveFromAccount(t *testing.T) { - t.Run("simple", func(t *testing.T) { - script := ` - save [USD 10] from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(20), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("save all", func(t *testing.T) { - script := ` - save [USD *] from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(0), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(30), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("save more than balance", func(t *testing.T) { - script := ` - save [USD 30] from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(0), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(30), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("with asset var", func(t *testing.T) { - script := ` - vars { - asset $ass - } - save [$ass 10] from @alice - - send [$ass 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.vars = map[string]string{ - "ass": "USD", - } - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(20), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("with monetary var", func(t *testing.T) { - script := ` - vars { - monetary $mon - } - - save $mon from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.vars = map[string]string{ - "mon": "USD 10", - } - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(20), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("multi postings", func(t *testing.T) { - script := ` - send [USD 10] ( - source = @alice - destination = @bob - ) - - save [USD 5] from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(5), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(25), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("save a different asset", func(t *testing.T) { - script := ` - save [COIN 100] from @alice - - send [USD 30] ( - source = { - @alice - @world - } - destination = @bob - )` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("alice", "COIN", 100) - tc.setBalance("alice", "USD", 20) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - { - Asset: "USD", - Amount: machine.NewMonetaryInt(20), - Source: "alice", - Destination: "bob", - }, - { - Asset: "USD", - Amount: machine.NewMonetaryInt(10), - Source: "world", - Destination: "bob", - }, - }, - Error: nil, - } - test(t, tc) - }) - - t.Run("negative amount", func(t *testing.T) { - script := ` - vars { - monetary $amt = balance(@A, USD) - } - save $amt from @A` - tc := NewTestCase() - tc.compile(t, script) - tc.setBalance("A", "USD", -100) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{}, - Error: &machine.ErrNegativeAmount{}, - } - test(t, tc) - }) -} - -func TestUseDifferentAssetsWithSameSourceAccount(t *testing.T) { - tc := NewTestCase() - tc.compile(t, `vars { - account $a_account -} -send [A 100] ( - source = $a_account allowing unbounded overdraft - destination = @account1 -) -send [B 100] ( - source = @world - destination = @account2 -)`) - tc.setBalance("account1", "A", 100) - tc.setBalance("account2", "B", 100) - tc.setVarsFromJSON(t, `{"a_account": "world"}`) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{{ - Source: "world", - Destination: "account1", - Amount: machine.NewMonetaryInt(100), - Asset: "A", - }, { - Source: "world", - Destination: "account2", - Amount: machine.NewMonetaryInt(100), - Asset: "B", - }}, - } - test(t, tc) -} - -func TestMaxWithUnboundedOverdraft(t *testing.T) { - tc := NewTestCase() - tc.compile(t, ` -send [COIN 100] ( - source = { - max [COIN 10] from @account1 allowing unbounded overdraft - @account2 - } - destination = @world -)`) - tc.setBalance("account1", "COIN", 10000) - tc.setBalance("account2", "COIN", 10000) - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{{ - Source: "account1", - Destination: "world", - Amount: machine.NewMonetaryInt(10), - Asset: "COIN", - }, { - Source: "account2", - Destination: "world", - Amount: machine.NewMonetaryInt(90), - Asset: "COIN", - }}, - } - test(t, tc) -} - -func TestRepayUnboundedMinimal(t *testing.T) { - tc := NewTestCase() - - tc.compile(t, ` -send [COIN 100]( - source = @src allowing unbounded overdraft - destination = { - max [COIN 1] to @d1 - remaining to @d2 - } - ) -`) - - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - {"src", "d1", machine.NewMonetaryInt(1), "COIN"}, - {"src", "d2", machine.NewMonetaryInt(99), "COIN"}, - }, - } - test(t, tc) -} - -func TestRepayUnboundedComplex(t *testing.T) { - tc := NewTestCase() - - tc.compile(t, ` -send [EGP 86640]( - source = { - max [EGP 86640] from @asset:current_assets allowing unbounded overdraft - } - destination = { - max [EGP 86466] to @liability:client_balances - max [EGP 9] to @liability:current_liabilities:1 - max [EGP 9] to @liability:current_liabilities:2 - max [EGP 100] to @liability:current_liabilities:3 - max [EGP 4] to @liability:current_liabilities:4 - max [EGP 43] to @liability:current_liabilities:checks:5 - remaining to @liability:current_liabilities:6 - } - ) - -`) - - tc.expected = CaseResult{ - Printed: []machine.Value{}, - Postings: []Posting{ - {"asset:current_assets", "liability:client_balances", machine.NewMonetaryInt(86466), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:1", machine.NewMonetaryInt(9), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:2", machine.NewMonetaryInt(9), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:3", machine.NewMonetaryInt(100), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:4", machine.NewMonetaryInt(4), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:checks:5", machine.NewMonetaryInt(43), "EGP"}, - {"asset:current_assets", "liability:current_liabilities:6", machine.NewMonetaryInt(9), "EGP"}, - }, - } - test(t, tc) -} - -type mockStore struct { - requestedAccounts []string -} - -func (s *mockStore) GetRequestedAccounts() []string { - slices.Sort(s.requestedAccounts) - return s.requestedAccounts -} - -func (s *mockStore) GetBalance(ctx context.Context, address, asset string) (*big.Int, error) { - s.requestedAccounts = append(s.requestedAccounts, address) - return big.NewInt(0), nil -} - -func (s *mockStore) GetAccount(ctx context.Context, address string) (*ledger.Account, error) { - panic("not implemented") -} diff --git a/components/ledger/internal/machine/vm/program/instructions.go b/components/ledger/internal/machine/vm/program/instructions.go deleted file mode 100644 index 8d109451ee..0000000000 --- a/components/ledger/internal/machine/vm/program/instructions.go +++ /dev/null @@ -1,86 +0,0 @@ -package program - -const ( - OP_APUSH = byte(iota + 1) - OP_BUMP // *N => *N - OP_DELETE // - OP_IADD // + => - OP_ISUB // - => - OP_PRINT // - OP_FAIL // - OP_ASSET // => - OP_MONETARY_NEW // => - OP_MONETARY_ADD // + => // panics if not same asset - OP_MONETARY_SUB // - => // panics if not same asset - OP_MAKE_ALLOTMENT // *N => - OP_TAKE_ALL // => - OP_TAKE_ALWAYS // => // takes amount from account unconditionally - OP_TAKE // => // fails with EXIT_INSUFFICIENT_FUNDS if not enough - OP_TAKE_MAX // => // Doesn't fail on insufficient funds. Either missing or remaining is zero. - OP_FUNDING_ASSEMBLE // *N => (first has highest priority) - OP_FUNDING_SUM // => - OP_FUNDING_REVERSE // => - OP_REPAY // - OP_ALLOC // => *N - OP_SEND // - OP_TX_META // - OP_ACCOUNT_META // - OP_SAVE -) - -func OpcodeName(op byte) string { - switch op { - case OP_APUSH: - return "OP_APUSH" - case OP_BUMP: - return "OP_BUMP" - case OP_DELETE: - return "OP_DELETE" - case OP_IADD: - return "OP_IADD" - case OP_ISUB: - return "OP_ISUB" - case OP_PRINT: - return "OP_PRINT" - case OP_FAIL: - return "OP_FAIL" - case OP_ASSET: - return "OP_ASSET" - case OP_MONETARY_NEW: - return "OP_MONETARY_NEW" - case OP_MONETARY_ADD: - return "OP_MONETARY_ADD" - case OP_MONETARY_SUB: - return "OP_MONETARY_SUB" - case OP_MAKE_ALLOTMENT: - return "OP_MAKE_ALLOTMENT" - case OP_TAKE_ALL: - return "OP_TAKE_ALL" - case OP_TAKE_ALWAYS: - return "OP_TAKE_ALWAYS" - case OP_TAKE: - return "OP_TAKE" - case OP_TAKE_MAX: - return "OP_TAKE_MAX" - case OP_FUNDING_ASSEMBLE: - return "OP_FUNDING_ASSEMBLE" - case OP_FUNDING_SUM: - return "OP_FUNDING_SUM" - case OP_FUNDING_REVERSE: - return "OP_FUNDING_REVERSE" - case OP_REPAY: - return "OP_REPAY" - case OP_ALLOC: - return "OP_ALLOC" - case OP_SEND: - return "OP_SEND" - case OP_TX_META: - return "OP_TX_META" - case OP_ACCOUNT_META: - return "OP_ACCOUNT_META" - case OP_SAVE: - return "OP_SAVE" - default: - return "Unknown opcode" - } -} diff --git a/components/ledger/internal/machine/vm/program/program.go b/components/ledger/internal/machine/vm/program/program.go deleted file mode 100644 index fb93e8f9ae..0000000000 --- a/components/ledger/internal/machine/vm/program/program.go +++ /dev/null @@ -1,115 +0,0 @@ -package program - -import ( - "encoding/binary" - "fmt" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/pkg/errors" -) - -type Program struct { - Instructions []byte - Resources []Resource - NeededBalances map[machine.Address]map[machine.Address]struct{} - - ReadLockAccounts []machine.Address - WriteLockAccounts []machine.Address -} - -func (p Program) String() string { - out := "Program:\nINSTRUCTIONS\n" - for i := 0; i < len(p.Instructions); i++ { - out += fmt.Sprintf("%02d----- ", i) - switch p.Instructions[i] { - case OP_APUSH: - out += "OP_APUSH " - address := binary.LittleEndian.Uint16(p.Instructions[i+1 : i+3]) - out += fmt.Sprintf("#%d\n", address) - i += 2 - default: - out += OpcodeName(p.Instructions[i]) + "\n" - } - } - - out += fmt.Sprintln("RESOURCES") - i := 0 - for i = 0; i < len(p.Resources); i++ { - out += fmt.Sprintf("%02d ", i) - out += fmt.Sprintf("%v\n", p.Resources[i]) - } - return out -} - -func (p *Program) ParseVariables(vars map[string]machine.Value) (map[string]machine.Value, error) { - variables := make(map[string]machine.Value) - for _, res := range p.Resources { - if variable, ok := res.(Variable); ok { - if val, ok := vars[variable.Name]; ok && val.GetType() == variable.Typ { - variables[variable.Name] = val - switch val.GetType() { - case machine.TypeAccount: - if err := machine.ValidateAccountAddress(val.(machine.AccountAddress)); err != nil { - return nil, errors.Wrapf(err, "invalid variable $%s value '%s'", - variable.Name, string(val.(machine.AccountAddress))) - } - case machine.TypeAsset: - if err := machine.ValidateAsset(val.(machine.Asset)); err != nil { - return nil, errors.Wrapf(err, "invalid variable $%s value '%s'", - variable.Name, string(val.(machine.Asset))) - } - case machine.TypeMonetary: - if err := machine.ParseMonetary(val.(machine.Monetary)); err != nil { - return nil, errors.Wrapf(err, "invalid variable $%s value '%s'", - variable.Name, val.(machine.Monetary).String()) - } - case machine.TypePortion: - if err := machine.ValidatePortionSpecific(val.(machine.Portion)); err != nil { - return nil, errors.Wrapf(err, "invalid variable $%s value '%s'", - variable.Name, val.(machine.Portion).String()) - } - case machine.TypeString: - case machine.TypeNumber: - default: - return nil, fmt.Errorf("unsupported type for variable $%s: %s", - variable.Name, val.GetType()) - } - delete(vars, variable.Name) - } else if val, ok := vars[variable.Name]; ok && val.GetType() != variable.Typ { - return nil, fmt.Errorf("wrong type for variable $%s: %s instead of %s", - variable.Name, variable.Typ, val.GetType()) - } else { - return nil, fmt.Errorf("missing variable $%s", variable.Name) - } - } - } - for name := range vars { - return nil, fmt.Errorf("extraneous variable $%s", name) - } - return variables, nil -} - -func (p *Program) ParseVariablesJSON(vars map[string]string) (map[string]machine.Value, error) { - variables := make(map[string]machine.Value) - for _, res := range p.Resources { - if param, ok := res.(Variable); ok { - data, ok := vars[param.Name] - if !ok { - return nil, fmt.Errorf("missing variable $%s", param.Name) - } - val, err := machine.NewValueFromString(param.Typ, data) - if err != nil { - return nil, fmt.Errorf( - "invalid JSON value for variable $%s of type %v: %w", - param.Name, param.Typ, err) - } - variables[param.Name] = val - delete(vars, param.Name) - } - } - for name := range vars { - return nil, fmt.Errorf("extraneous variable $%s", name) - } - return variables, nil -} diff --git a/components/ledger/internal/machine/vm/program/program_test.go b/components/ledger/internal/machine/vm/program/program_test.go deleted file mode 100644 index 12303e0d98..0000000000 --- a/components/ledger/internal/machine/vm/program/program_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package program_test - -import ( - "testing" - - "github.com/formancehq/ledger/internal/machine/script/compiler" - "github.com/stretchr/testify/require" -) - -func TestProgram_String(t *testing.T) { - p, err := compiler.Compile(` - send [COIN 99] ( - source = @world - destination = @alice - )`) - require.NoError(t, err) - _ = p.String() -} diff --git a/components/ledger/internal/machine/vm/program/resource.go b/components/ledger/internal/machine/vm/program/resource.go deleted file mode 100644 index 983ca7f24b..0000000000 --- a/components/ledger/internal/machine/vm/program/resource.go +++ /dev/null @@ -1,59 +0,0 @@ -package program - -import ( - "fmt" - - "github.com/formancehq/ledger/internal/machine" -) - -type Resource interface { - GetType() machine.Type -} - -type Constant struct { - Inner machine.Value -} - -func (c Constant) GetType() machine.Type { return c.Inner.GetType() } -func (c Constant) String() string { return fmt.Sprintf("%v", c.Inner) } - -type Variable struct { - Typ machine.Type - Name string -} - -func (p Variable) GetType() machine.Type { return p.Typ } -func (p Variable) String() string { return fmt.Sprintf("<%v %v>", p.Typ, p.Name) } - -type VariableAccountMetadata struct { - Typ machine.Type - Name string - Account machine.Address - Key string -} - -func (m VariableAccountMetadata) GetType() machine.Type { return m.Typ } -func (m VariableAccountMetadata) String() string { - return fmt.Sprintf("<%v %v meta(%v, %v)>", m.Typ, m.Name, m.Account, m.Key) -} - -type VariableAccountBalance struct { - Name string - Account machine.Address - Asset machine.Address -} - -func (a VariableAccountBalance) GetType() machine.Type { return machine.TypeMonetary } -func (a VariableAccountBalance) String() string { - return fmt.Sprintf("<%v %v balance(%v, %v)>", machine.TypeMonetary, a.Name, a.Account, a.Asset) -} - -type Monetary struct { - Asset machine.Address - Amount *machine.MonetaryInt -} - -func (a Monetary) GetType() machine.Type { return machine.TypeMonetary } -func (a Monetary) String() string { - return fmt.Sprintf("<%v [%v %v]>", machine.TypeMonetary, a.Asset, a.Amount) -} diff --git a/components/ledger/internal/machine/vm/program/resource_test.go b/components/ledger/internal/machine/vm/program/resource_test.go deleted file mode 100644 index 2c5618a324..0000000000 --- a/components/ledger/internal/machine/vm/program/resource_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package program - -import ( - "testing" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/stretchr/testify/require" -) - -func TestResource(t *testing.T) { - c := Constant{ - Inner: machine.NewMonetaryInt(0), - } - c.GetType() - require.Equal(t, "0", c.String()) - - v := Variable{ - Typ: machine.TypeAccount, - Name: "acc", - } - require.Equal(t, "", v.String()) - - vab := VariableAccountBalance{ - Name: "name", - Account: machine.Address(0), - Asset: machine.Address(1), - } - require.Equal(t, "", vab.String()) - - vam := VariableAccountMetadata{ - Typ: machine.TypeMonetary, - Name: "name", - Account: machine.Address(0), - Key: "key", - } - require.Equal(t, "", vam.String()) -} diff --git a/components/ledger/internal/machine/vm/run.go b/components/ledger/internal/machine/vm/run.go deleted file mode 100644 index ae2cf0da40..0000000000 --- a/components/ledger/internal/machine/vm/run.go +++ /dev/null @@ -1,49 +0,0 @@ -package vm - -import ( - "math/big" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/pkg/errors" -) - -type Result struct { - Postings ledger.Postings - Metadata metadata.Metadata - AccountMetadata map[string]metadata.Metadata -} - -func Run(m *Machine, script ledger.RunScript) (*Result, error) { - err := m.Execute() - if err != nil { - return nil, errors.Wrap(err, "script execution failed") - } - - result := Result{ - Postings: make([]ledger.Posting, len(m.Postings)), - Metadata: m.GetTxMetaJSON(), - AccountMetadata: m.GetAccountsMetaJSON(), - } - - for j, posting := range m.Postings { - result.Postings[j] = ledger.Posting{ - Source: posting.Source, - Destination: posting.Destination, - Amount: (*big.Int)(posting.Amount), - Asset: posting.Asset, - } - } - - for k, v := range script.Metadata { - _, ok := result.Metadata[k] - if ok { - return nil, machine.NewErrMetadataOverride(k) - } - result.Metadata[k] = v - } - - return &result, nil -} diff --git a/components/ledger/internal/machine/vm/run_test.go b/components/ledger/internal/machine/vm/run_test.go deleted file mode 100644 index c5b4af34f5..0000000000 --- a/components/ledger/internal/machine/vm/run_test.go +++ /dev/null @@ -1,453 +0,0 @@ -package vm - -import ( - "context" - "errors" - "math/big" - "testing" - - "github.com/formancehq/ledger/internal/machine" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/formancehq/ledger/internal/machine/script/compiler" - "github.com/stretchr/testify/require" -) - -type runTestCase struct { - name string - script string - vars map[string]string - expectErrorCode error - expectResult Result - store Store - metadata metadata.Metadata -} - -var runTestCases = []runTestCase{ - { - name: "nominal", - script: ` - send [USD/2 99] ( - source = @world - destination = @user:001 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "user:001", "USD/2", big.NewInt(99)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "not enough funds", - script: ` - send [USD/2 99] ( - source = @bank - destination = @user:001 - )`, - expectErrorCode: &machine.ErrInsufficientFund{}, - }, - { - name: "send $0", - script: ` - send [USD/2 0] ( - source = @alice - destination = @user:001 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("alice", "user:001", "USD/2", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send $0 world", - script: ` - send [USD/2 0] ( - source = @world - destination = @user:001 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "user:001", "USD/2", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send $42 dash", - script: ` - send [USD/2 42] ( - source = @world - destination = @user:001-toto - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "user:001-toto", "USD/2", big.NewInt(42)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send $42 dash 2", - script: ` - send [USD/2 42] ( - source = @world - destination = @user:001-toto - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "user:001-toto", "USD/2", big.NewInt(42)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send $42 dash 3", - script: ` - send [USD/2 42] ( - source = @world - destination = @--t-t--edd-st--- - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "--t-t--edd-st---", "USD/2", big.NewInt(42)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send all available", - script: ` - send [USD/2 *] ( - source = @alice - destination = @user:001 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("alice", "user:001", "USD/2", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "with variable", - script: ` - vars { - account $dest - } - - send [CAD/2 42] ( - source = @world - destination = $dest - )`, - vars: map[string]string{ - "dest": "user:001", - }, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "user:001", "CAD/2", big.NewInt(42)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "using metadata", - store: StaticStore{ - "sales:001": &AccountWithBalances{ - Account: ledger.Account{ - Address: "sales:001", - Metadata: metadata.Metadata{ - "seller": "users:001", - }, - }, - Balances: map[string]*big.Int{ - "COIN": big.NewInt(100), - }, - }, - "users:001": &AccountWithBalances{ - Account: ledger.Account{ - Address: "sales:001", - Metadata: metadata.Metadata{ - "commission": "15.5%", - }, - }, - Balances: map[string]*big.Int{}, - }, - }, - script: ` - vars { - account $sale - account $seller = meta($sale, "seller") - portion $commission = meta($seller, "commission") - } - - send [COIN *] ( - source = $sale - destination = { - remaining to $seller - $commission to @platform - } - ) - `, - vars: map[string]string{ - "sale": "sales:001", - }, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("sales:001", "users:001", "COIN", big.NewInt(85)), - ledger.NewPosting("sales:001", "platform", "COIN", big.NewInt(15)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "defining metadata from input", - script: ` - send [USD/2 99] ( - source = @world - destination = @users:001 - )`, - metadata: metadata.Metadata{ - "priority": "low", - }, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "users:001", "USD/2", big.NewInt(99)), - }, - Metadata: metadata.Metadata{ - "priority": "low", - }, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "defining metadata from script", - script: ` - set_tx_meta("priority", "low") - send [USD/2 99] ( - source = @world - destination = @users:001 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "users:001", "USD/2", big.NewInt(99)), - }, - Metadata: metadata.Metadata{ - "priority": "low", - }, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "override metadata from script", - script: ` - set_tx_meta("priority", "low") - send [USD/2 99] ( - source = @world - destination = @users:001 - )`, - metadata: metadata.Metadata{ - "priority": "low", - }, - expectErrorCode: &machine.ErrMetadataOverride{}, - }, - { - name: "set account meta", - script: ` - send [USD/2 99] ( - source = @world - destination = @users:001 - ) - set_account_meta(@alice, "aaa", "string meta") - set_account_meta(@alice, "bbb", 42) - set_account_meta(@alice, "ccc", COIN) - set_account_meta(@alice, "ddd", [COIN 30]) - set_account_meta(@alice, "eee", @bob) - `, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("world", "users:001", "USD/2", big.NewInt(99)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{ - "alice": { - "aaa": "string meta", - "bbb": "42", - "ccc": "COIN", - "ddd": "COIN 30", - "eee": "bob", - }, - }, - }, - }, - { - name: "balance function", - store: StaticStore{ - "users:001": { - Account: ledger.Account{ - Address: "users:001", - Metadata: metadata.Metadata{}, - }, - Balances: map[string]*big.Int{ - "COIN": big.NewInt(100), - }, - }, - }, - script: ` - vars { - monetary $bal = balance(@users:001, COIN) - } - send $bal ( - source = @users:001 - destination = @world - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("users:001", "world", "COIN", big.NewInt(100)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "overdraft", - script: ` - send [USD/2 100] ( - source = @users:001 allowing unbounded overdraft - destination = @123:users:002 - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("users:001", "123:users:002", "USD/2", big.NewInt(100)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send amount 0", - store: StaticStore{ - "alice": { - Account: ledger.Account{ - Address: "alice", - Metadata: metadata.Metadata{}, - }, - Balances: map[string]*big.Int{}, - }, - }, - script: ` - send [USD 0] ( - source = @alice - destination = @bob - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("alice", "bob", "USD", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send all with balance 0", - store: StaticStore{ - "alice": { - Account: ledger.Account{ - Address: "alice", - Metadata: metadata.Metadata{}, - }, - Balances: map[string]*big.Int{}, - }, - }, - script: ` - send [USD *] ( - source = @alice - destination = @bob - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("alice", "bob", "USD", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, - { - name: "send account balance of 0", - store: StaticStore{ - "alice": { - Account: ledger.Account{ - Address: "alice", - Metadata: metadata.Metadata{}, - }, - Balances: map[string]*big.Int{}, - }, - }, - script: ` - vars { - monetary $bal = balance(@alice, USD) - } - send $bal ( - source = @alice - destination = @bob - )`, - expectResult: Result{ - Postings: []ledger.Posting{ - ledger.NewPosting("alice", "bob", "USD", big.NewInt(0)), - }, - Metadata: metadata.Metadata{}, - AccountMetadata: map[string]metadata.Metadata{}, - }, - }, -} - -func TestRun(t *testing.T) { - t.Parallel() - - for _, tc := range runTestCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - - if tc.store == nil { - tc.store = StaticStore{} - } - - program, err := compiler.Compile(tc.script) - require.NoError(t, err) - - m := NewMachine(*program) - require.NoError(t, m.SetVarsFromJSON(tc.vars)) - - _, _, err = m.ResolveResources(context.Background(), tc.store) - require.NoError(t, err) - require.NoError(t, m.ResolveBalances(context.Background(), tc.store)) - - result, err := Run(m, ledger.RunScript{ - Script: ledger.Script{ - Plain: tc.script, - Vars: tc.vars, - }, - Metadata: tc.metadata, - }) - if tc.expectErrorCode != nil { - require.True(t, errors.Is(err, tc.expectErrorCode)) - } else { - require.NoError(t, err) - require.NotNil(t, result) - require.Equal(t, tc.expectResult, *result) - } - }) - } -} diff --git a/components/ledger/internal/machine/vm/stack.go b/components/ledger/internal/machine/vm/stack.go deleted file mode 100644 index b4f695098b..0000000000 --- a/components/ledger/internal/machine/vm/stack.go +++ /dev/null @@ -1,26 +0,0 @@ -package vm - -import ( - "fmt" - - "github.com/formancehq/ledger/internal/machine" -) - -func (m *Machine) popValue() machine.Value { - l := len(m.Stack) - x := m.Stack[l-1] - m.Stack = m.Stack[:l-1] - return x -} - -func pop[T machine.Value](m *Machine) T { - x := m.popValue() - if v, ok := x.(T); ok { - return v - } - panic(fmt.Errorf("unexpected type '%T' on stack", x)) -} - -func (m *Machine) pushValue(v machine.Value) { - m.Stack = append(m.Stack, v) -} diff --git a/components/ledger/internal/machine/vm/store.go b/components/ledger/internal/machine/vm/store.go deleted file mode 100644 index 188f65e943..0000000000 --- a/components/ledger/internal/machine/vm/store.go +++ /dev/null @@ -1,65 +0,0 @@ -package vm - -import ( - "context" - "math/big" - - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" -) - -type Store interface { - GetBalance(ctx context.Context, address, asset string) (*big.Int, error) - GetAccount(ctx context.Context, address string) (*ledger.Account, error) -} - -type emptyStore struct{} - -func (e *emptyStore) GetBalance(ctx context.Context, address, asset string) (*big.Int, error) { - return new(big.Int), nil -} - -func (e *emptyStore) GetAccount(ctx context.Context, address string) (*ledger.Account, error) { - return &ledger.Account{ - Address: address, - Metadata: metadata.Metadata{}, - }, nil -} - -var _ Store = (*emptyStore)(nil) - -var EmptyStore = &emptyStore{} - -type AccountWithBalances struct { - ledger.Account - Balances map[string]*big.Int -} - -type StaticStore map[string]*AccountWithBalances - -func (s StaticStore) GetBalance(ctx context.Context, address, asset string) (*big.Int, error) { - account, ok := s[address] - if !ok { - return new(big.Int), nil - } - balance, ok := account.Balances[asset] - if !ok { - return new(big.Int), nil - } - - return balance, nil -} - -func (s StaticStore) GetAccount(ctx context.Context, address string) (*ledger.Account, error) { - account, ok := s[address] - if !ok { - return &ledger.Account{ - Address: address, - Metadata: metadata.Metadata{}, - }, nil - } - - return &account.Account, nil -} - -var _ Store = StaticStore{} diff --git a/components/ledger/internal/metadata.go b/components/ledger/internal/metadata.go deleted file mode 100644 index 564ff1b552..0000000000 --- a/components/ledger/internal/metadata.go +++ /dev/null @@ -1,37 +0,0 @@ -package ledger - -import ( - "math/big" - - "github.com/formancehq/go-libs/metadata" -) - -const ( - formanceNamespace = "com.formance.spec/" - revertKey = "state/reverts" - - MetaTargetTypeAccount = "ACCOUNT" - MetaTargetTypeTransaction = "TRANSACTION" -) - -func SpecMetadata(name string) string { - return formanceNamespace + name -} - -func MarkReverts(m metadata.Metadata, txID *big.Int) metadata.Metadata { - return m.Merge(RevertMetadata(txID)) -} - -func RevertMetadataSpecKey() string { - return SpecMetadata(revertKey) -} - -func ComputeMetadata(key, value string) metadata.Metadata { - return metadata.Metadata{ - key: value, - } -} - -func RevertMetadata(tx *big.Int) metadata.Metadata { - return ComputeMetadata(RevertMetadataSpecKey(), tx.String()) -} diff --git a/components/ledger/internal/numscript.go b/components/ledger/internal/numscript.go deleted file mode 100644 index 9936f86bcf..0000000000 --- a/components/ledger/internal/numscript.go +++ /dev/null @@ -1,123 +0,0 @@ -package ledger - -import ( - "fmt" - "sort" - "strings" - - "github.com/formancehq/go-libs/metadata" -) - -type variable struct { - name string - value string -} - -func TxToScriptData(txData TransactionData, allowUnboundedOverdrafts bool) RunScript { - sb := strings.Builder{} - monetaryToVars := map[string]variable{} - accountsToVars := map[string]variable{} - i := 0 - j := 0 - for _, p := range txData.Postings { - if _, ok := accountsToVars[p.Source]; !ok { - if p.Source != WORLD { - accountsToVars[p.Source] = variable{ - name: fmt.Sprintf("va%d", i), - value: p.Source, - } - i++ - } - } - if _, ok := accountsToVars[p.Destination]; !ok { - if p.Destination != WORLD { - accountsToVars[p.Destination] = variable{ - name: fmt.Sprintf("va%d", i), - value: p.Destination, - } - i++ - } - } - mon := fmt.Sprintf("[%s %s]", p.Amount.String(), p.Asset) - if _, ok := monetaryToVars[mon]; !ok { - monetaryToVars[mon] = variable{ - name: fmt.Sprintf("vm%d", j), - value: fmt.Sprintf("%s %s", p.Asset, p.Amount.String()), - } - j++ - } - } - - sb.WriteString("vars {\n") - accVars := make([]string, 0) - for _, v := range accountsToVars { - accVars = append(accVars, v.name) - } - sort.Strings(accVars) - for _, v := range accVars { - sb.WriteString(fmt.Sprintf("\taccount $%s\n", v)) - } - monVars := make([]string, 0) - for _, v := range monetaryToVars { - monVars = append(monVars, v.name) - } - sort.Strings(monVars) - for _, v := range monVars { - sb.WriteString(fmt.Sprintf("\tmonetary $%s\n", v)) - } - sb.WriteString("}\n") - - for _, p := range txData.Postings { - m := fmt.Sprintf("[%s %s]", p.Amount.String(), p.Asset) - mon, ok := monetaryToVars[m] - if !ok { - panic(fmt.Sprintf("monetary %s not found", m)) - } - sb.WriteString(fmt.Sprintf("send $%s (\n", mon.name)) - if p.Source == WORLD { - sb.WriteString("\tsource = @world\n") - } else { - src, ok := accountsToVars[p.Source] - if !ok { - panic(fmt.Sprintf("source %s not found", p.Source)) - } - sb.WriteString(fmt.Sprintf("\tsource = $%s", src.name)) - if allowUnboundedOverdrafts { - sb.WriteString(" allowing unbounded overdraft") - } - sb.WriteString("\n") - } - if p.Destination == WORLD { - sb.WriteString("\tdestination = @world\n") - } else { - dest, ok := accountsToVars[p.Destination] - if !ok { - panic(fmt.Sprintf("destination %s not found", p.Destination)) - } - sb.WriteString(fmt.Sprintf("\tdestination = $%s\n", dest.name)) - } - sb.WriteString(")\n") - } - - vars := map[string]string{} - for _, v := range accountsToVars { - vars[v.name] = v.value - } - for _, v := range monetaryToVars { - vars[v.name] = v.value - } - - if txData.Metadata == nil { - txData.Metadata = metadata.Metadata{} - } - - return RunScript{ - Script: Script{ - Plain: sb.String(), - Vars: vars, - }, - Timestamp: txData.Timestamp, - Metadata: txData.Metadata, - Reference: txData.Reference, - } -} diff --git a/components/ledger/internal/opentelemetry/metrics/metrics.go b/components/ledger/internal/opentelemetry/metrics/metrics.go deleted file mode 100644 index 1cc65dea03..0000000000 --- a/components/ledger/internal/opentelemetry/metrics/metrics.go +++ /dev/null @@ -1,89 +0,0 @@ -package metrics - -import ( - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" -) - -type GlobalRegistry interface { - APILatencies() metric.Int64Histogram - StatusCodes() metric.Int64Counter - ActiveLedgers() metric.Int64UpDownCounter -} - -type globalRegistry struct { - // API Latencies - apiLatencies metric.Int64Histogram - statusCodes metric.Int64Counter - activeLedgers metric.Int64UpDownCounter -} - -func RegisterGlobalRegistry(meterProvider metric.MeterProvider) (GlobalRegistry, error) { - meter := meterProvider.Meter("global") - - apiLatencies, err := meter.Int64Histogram( - "ledger.api.time", - metric.WithUnit("ms"), - metric.WithDescription("Latency of API calls"), - ) - if err != nil { - return nil, err - } - - statusCodes, err := meter.Int64Counter( - "ledger.api.status", - metric.WithUnit("1"), - metric.WithDescription("Status codes of API calls"), - ) - if err != nil { - return nil, err - } - - activeLedgers, err := meter.Int64UpDownCounter( - "ledger.api.ledgers", - metric.WithUnit("1"), - metric.WithDescription("Number of active ledgers"), - ) - if err != nil { - return nil, err - } - - return &globalRegistry{ - apiLatencies: apiLatencies, - statusCodes: statusCodes, - activeLedgers: activeLedgers, - }, nil -} - -func (gm *globalRegistry) APILatencies() metric.Int64Histogram { - return gm.apiLatencies -} - -func (gm *globalRegistry) StatusCodes() metric.Int64Counter { - return gm.statusCodes -} - -func (gm *globalRegistry) ActiveLedgers() metric.Int64UpDownCounter { - return gm.activeLedgers -} - -type noOpRegistry struct{} - -func NewNoOpRegistry() *noOpRegistry { - return &noOpRegistry{} -} - -func (nm *noOpRegistry) APILatencies() metric.Int64Histogram { - histogram, _ := noop.NewMeterProvider().Meter("ledger").Int64Histogram("api_latencies") - return histogram -} - -func (nm *noOpRegistry) StatusCodes() metric.Int64Counter { - counter, _ := noop.NewMeterProvider().Meter("ledger").Int64Counter("status_codes") - return counter -} - -func (nm *noOpRegistry) ActiveLedgers() metric.Int64UpDownCounter { - counter, _ := noop.NewMeterProvider().Meter("ledger").Int64UpDownCounter("active_ledgers") - return counter -} diff --git a/components/ledger/internal/opentelemetry/tracer/tracer.go b/components/ledger/internal/opentelemetry/tracer/tracer.go deleted file mode 100644 index 97ab0fe1b5..0000000000 --- a/components/ledger/internal/opentelemetry/tracer/tracer.go +++ /dev/null @@ -1,14 +0,0 @@ -package tracer - -import ( - "context" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" -) - -var Tracer = otel.Tracer("com.formance.ledger") - -func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { - return Tracer.Start(ctx, name, opts...) -} diff --git a/components/ledger/internal/posting.go b/components/ledger/internal/posting.go deleted file mode 100644 index 6a48194a89..0000000000 --- a/components/ledger/internal/posting.go +++ /dev/null @@ -1,83 +0,0 @@ -package ledger - -import ( - "database/sql/driver" - "encoding/json" - "math/big" - - "github.com/formancehq/ledger/pkg/core/accounts" - "github.com/formancehq/ledger/pkg/core/assets" - - "github.com/pkg/errors" -) - -type Posting struct { - Source string `json:"source"` - Destination string `json:"destination"` - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` -} - -func NewPosting(source string, destination string, asset string, amount *big.Int) Posting { - return Posting{ - Source: source, - Destination: destination, - Amount: amount, - Asset: asset, - } -} - -type Postings []Posting - -func (p Postings) Reverse() { - for i := range p { - p[i].Source, p[i].Destination = p[i].Destination, p[i].Source - } - - for i := 0; i < len(p)/2; i++ { - p[i], p[len(p)-i-1] = p[len(p)-i-1], p[i] - } -} - -// Scan - Implement the database/sql scanner interface -func (p *Postings) Scan(value interface{}) error { - if value == nil { - return nil - } - v, err := driver.String.ConvertValue(value) - if err != nil { - return err - } - - *p = Postings{} - switch vv := v.(type) { - case []uint8: - return json.Unmarshal(vv, p) - case string: - return json.Unmarshal([]byte(vv), p) - default: - panic("not supported type") - } -} - -func (p Postings) Validate() (int, error) { - for i, p := range p { - if p.Amount == nil { - return i, errors.New("no amount defined") - } - if p.Amount.Cmp(Zero) < 0 { - return i, errors.New("negative amount") - } - if !accounts.ValidateAddress(p.Source) { - return i, errors.New("invalid source address") - } - if !accounts.ValidateAddress(p.Destination) { - return i, errors.New("invalid destination address") - } - if !assets.IsValid(p.Asset) { - return i, errors.New("invalid asset") - } - } - - return 0, nil -} diff --git a/components/ledger/internal/posting_test.go b/components/ledger/internal/posting_test.go deleted file mode 100644 index 13114815c1..0000000000 --- a/components/ledger/internal/posting_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package ledger - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestReverseMultiple(t *testing.T) { - p := Postings{ - { - Source: "world", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "payments:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - } - - expected := Postings{ - { - Source: "payments:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "world", - Amount: big.NewInt(100), - Asset: "COIN", - }, - } - - p.Reverse() - require.Equal(t, expected, p) -} - -func TestReverseSingle(t *testing.T) { - p := Postings{ - { - Source: "world", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - } - - expected := Postings{ - { - Source: "users:001", - Destination: "world", - Amount: big.NewInt(100), - Asset: "COIN", - }, - } - - p.Reverse() - require.Equal(t, expected, p) -} diff --git a/components/ledger/internal/script.go b/components/ledger/internal/script.go deleted file mode 100644 index 40322a3942..0000000000 --- a/components/ledger/internal/script.go +++ /dev/null @@ -1,41 +0,0 @@ -package ledger - -import ( - "fmt" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/metadata" -) - -type RunScript struct { - Script - Timestamp time.Time `json:"timestamp"` - Metadata metadata.Metadata `json:"metadata"` - Reference string `json:"reference"` -} - -type Script struct { - Plain string `json:"plain"` - Vars map[string]string `json:"vars" swaggertype:"object"` -} - -type ScriptV1 struct { - Script - Vars map[string]any `json:"vars"` -} - -func (s ScriptV1) ToCore() Script { - s.Script.Vars = map[string]string{} - for k, v := range s.Vars { - switch v := v.(type) { - case string: - s.Script.Vars[k] = v - case map[string]any: - s.Script.Vars[k] = fmt.Sprintf("%s %v", v["asset"], v["amount"]) - default: - s.Script.Vars[k] = fmt.Sprint(v) - } - } - return s.Script -} diff --git a/components/ledger/internal/storage/driver/driver.go b/components/ledger/internal/storage/driver/driver.go deleted file mode 100644 index 577871c8f3..0000000000 --- a/components/ledger/internal/storage/driver/driver.go +++ /dev/null @@ -1,234 +0,0 @@ -package driver - -import ( - "context" - "database/sql" - "sync" - - "github.com/formancehq/go-libs/bun/bundebug" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/metadata" - - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/pkg/errors" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/time" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/ledger/internal/storage/systemstore" -) - -const defaultBucket = "_default" - -var ( - ErrNeedUpgradeBucket = errors.New("need to upgrade bucket before add a new ledger on it") - ErrLedgerAlreadyExists = errors.New("ledger already exists") -) - -type LedgerConfiguration struct { - Bucket string `json:"bucket"` - Metadata metadata.Metadata `json:"metadata"` -} - -type LedgerState struct { - LedgerConfiguration - State string `json:"state"` -} - -type Driver struct { - systemStore *systemstore.Store - lock sync.Mutex - connectionOptions bunconnect.ConnectionOptions - buckets map[string]*ledgerstore.Bucket - db *bun.DB - debug bool -} - -func (d *Driver) GetSystemStore() *systemstore.Store { - return d.systemStore -} - -func (d *Driver) OpenBucket(ctx context.Context, name string) (*ledgerstore.Bucket, error) { - - bucket, ok := d.buckets[name] - if ok { - return bucket, nil - } - - hooks := make([]bun.QueryHook, 0) - if d.debug { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - b, err := ledgerstore.ConnectToBucket(ctx, d.connectionOptions, name, hooks...) - if err != nil { - return nil, err - } - d.buckets[name] = b - - return b, nil -} - -func (d *Driver) GetLedgerStore(ctx context.Context, name string, configuration LedgerState) (*ledgerstore.Store, error) { - d.lock.Lock() - defer d.lock.Unlock() - - bucket, err := d.OpenBucket(ctx, configuration.Bucket) - if err != nil { - return nil, err - } - - return bucket.GetLedgerStore(name) -} - -func (f *Driver) CreateLedgerStore(ctx context.Context, name string, configuration LedgerConfiguration) (*ledgerstore.Store, error) { - - tx, err := f.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return nil, err - } - defer func() { - _ = tx.Rollback() - }() - - if _, err := f.systemStore.GetLedger(ctx, name); err == nil { - return nil, ErrLedgerAlreadyExists - } else if !sqlutils.IsNotFoundError(err) { - return nil, err - } - - bucketName := defaultBucket - if configuration.Bucket != "" { - bucketName = configuration.Bucket - } - - bucket, err := f.OpenBucket(ctx, bucketName) - if err != nil { - return nil, errors.Wrap(err, "opening bucket") - } - - isInitialized, err := bucket.IsInitialized(ctx) - if err != nil { - return nil, errors.Wrap(err, "checking if bucket is initialized") - } - - if isInitialized { - isUpToDate, err := bucket.IsUpToDate(ctx) - if err != nil { - return nil, errors.Wrap(err, "checking if bucket is up to date") - } - if !isUpToDate { - return nil, ErrNeedUpgradeBucket - } - } else { - if err := ledgerstore.MigrateBucket(ctx, tx, bucketName); err != nil { - return nil, errors.Wrap(err, "migrating bucket") - } - } - - store, err := bucket.GetLedgerStore(name) - if err != nil { - return nil, errors.Wrap(err, "getting ledger store") - } - - _, err = systemstore.RegisterLedger(ctx, tx, &systemstore.Ledger{ - Name: name, - AddedAt: time.Now(), - Bucket: bucketName, - Metadata: configuration.Metadata, - State: systemstore.StateInitializing, - }) - if err != nil { - return nil, errors.Wrap(err, "registring ledger on system store") - } - - return store, errors.Wrap(tx.Commit(), "committing sql transaction") -} - -func (d *Driver) Initialize(ctx context.Context) error { - logging.FromContext(ctx).Debugf("Initialize driver") - - hooks := make([]bun.QueryHook, 0) - if d.debug { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - var err error - d.db, err = bunconnect.OpenSQLDB(ctx, d.connectionOptions, hooks...) - if err != nil { - return errors.Wrap(err, "connecting to database") - } - - if err := systemstore.Migrate(ctx, d.db); err != nil { - return errors.Wrap(err, "migrating data") - } - - d.systemStore, err = systemstore.Connect(ctx, d.connectionOptions, hooks...) - if err != nil { - return errors.Wrap(err, "connecting to system store") - } - - return nil -} - -func (d *Driver) UpgradeAllBuckets(ctx context.Context) error { - - systemStore := d.GetSystemStore() - - buckets := collectionutils.Set[string]{} - err := bunpaginate.Iterate(ctx, systemstore.NewListLedgersQuery(10), - func(ctx context.Context, q systemstore.ListLedgersQuery) (*bunpaginate.Cursor[systemstore.Ledger], error) { - return systemStore.ListLedgers(ctx, q) - }, - func(cursor *bunpaginate.Cursor[systemstore.Ledger]) error { - for _, name := range cursor.Data { - buckets.Put(name.Bucket) - } - return nil - }) - if err != nil { - return err - } - - for _, bucket := range collectionutils.Keys(buckets) { - bucket, err := d.OpenBucket(ctx, bucket) - if err != nil { - return err - } - - logging.FromContext(ctx).Infof("Upgrading bucket '%s'", bucket.Name()) - if err := bucket.Migrate(ctx); err != nil { - return err - } - } - - return nil -} - -func (d *Driver) Close() error { - if err := d.systemStore.Close(); err != nil { - return err - } - for _, b := range d.buckets { - if err := b.Close(); err != nil { - return err - } - } - if err := d.db.Close(); err != nil { - return err - } - return nil -} - -func New(connectionOptions bunconnect.ConnectionOptions) *Driver { - return &Driver{ - connectionOptions: connectionOptions, - buckets: make(map[string]*ledgerstore.Bucket), - } -} diff --git a/components/ledger/internal/storage/driver/driver_test.go b/components/ledger/internal/storage/driver/driver_test.go deleted file mode 100644 index e845dc9850..0000000000 --- a/components/ledger/internal/storage/driver/driver_test.go +++ /dev/null @@ -1,105 +0,0 @@ -//go:build it - -package driver_test - -import ( - "fmt" - "testing" - - "github.com/formancehq/ledger/internal/storage/driver" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/logging" - "github.com/google/uuid" - - "github.com/formancehq/ledger/internal/storage/storagetesting" - "github.com/stretchr/testify/require" -) - -func TestConfiguration(t *testing.T) { - t.Parallel() - - d := storagetesting.StorageDriver(t) - ctx := logging.TestingContext() - - require.NoError(t, d.GetSystemStore().InsertConfiguration(ctx, "foo", "bar")) - bar, err := d.GetSystemStore().GetConfiguration(ctx, "foo") - require.NoError(t, err) - require.Equal(t, "bar", bar) -} - -func TestConfigurationError(t *testing.T) { - t.Parallel() - - d := storagetesting.StorageDriver(t) - ctx := logging.TestingContext() - - _, err := d.GetSystemStore().GetConfiguration(ctx, "not_existing") - require.Error(t, err) - require.True(t, sqlutils.IsNotFoundError(err)) -} - -func TestErrorOnOutdatedBucket(t *testing.T) { - t.Parallel() - - ctx := logging.TestingContext() - d := storagetesting.StorageDriver(t) - - name := uuid.NewString() - - b, err := d.OpenBucket(ctx, name) - require.NoError(t, err) - t.Cleanup(func() { - _ = b.Close() - }) - - upToDate, err := b.IsUpToDate(ctx) - require.NoError(t, err) - require.False(t, upToDate) -} - -func TestGetLedgerFromDefaultBucket(t *testing.T) { - t.Parallel() - - d := storagetesting.StorageDriver(t) - ctx := logging.TestingContext() - - name := uuid.NewString() - _, err := d.CreateLedgerStore(ctx, name, driver.LedgerConfiguration{}) - require.NoError(t, err) -} - -func TestGetLedgerFromAlternateBucket(t *testing.T) { - t.Parallel() - - d := storagetesting.StorageDriver(t) - ctx := logging.TestingContext() - - ledgerName := "ledger0" - bucketName := "bucket0" - - _, err := d.CreateLedgerStore(ctx, ledgerName, driver.LedgerConfiguration{ - Bucket: bucketName, - }) - require.NoError(t, err) -} - -func TestUpgradeAllBuckets(t *testing.T) { - t.Parallel() - - d := storagetesting.StorageDriver(t) - ctx := logging.TestingContext() - - count := 30 - - for i := 0; i < count; i++ { - name := fmt.Sprintf("ledger%d", i) - _, err := d.CreateLedgerStore(ctx, name, driver.LedgerConfiguration{ - Bucket: name, - }) - require.NoError(t, err) - } - - require.NoError(t, d.UpgradeAllBuckets(ctx)) -} diff --git a/components/ledger/internal/storage/driver/main_test.go b/components/ledger/internal/storage/driver/main_test.go deleted file mode 100644 index 3bac1ed9ff..0000000000 --- a/components/ledger/internal/storage/driver/main_test.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build it - -package driver - -import ( - "testing" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/components/ledger/internal/storage/driver/module.go b/components/ledger/internal/storage/driver/module.go deleted file mode 100644 index 2a07515da6..0000000000 --- a/components/ledger/internal/storage/driver/module.go +++ /dev/null @@ -1,41 +0,0 @@ -package driver - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/spf13/cobra" - - "github.com/formancehq/go-libs/logging" - "go.uber.org/fx" -) - -type PostgresConfig struct { - ConnString string -} - -func FXModuleFromFlags(cmd *cobra.Command) fx.Option { - - options := make([]fx.Option, 0) - options = append(options, fx.Provide(func() (*bunconnect.ConnectionOptions, error) { - return bunconnect.ConnectionOptionsFromFlags(cmd) - })) - options = append(options, fx.Provide(func(connectionOptions *bunconnect.ConnectionOptions) (*Driver, error) { - return New(*connectionOptions), nil - })) - - options = append(options, fx.Invoke(func(driver *Driver, lifecycle fx.Lifecycle, logger logging.Logger) error { - lifecycle.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - logger.Infof("Initializing database...") - return driver.Initialize(ctx) - }, - OnStop: func(ctx context.Context) error { - logger.Infof("Closing driver...") - return driver.Close() - }, - }) - return nil - })) - return fx.Options(options...) -} diff --git a/components/ledger/internal/storage/inmemory.go b/components/ledger/internal/storage/inmemory.go deleted file mode 100644 index 8705fdcf6a..0000000000 --- a/components/ledger/internal/storage/inmemory.go +++ /dev/null @@ -1,139 +0,0 @@ -package storage - -import ( - "context" - "math/big" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" -) - -type InMemoryStore struct { - logs []*ledger.ChainedLog - transactions []*ledger.ExpandedTransaction - accounts []*ledger.Account -} - -func (m *InMemoryStore) GetTransactionByReference(ctx context.Context, ref string) (*ledger.ExpandedTransaction, error) { - filtered := collectionutils.Filter(m.transactions, func(transaction *ledger.ExpandedTransaction) bool { - return transaction.Reference == ref - }) - if len(filtered) == 0 { - return nil, sqlutils.ErrNotFound - } - return filtered[0], nil -} - -func (m *InMemoryStore) GetTransaction(ctx context.Context, txID *big.Int) (*ledger.Transaction, error) { - filtered := collectionutils.Filter(m.transactions, func(transaction *ledger.ExpandedTransaction) bool { - return transaction.ID.Cmp(txID) == 0 - }) - if len(filtered) == 0 { - return nil, sqlutils.ErrNotFound - } - return &filtered[0].Transaction, nil -} - -func (m *InMemoryStore) GetLastLog(ctx context.Context) (*ledger.ChainedLog, error) { - if len(m.logs) == 0 { - return nil, nil - } - return m.logs[len(m.logs)-1], nil -} - -func (m *InMemoryStore) GetBalance(ctx context.Context, address, asset string) (*big.Int, error) { - balance := new(big.Int) - - var processPostings = func(postings ledger.Postings) { - for _, posting := range postings { - if posting.Asset != asset { - continue - } - if posting.Source == address { - balance = balance.Sub(balance, posting.Amount) - } - if posting.Destination == address { - balance = balance.Add(balance, posting.Amount) - } - } - } - - for _, log := range m.logs { - switch payload := log.Data.(type) { - case ledger.NewTransactionLogPayload: - processPostings(payload.Transaction.Postings) - case ledger.RevertedTransactionLogPayload: - processPostings(payload.RevertTransaction.Postings) - } - } - return balance, nil -} - -func (m *InMemoryStore) GetAccount(ctx context.Context, address string) (*ledger.Account, error) { - account := collectionutils.Filter(m.accounts, func(account *ledger.Account) bool { - return account.Address == address - }) - if len(account) == 0 { - return &ledger.Account{ - Address: address, - Metadata: metadata.Metadata{}, - }, nil - } - return account[0], nil -} - -func (m *InMemoryStore) ReadLogWithIdempotencyKey(ctx context.Context, key string) (*ledger.ChainedLog, error) { - first := collectionutils.First(m.logs, func(log *ledger.ChainedLog) bool { - return log.IdempotencyKey == key - }) - if first == nil { - return nil, sqlutils.ErrNotFound - } - return first, nil -} - -func (m *InMemoryStore) InsertLogs(ctx context.Context, logs ...*ledger.ChainedLog) error { - - m.logs = append(m.logs, logs...) - for _, log := range logs { - switch payload := log.Data.(type) { - case ledger.NewTransactionLogPayload: - m.transactions = append(m.transactions, &ledger.ExpandedTransaction{ - Transaction: *payload.Transaction, - // TODO - PreCommitVolumes: nil, - PostCommitVolumes: nil, - }) - case ledger.RevertedTransactionLogPayload: - tx := collectionutils.Filter(m.transactions, func(transaction *ledger.ExpandedTransaction) bool { - return transaction.ID.Cmp(payload.RevertedTransactionID) == 0 - })[0] - tx.Reverted = true - m.transactions = append(m.transactions, &ledger.ExpandedTransaction{ - Transaction: *payload.RevertTransaction, - // TODO - PreCommitVolumes: nil, - PostCommitVolumes: nil, - }) - case ledger.SetMetadataLogPayload: - } - } - - return nil -} - -func (m *InMemoryStore) GetLastTransaction(ctx context.Context) (*ledger.ExpandedTransaction, error) { - if len(m.transactions) == 0 { - return nil, sqlutils.ErrNotFound - } - return m.transactions[len(m.transactions)-1], nil -} - -func NewInMemoryStore() *InMemoryStore { - return &InMemoryStore{ - logs: []*ledger.ChainedLog{}, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/accounts.go b/components/ledger/internal/storage/ledgerstore/accounts.go deleted file mode 100644 index c45ced5194..0000000000 --- a/components/ledger/internal/storage/ledgerstore/accounts.go +++ /dev/null @@ -1,275 +0,0 @@ -package ledgerstore - -import ( - "context" - "errors" - "fmt" - "regexp" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/uptrace/bun" -) - -func (store *Store) buildAccountQuery(q PITFilterWithVolumes, query *bun.SelectQuery) *bun.SelectQuery { - - query = query. - Column("accounts.address", "accounts.first_usage"). - Where("accounts.ledger = ?", store.name). - Apply(filterPIT(q.PIT, "first_usage")). - Order("accounts.address") - - if q.PIT != nil && !q.PIT.IsZero() { - query = query. - Column("accounts.address"). - ColumnExpr(`coalesce(accounts_metadata.metadata, '{}'::jsonb) as metadata`). - Join(` - left join lateral ( - select metadata, accounts_seq - from accounts_metadata - where accounts_metadata.accounts_seq = accounts.seq and accounts_metadata.date < ? - order by revision desc - limit 1 - ) accounts_metadata on true - `, q.PIT) - } else { - query = query.Column("metadata") - } - - if q.ExpandVolumes { - query = query. - ColumnExpr("volumes.*"). - Join("join get_account_aggregated_volumes(?, accounts.address, ?) volumes on true", store.name, q.PIT) - } - - if q.ExpandEffectiveVolumes { - query = query. - ColumnExpr("effective_volumes.*"). - Join("join get_account_aggregated_effective_volumes(?, accounts.address, ?) effective_volumes on true", store.name, q.PIT) - } - - return query -} - -func (store *Store) accountQueryContext(qb query.Builder, q GetAccountsQuery) (string, []any, error) { - metadataRegex := regexp.MustCompile("metadata\\[(.+)\\]") - balanceRegex := regexp.MustCompile("balance\\[(.*)\\]") - - return qb.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - convertOperatorToSQL := func() string { - switch operator { - case "$match": - return "=" - case "$lt": - return "<" - case "$gt": - return ">" - case "$lte": - return "<=" - case "$gte": - return ">=" - } - panic("unreachable") - } - switch { - case key == "address": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, errors.New("'address' column can only be used with $match") - } - switch address := value.(type) { - case string: - return filterAccountAddress(address, "accounts.address"), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'address'", address) - } - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, newErrInvalidQuery("'account' column can only be used with $match") - } - match := metadataRegex.FindAllStringSubmatch(key, 3) - - key := "metadata" - if q.Options.Options.PIT != nil && !q.Options.Options.PIT.IsZero() { - key = "accounts_metadata.metadata" - } - - return key + " @> ?", []any{map[string]any{ - match[0][1]: value, - }}, nil - case balanceRegex.Match([]byte(key)): - match := balanceRegex.FindAllStringSubmatch(key, 2) - - return fmt.Sprintf(`( - select balance_from_volumes(post_commit_volumes) - from moves - where asset = ? and account_address = accounts.address and ledger = ? - order by seq desc - limit 1 - ) %s ?`, convertOperatorToSQL()), []any{match[0][1], store.name, value}, nil - case key == "balance": - return fmt.Sprintf(`( - select balance_from_volumes(post_commit_volumes) - from moves - where account_address = accounts.address and ledger = ? - order by seq desc - limit 1 - ) %s ?`, convertOperatorToSQL()), []any{store.name, value}, nil - - case key == "metadata": - if operator != "$exists" { - return "", nil, newErrInvalidQuery("'metadata' key filter can only be used with $exists") - } - if q.Options.Options.PIT != nil && !q.Options.Options.PIT.IsZero() { - key = "accounts_metadata.metadata" - } - - return fmt.Sprintf("%s -> ? IS NOT NULL", key), []any{value}, nil - default: - return "", nil, newErrInvalidQuery("unknown key '%s' when building query", key) - } - })) -} - -func (store *Store) buildAccountListQuery(selectQuery *bun.SelectQuery, q GetAccountsQuery, where string, args []any) *bun.SelectQuery { - selectQuery = store.buildAccountQuery(q.Options.Options, selectQuery) - - if where != "" { - return selectQuery.Where(where, args...) - } - - return selectQuery -} - -func (store *Store) GetAccountsWithVolumes(ctx context.Context, q GetAccountsQuery) (*bunpaginate.Cursor[ledger.ExpandedAccount], error) { - var ( - where string - args []any - err error - ) - if q.Options.QueryBuilder != nil { - where, args, err = store.accountQueryContext(q.Options.QueryBuilder, q) - if err != nil { - return nil, err - } - } - - return paginateWithOffset[PaginatedQueryOptions[PITFilterWithVolumes], ledger.ExpandedAccount](store, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PITFilterWithVolumes]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildAccountListQuery(query, q, where, args) - }, - ) -} - -func (store *Store) GetAccount(ctx context.Context, address string) (*ledger.Account, error) { - account, err := fetch[*ledger.Account](store, false, ctx, func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - ColumnExpr("accounts.address"). - ColumnExpr("coalesce(accounts_metadata.metadata, '{}'::jsonb) as metadata"). - ColumnExpr("accounts.first_usage"). - Table("accounts"). - Join("left join accounts_metadata on accounts_metadata.accounts_seq = accounts.seq"). - Where("accounts.address = ?", address). - Where("accounts.ledger = ?", store.name). - Order("revision desc"). - Limit(1) - }) - if err != nil { - if storageerrors.IsNotFoundError(err) { - return pointer.For(ledger.NewAccount(address)), nil - } - return nil, err - } - return account, nil -} - -func (store *Store) GetAccountWithVolumes(ctx context.Context, q GetAccountQuery) (*ledger.ExpandedAccount, error) { - account, err := fetch[*ledger.ExpandedAccount](store, true, ctx, func(query *bun.SelectQuery) *bun.SelectQuery { - query = store.buildAccountQuery(q.PITFilterWithVolumes, query). - Where("accounts.address = ?", q.Addr). - Limit(1) - - return query - }) - if err != nil { - return nil, err - } - return account, nil -} - -func (store *Store) CountAccounts(ctx context.Context, q GetAccountsQuery) (int, error) { - var ( - where string - args []any - err error - ) - if q.Options.QueryBuilder != nil { - where, args, err = store.accountQueryContext(q.Options.QueryBuilder, q) - if err != nil { - return 0, err - } - } - - return count[ledger.Account](store, true, ctx, func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildAccountListQuery(query, q, where, args) - }) -} - -type GetAccountQuery struct { - PITFilterWithVolumes - Addr string -} - -func (q GetAccountQuery) WithPIT(pit time.Time) GetAccountQuery { - q.PIT = &pit - - return q -} - -func (q GetAccountQuery) WithExpandVolumes() GetAccountQuery { - q.ExpandVolumes = true - - return q -} - -func (q GetAccountQuery) WithExpandEffectiveVolumes() GetAccountQuery { - q.ExpandEffectiveVolumes = true - - return q -} - -func NewGetAccountQuery(addr string) GetAccountQuery { - return GetAccountQuery{ - Addr: addr, - } -} - -type GetAccountsQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PITFilterWithVolumes]] - -func (q GetAccountsQuery) WithExpandVolumes() GetAccountsQuery { - q.Options.Options.ExpandVolumes = true - - return q -} - -func (q GetAccountsQuery) WithExpandEffectiveVolumes() GetAccountsQuery { - q.Options.Options.ExpandEffectiveVolumes = true - - return q -} - -func NewGetAccountsQuery(opts PaginatedQueryOptions[PITFilterWithVolumes]) GetAccountsQuery { - return GetAccountsQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/accounts_test.go b/components/ledger/internal/storage/ledgerstore/accounts_test.go deleted file mode 100644 index 8d249a459c..0000000000 --- a/components/ledger/internal/storage/ledgerstore/accounts_test.go +++ /dev/null @@ -1,413 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - "math/big" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/stretchr/testify/require" -) - -func TestGetAccounts(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - require.NoError(t, store.InsertLogs(ctx, - ledger.ChainLogs( - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). - WithDate(now), - map[string]metadata.Metadata{ - "account:1": { - "category": "4", - }, - }, - ).WithDate(now), - ledger.NewSetMetadataOnAccountLog(time.Now(), "account:1", metadata.Metadata{"category": "1"}).WithDate(now.Add(time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "account:2", metadata.Metadata{"category": "2"}).WithDate(now.Add(2*time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "account:3", metadata.Metadata{"category": "3"}).WithDate(now.Add(3*time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "orders:1", metadata.Metadata{"foo": "bar"}).WithDate(now.Add(3*time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "orders:2", metadata.Metadata{"foo": "bar"}).WithDate(now.Add(3*time.Minute)), - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). - WithIDUint64(1). - WithDate(now.Add(4*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(100*time.Millisecond)), - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("account:1", "bank", "USD", big.NewInt(50))). - WithDate(now.Add(3*time.Minute)). - WithIDUint64(2), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(200*time.Millisecond)), - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(0))). - WithDate(now.Add(-time.Minute)). - WithIDUint64(3), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(200*time.Millisecond)), - )..., - )) - - t.Run("list all", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}))) - require.NoError(t, err) - require.Len(t, accounts.Data, 7) - }) - - t.Run("list using metadata", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("metadata[category]", "1")), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) - }) - - t.Run("list before date", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: &now, - }, - }))) - require.NoError(t, err) - require.Len(t, accounts.Data, 2) - }) - - t.Run("list with volumes", func(t *testing.T) { - t.Parallel() - - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{ - ExpandVolumes: true, - }).WithQueryBuilder(query.Match("address", "account:1")))) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) - require.Equal(t, ledger.VolumesByAssets{ - "USD": ledger.NewVolumesInt64(200, 50), - }, accounts.Data[0].Volumes) - }) - - t.Run("list with volumes using PIT", func(t *testing.T) { - t.Parallel() - - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: &now, - }, - ExpandVolumes: true, - }).WithQueryBuilder(query.Match("address", "account:1")))) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) - require.Equal(t, ledger.VolumesByAssets{ - "USD": ledger.NewVolumesInt64(100, 0), - }, accounts.Data[0].Volumes) - }) - - t.Run("list with effective volumes", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{ - ExpandEffectiveVolumes: true, - }).WithQueryBuilder(query.Match("address", "account:1")))) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) - require.Equal(t, ledger.VolumesByAssets{ - "USD": ledger.NewVolumesInt64(200, 50), - }, accounts.Data[0].EffectiveVolumes) - }) - - t.Run("list with effective volumes using PIT", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: &now, - }, - ExpandEffectiveVolumes: true, - }).WithQueryBuilder(query.Match("address", "account:1")))) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) - require.Equal(t, ledger.VolumesByAssets{ - "USD": ledger.NewVolumesInt64(100, 0), - }, accounts.Data[0].EffectiveVolumes) - }) - - t.Run("list using filter on address", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("address", "account:")), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 3) - }) - t.Run("list using filter on multiple address", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder( - query.Or( - query.Match("address", "account:1"), - query.Match("address", "orders:"), - ), - ), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 3) - }) - t.Run("list using filter on balances", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Lt("balance[USD]", 0)), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 1) // world - - accounts, err = store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Gt("balance[USD]", 0)), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 2) - require.Equal(t, "account:1", accounts.Data[0].Account.Address) - require.Equal(t, "bank", accounts.Data[1].Account.Address) - }) - - t.Run("list using filter on exists metadata", func(t *testing.T) { - t.Parallel() - accounts, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Exists("metadata", "foo")), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 2) - - accounts, err = store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Exists("metadata", "category")), - )) - require.NoError(t, err) - require.Len(t, accounts.Data, 3) - }) - - t.Run("list using filter invalid field", func(t *testing.T) { - t.Parallel() - _, err := store.GetAccountsWithVolumes(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Lt("invalid", 0)), - )) - require.Error(t, err) - require.True(t, IsErrInvalidQuery(err)) - }) -} - -func TestUpdateAccountsMetadata(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - - metadata := metadata.Metadata{ - "foo": "bar", - } - - require.NoError(t, store.InsertLogs(context.Background(), - ledger.NewSetMetadataOnAccountLog(time.Now(), "bank", metadata).ChainLog(nil), - ), "account insertion should not fail") - - account, err := store.GetAccountWithVolumes(context.Background(), NewGetAccountQuery("bank")) - require.NoError(t, err, "account retrieval should not fail") - - require.Equal(t, "bank", account.Address, "account address should match") - require.Equal(t, metadata, account.Metadata, "account metadata should match") -} - -func TestGetAccount(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - require.NoError(t, store.InsertLogs(ctx, - ledger.ChainLogs( - ledger.NewTransactionLog(ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "multi", "USD/2", big.NewInt(100)), - ).WithDate(now), map[string]metadata.Metadata{}), - ledger.NewSetMetadataLog(now.Add(time.Minute), ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: "multi", - Metadata: metadata.Metadata{ - "category": "gold", - }, - }), - ledger.NewTransactionLog(ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "multi", "USD/2", big.NewInt(0)), - ).WithID(big.NewInt(1)).WithDate(now.Add(-time.Minute)), map[string]metadata.Metadata{}), - )..., - )) - - t.Run("find account", func(t *testing.T) { - t.Parallel() - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("multi")) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{ - "category": "gold", - }, - FirstUsage: now.Add(-time.Minute), - }, - }, *account) - - account, err = store.GetAccountWithVolumes(ctx, NewGetAccountQuery("world")) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "world", - Metadata: metadata.Metadata{}, - FirstUsage: now.Add(-time.Minute), - }, - }, *account) - }) - - t.Run("find account in past", func(t *testing.T) { - t.Parallel() - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("multi").WithPIT(now.Add(-30*time.Second))) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{}, - FirstUsage: now.Add(-time.Minute), - }, - }, *account) - }) - - t.Run("find account with volumes", func(t *testing.T) { - t.Parallel() - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("multi"). - WithExpandVolumes()) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{ - "category": "gold", - }, - FirstUsage: now.Add(-time.Minute), - }, - Volumes: ledger.VolumesByAssets{ - "USD/2": ledger.NewVolumesInt64(100, 0), - }, - }, *account) - }) - - t.Run("find account with effective volumes", func(t *testing.T) { - t.Parallel() - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("multi"). - WithExpandEffectiveVolumes()) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{ - "category": "gold", - }, - FirstUsage: now.Add(-time.Minute), - }, - EffectiveVolumes: ledger.VolumesByAssets{ - "USD/2": ledger.NewVolumesInt64(100, 0), - }, - }, *account) - }) - - t.Run("find account using pit", func(t *testing.T) { - t.Parallel() - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("multi").WithPIT(now)) - require.NoError(t, err) - require.Equal(t, ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{}, - FirstUsage: now.Add(-time.Minute), - }, - }, *account) - }) - - t.Run("not existent account", func(t *testing.T) { - t.Parallel() - _, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("account_not_existing")) - require.Error(t, err) - }) - -} - -func TestGetAccountWithVolumes(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - ctx := logging.TestingContext() - - bigInt, _ := big.NewInt(0).SetString("999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10) - - require.NoError(t, store.InsertLogs(ctx, - ledger.ChainLogs( - ledger.NewTransactionLog(ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "multi", "USD/2", bigInt), - ).WithDate(now), map[string]metadata.Metadata{}), - )..., - )) - - accountWithVolumes, err := store.GetAccountWithVolumes(ctx, - NewGetAccountQuery("multi").WithExpandVolumes()) - require.NoError(t, err) - require.Equal(t, &ledger.ExpandedAccount{ - Account: ledger.Account{ - Address: "multi", - Metadata: metadata.Metadata{}, - FirstUsage: now, - }, - Volumes: map[string]*ledger.Volumes{ - "USD/2": ledger.NewEmptyVolumes().WithInput(bigInt), - }, - }, accountWithVolumes) -} - -func TestUpdateAccountMetadata(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - ctx := logging.TestingContext() - - require.NoError(t, store.InsertLogs(ctx, - ledger.NewSetMetadataOnAccountLog(time.Now(), "central_bank", metadata.Metadata{ - "foo": "bar", - }).ChainLog(nil), - )) - - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("central_bank")) - require.NoError(t, err) - require.EqualValues(t, "bar", account.Metadata["foo"]) -} - -func TestCountAccounts(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - ctx := logging.TestingContext() - - require.NoError(t, insertTransactions(ctx, store, - *ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "central_bank", "USD/2", big.NewInt(100)), - ), - )) - - countAccounts, err := store.CountAccounts(ctx, NewGetAccountsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}))) - require.NoError(t, err) - require.EqualValues(t, 2, countAccounts) // world + central_bank -} diff --git a/components/ledger/internal/storage/ledgerstore/balances.go b/components/ledger/internal/storage/ledgerstore/balances.go deleted file mode 100644 index e7df72cf7d..0000000000 --- a/components/ledger/internal/storage/ledgerstore/balances.go +++ /dev/null @@ -1,171 +0,0 @@ -package ledgerstore - -import ( - "context" - "errors" - "fmt" - "math/big" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/uptrace/bun" -) - -func (store *Store) GetAggregatedBalances(ctx context.Context, q GetAggregatedBalanceQuery) (ledger.BalancesByAssets, error) { - - var ( - needMetadata bool - subQuery string - args []any - err error - ) - if q.QueryBuilder != nil { - subQuery, args, err = q.QueryBuilder.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "address": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, newErrInvalidQuery("'address' column can only be used with $match") - } - - switch address := value.(type) { - case string: - return filterAccountAddress(address, "account_address"), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'address'", address) - } - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, newErrInvalidQuery("'metadata' column can only be used with $match") - } - match := metadataRegex.FindAllStringSubmatch(key, 3) - needMetadata = true - key := "accounts.metadata" - if q.PIT != nil { - key = "am.metadata" - } - - return key + " @> ?", []any{map[string]any{ - match[0][1]: value, - }}, nil - - case key == "metadata": - if operator != "$exists" { - return "", nil, newErrInvalidQuery("'metadata' key filter can only be used with $exists") - } - needMetadata = true - key := "accounts.metadata" - if q.PIT != nil && !q.PIT.IsZero() { - key = "am.metadata" - } - - return fmt.Sprintf("%s -> ? IS NOT NULL", key), []any{value}, nil - default: - return "", nil, newErrInvalidQuery("unknown key '%s' when building query", key) - } - })) - if err != nil { - return nil, err - } - } - - type Temp struct { - Aggregated ledger.VolumesByAssets `bun:"aggregated,type:jsonb"` - } - ret, err := fetch[*Temp](store, false, ctx, - func(selectQuery *bun.SelectQuery) *bun.SelectQuery { - pitColumn := "effective_date" - if q.UseInsertionDate { - pitColumn = "insertion_date" - } - moves := store.bucket.db. - NewSelect(). - Table(MovesTableName). - ColumnExpr("distinct on (moves.account_address, moves.asset) moves.*"). - Order("account_address", "asset"). - Where("moves.ledger = ?", store.name). - Apply(filterPIT(q.PIT, pitColumn)) - - if q.UseInsertionDate { - moves = moves.Order("moves.insertion_date desc") - } else { - moves = moves.Order("moves.effective_date desc") - } - moves = moves.Order("seq desc") - - if needMetadata { - if q.PIT != nil { - moves = moves.Join(`join lateral ( - select metadata - from accounts_metadata am - where am.accounts_seq = moves.accounts_seq and (? is null or date <= ?) - order by revision desc - limit 1 - ) am on true`, q.PIT, q.PIT) - } else { - moves = moves.Join(`join lateral ( - select metadata - from accounts a - where a.seq = moves.accounts_seq - ) accounts on true`) - } - } - if subQuery != "" { - moves = moves.Where(subQuery, args...) - } - - volumesColumn := "post_commit_effective_volumes" - if q.UseInsertionDate { - volumesColumn = "post_commit_volumes" - } - - asJsonb := selectQuery.NewSelect(). - TableExpr("moves"). - ColumnExpr(fmt.Sprintf("volumes_to_jsonb((moves.asset, (sum((moves.%s).inputs), sum((moves.%s).outputs))::volumes)) as aggregated", volumesColumn, volumesColumn)). - Group("moves.asset") - - return selectQuery. - With("moves", moves). - With("data", asJsonb). - TableExpr("data"). - ColumnExpr("aggregate_objects(data.aggregated) as aggregated") - }) - if err != nil && !errors.Is(err, sqlutils.ErrNotFound) { - return nil, err - } - if errors.Is(err, sqlutils.ErrNotFound) { - return ledger.BalancesByAssets{}, nil - } - - return ret.Aggregated.Balances(), nil -} - -func (store *Store) GetBalance(ctx context.Context, address, asset string) (*big.Int, error) { - type Temp struct { - Balance *big.Int `bun:"balance,type:numeric"` - } - v, err := fetch[*Temp](store, false, ctx, func(query *bun.SelectQuery) *bun.SelectQuery { - return query.TableExpr("get_account_balance(?, ?, ?) as balance", store.name, address, asset) - }) - if err != nil { - return nil, err - } - - return v.Balance, nil -} - -type GetAggregatedBalanceQuery struct { - PITFilter - QueryBuilder query.Builder - UseInsertionDate bool -} - -func NewGetAggregatedBalancesQuery(filter PITFilter, qb query.Builder, useInsertionDate bool) GetAggregatedBalanceQuery { - return GetAggregatedBalanceQuery{ - PITFilter: filter, - QueryBuilder: qb, - UseInsertionDate: useInsertionDate, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/balances_test.go b/components/ledger/internal/storage/ledgerstore/balances_test.go deleted file mode 100644 index 8b099d7f79..0000000000 --- a/components/ledger/internal/storage/ledgerstore/balances_test.go +++ /dev/null @@ -1,158 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "math/big" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - internaltesting "github.com/formancehq/ledger/internal/testing" - "github.com/stretchr/testify/require" -) - -func TestGetBalancesAggregated(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - bigInt, _ := big.NewInt(0).SetString("999999999999999999999999999999999999999999999999999999999999999999999999999999999", 10) - smallInt := big.NewInt(199) - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "users:1", "USD", bigInt), - ledger.NewPosting("world", "users:2", "USD", smallInt), - ).WithDate(now) - - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "users:1", "USD", bigInt), - ledger.NewPosting("world", "users:2", "USD", smallInt), - ledger.NewPosting("world", "xxx", "EUR", smallInt), - ).WithDate(now.Add(-time.Minute)).WithIDUint64(1) - - logs := []*ledger.Log{ - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}).WithDate(now), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}).WithDate(now.Add(time.Minute)), - ledger.NewSetMetadataLog(now.Add(time.Minute), ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: "users:1", - Metadata: metadata.Metadata{ - "category": "premium", - }, - }), - ledger.NewSetMetadataLog(now.Add(time.Minute), ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: "users:2", - Metadata: metadata.Metadata{ - "category": "premium", - }, - }), - ledger.NewDeleteMetadataLog(now.Add(2*time.Minute), ledger.DeleteMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: "users:2", - Key: "category", - }), - ledger.NewSetMetadataOnAccountLog(time.Now(), "users:1", metadata.Metadata{"category": "premium"}).WithDate(now.Add(time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "users:2", metadata.Metadata{"category": "2"}).WithDate(now.Add(time.Minute)), - ledger.NewSetMetadataOnAccountLog(time.Now(), "world", metadata.Metadata{"foo": "bar"}).WithDate(now.Add(time.Minute)), - } - - require.NoError(t, store.InsertLogs(ctx, ledger.ChainLogs(logs...)...)) - - t.Run("aggregate on all", func(t *testing.T) { - t.Parallel() - cursor, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{}, nil, false)) - require.NoError(t, err) - internaltesting.RequireEqual(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0), - "EUR": big.NewInt(0), - }, cursor) - }) - t.Run("filter on address", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{}, - query.Match("address", "users:"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Add( - big.NewInt(0).Mul(bigInt, big.NewInt(2)), - big.NewInt(0).Mul(smallInt, big.NewInt(2)), - ), - }, ret) - }) - t.Run("using pit on effective date", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{ - PIT: pointer.For(now.Add(-time.Second)), - }, query.Match("address", "users:"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Add( - bigInt, - smallInt, - ), - }, ret) - }) - t.Run("using pit on insertion date", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{ - PIT: pointer.For(now), - }, query.Match("address", "users:"), true)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Add( - bigInt, - smallInt, - ), - }, ret) - }) - t.Run("using a metadata and pit", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{ - PIT: pointer.For(now.Add(time.Minute)), - }, query.Match("metadata[category]", "premium"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Add( - big.NewInt(0).Mul(bigInt, big.NewInt(2)), - big.NewInt(0), - ), - }, ret) - }) - t.Run("using a metadata without pit", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{}, - query.Match("metadata[category]", "premium"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Mul(bigInt, big.NewInt(2)), - }, ret) - }) - t.Run("when no matching", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{}, - query.Match("metadata[category]", "guest"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{}, ret) - }) - - t.Run("using a filter exist on metadata", func(t *testing.T) { - t.Parallel() - ret, err := store.GetAggregatedBalances(ctx, NewGetAggregatedBalancesQuery(PITFilter{}, query.Exists("metadata", "category"), false)) - require.NoError(t, err) - require.Equal(t, ledger.BalancesByAssets{ - "USD": big.NewInt(0).Add( - big.NewInt(0).Mul(bigInt, big.NewInt(2)), - big.NewInt(0).Mul(smallInt, big.NewInt(2)), - ), - }, ret) - }) -} diff --git a/components/ledger/internal/storage/ledgerstore/bucket.go b/components/ledger/internal/storage/ledgerstore/bucket.go deleted file mode 100644 index dc0e217fbb..0000000000 --- a/components/ledger/internal/storage/ledgerstore/bucket.go +++ /dev/null @@ -1,162 +0,0 @@ -package ledgerstore - -import ( - "context" - "database/sql" - "embed" - "fmt" - - "github.com/formancehq/go-libs/migrations" - - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -//go:embed migrations -var migrationsDir embed.FS - -type Bucket struct { - name string - db *bun.DB -} - -func (b *Bucket) Name() string { - return b.name -} - -func (b *Bucket) Migrate(ctx context.Context) error { - return MigrateBucket(ctx, b.db, b.name) -} - -func (b *Bucket) GetMigrationsInfo(ctx context.Context) ([]migrations.Info, error) { - return getBucketMigrator(b.name).GetMigrations(ctx, b.db) -} - -func (b *Bucket) IsUpToDate(ctx context.Context) (bool, error) { - ret, err := getBucketMigrator(b.name).IsUpToDate(ctx, b.db) - if err != nil && errors.Is(err, migrations.ErrMissingVersionTable) { - return false, nil - } - return ret, err -} - -func (b *Bucket) Close() error { - return b.db.Close() -} - -func (b *Bucket) createLedgerStore(name string) (*Store, error) { - return New(b, name) -} - -func (b *Bucket) CreateLedgerStore(name string) (*Store, error) { - return b.createLedgerStore(name) -} - -func (b *Bucket) GetLedgerStore(name string) (*Store, error) { - return New(b, name) -} - -func (b *Bucket) IsInitialized(ctx context.Context) (bool, error) { - row := b.db.QueryRowContext(ctx, ` - select schema_name - from information_schema.schemata - where schema_name = ?; - `, b.name) - if row.Err() != nil { - return false, sqlutils.PostgresError(row.Err()) - } - var t string - if err := row.Scan(&t); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return false, nil - } - } - return true, nil -} - -func registerMigrations(migrator *migrations.Migrator, name string) { - ret, err := migrations.CollectMigrationFiles(migrationsDir, "migrations", func(s string) string { - return s - }) - if err != nil { - panic(err) - } - initSchema := ret[0] - - // notes(gfyrag): override default schema initialization to handle ledger v1 upgrades - ret[0] = migrations.Migration{ - Name: "Init schema", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - - needV1Upgrade := false - row := tx.QueryRowContext(ctx, `select exists ( - select from pg_tables - where schemaname = ? and tablename = 'log' - )`, name) - if row.Err() != nil { - return row.Err() - } - var ret string - if err := row.Scan(&ret); err != nil { - panic(err) - } - needV1Upgrade = ret != "false" - - oldSchemaRenamed := fmt.Sprintf(name + oldSchemaRenameSuffix) - if needV1Upgrade { - _, err := tx.ExecContext(ctx, fmt.Sprintf(`alter schema "%s" rename to "%s"`, name, oldSchemaRenamed)) - if err != nil { - return errors.Wrap(err, "renaming old schema") - } - _, err = tx.ExecContext(ctx, fmt.Sprintf(`create schema if not exists "%s"`, name)) - if err != nil { - return errors.Wrap(err, "creating new schema") - } - } - - if err := initSchema.UpWithContext(ctx, tx); err != nil { - return errors.Wrap(err, "initializing new schema") - } - - if needV1Upgrade { - if err := migrateLogs(ctx, oldSchemaRenamed, name, tx); err != nil { - return errors.Wrap(err, "migrating logs") - } - - _, err = tx.ExecContext(ctx, fmt.Sprintf(`create table goose_db_version as table "%s".goose_db_version with no data`, oldSchemaRenamed)) - if err != nil { - return err - } - } - - return nil - }, - } - - migrator.RegisterMigrations(ret...) -} - -func ConnectToBucket(ctx context.Context, connectionOptions bunconnect.ConnectionOptions, name string, hooks ...bun.QueryHook) (*Bucket, error) { - db, err := bunconnect.OpenDBWithSchema(ctx, connectionOptions, name, hooks...) - if err != nil { - return nil, sqlutils.PostgresError(err) - } - - return &Bucket{ - db: db, - name: name, - }, nil -} - -func getBucketMigrator(name string) *migrations.Migrator { - migrator := migrations.NewMigrator(migrations.WithSchema(name, true)) - registerMigrations(migrator, name) - return migrator -} - -func MigrateBucket(ctx context.Context, db bun.IDB, name string) error { - return getBucketMigrator(name).Up(ctx, db) -} diff --git a/components/ledger/internal/storage/ledgerstore/bucket_test.go b/components/ledger/internal/storage/ledgerstore/bucket_test.go deleted file mode 100644 index 2268b7d4ce..0000000000 --- a/components/ledger/internal/storage/ledgerstore/bucket_test.go +++ /dev/null @@ -1,73 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "math/big" - "testing" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestBuckets(t *testing.T) { - ctx := logging.TestingContext() - bucket := newBucket(t) - var ( - ledger0 = uuid.NewString() - ledger1 = uuid.NewString() - ) - ledger0Store, err := bucket.CreateLedgerStore(ledger0) - require.NoError(t, err) - - ledger1Store, err := bucket.CreateLedgerStore(ledger1) - require.NoError(t, err) - - txLedger0 := ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Metadata: metadata.Metadata{}, - }, - } - - txLedger1 := ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Metadata: metadata.Metadata{}, - }, - } - - require.NoError(t, ledger0Store.InsertLogs(ctx, - ledger.NewTransactionLog(&txLedger0, map[string]metadata.Metadata{}).ChainLog(nil), - )) - require.NoError(t, ledger1Store.InsertLogs(ctx, - ledger.NewTransactionLog(&txLedger1, map[string]metadata.Metadata{}).ChainLog(nil), - )) - - count, err := ledger0Store.CountTransactions(ctx, NewGetTransactionsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{})) - require.NoError(t, err) - require.Equal(t, count, 1) - - count, err = ledger1Store.CountTransactions(ctx, NewGetTransactionsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{})) - require.NoError(t, err) - require.Equal(t, count, 1) -} diff --git a/components/ledger/internal/storage/ledgerstore/errors.go b/components/ledger/internal/storage/ledgerstore/errors.go deleted file mode 100644 index aa95d49b50..0000000000 --- a/components/ledger/internal/storage/ledgerstore/errors.go +++ /dev/null @@ -1,30 +0,0 @@ -package ledgerstore - -import ( - "fmt" - - "github.com/pkg/errors" -) - -type errInvalidQuery struct { - msg string -} - -func (e *errInvalidQuery) Error() string { - return e.msg -} - -func (e *errInvalidQuery) Is(err error) bool { - _, ok := err.(*errInvalidQuery) - return ok -} - -func newErrInvalidQuery(msg string, args ...any) *errInvalidQuery { - return &errInvalidQuery{ - msg: fmt.Sprintf(msg, args...), - } -} - -func IsErrInvalidQuery(err error) bool { - return errors.Is(err, &errInvalidQuery{}) -} diff --git a/components/ledger/internal/storage/ledgerstore/logs.go b/components/ledger/internal/storage/ledgerstore/logs.go deleted file mode 100644 index 50a5362d9c..0000000000 --- a/components/ledger/internal/storage/ledgerstore/logs.go +++ /dev/null @@ -1,178 +0,0 @@ -package ledgerstore - -import ( - "context" - "database/sql/driver" - "encoding/json" - "fmt" - "math/big" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type Logs struct { - bun.BaseModel `bun:"table:logs,alias:logs"` - - Ledger string `bun:"ledger,type:varchar"` - ID *bunpaginate.BigInt `bun:"id,unique,type:numeric"` - Type string `bun:"type,type:log_type"` - Hash []byte `bun:"hash,type:bytea"` - Date time.Time `bun:"date,type:timestamptz"` - Data RawMessage `bun:"data,type:jsonb"` - IdempotencyKey *string `bun:"idempotency_key,type:varchar(256),unique"` -} - -func (log *Logs) ToCore() *ledger.ChainedLog { - - payload, err := ledger.HydrateLog(ledger.LogTypeFromString(log.Type), log.Data) - if err != nil { - panic(errors.Wrap(err, "hydrating log data")) - } - - return &ledger.ChainedLog{ - Log: ledger.Log{ - Type: ledger.LogTypeFromString(log.Type), - Data: payload, - Date: log.Date.UTC(), - IdempotencyKey: func() string { - if log.IdempotencyKey != nil { - return *log.IdempotencyKey - } - return "" - }(), - }, - ID: (*big.Int)(log.ID), - Hash: log.Hash, - } -} - -type RawMessage json.RawMessage - -func (j RawMessage) Value() (driver.Value, error) { - if j == nil { - return nil, nil - } - return string(j), nil -} - -func (store *Store) logsQueryBuilder(q PaginatedQueryOptions[any]) func(*bun.SelectQuery) *bun.SelectQuery { - return func(selectQuery *bun.SelectQuery) *bun.SelectQuery { - - selectQuery = selectQuery.Where("ledger = ?", store.name) - if q.QueryBuilder != nil { - subQuery, args, err := q.QueryBuilder.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "date": - return fmt.Sprintf("%s %s ?", key, query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - default: - return "", nil, fmt.Errorf("unknown key '%s' when building query", key) - } - })) - if err != nil { - panic(err) - } - selectQuery = selectQuery.Where(subQuery, args...) - } - - return selectQuery - } -} - -func (store *Store) InsertLogs(ctx context.Context, activeLogs ...*ledger.ChainedLog) error { - _, err := store.bucket.db. - NewInsert(). - Model(pointer.For(collectionutils.Map(activeLogs, func(from *ledger.ChainedLog) Logs { - data, err := json.Marshal(from.Data) - if err != nil { - panic(err) - } - - return Logs{ - Ledger: store.name, - ID: (*bunpaginate.BigInt)(from.ID), - Type: from.Type.String(), - Hash: from.Hash, - Date: from.Date, - Data: data, - IdempotencyKey: func() *string { - if from.IdempotencyKey != "" { - return &from.IdempotencyKey - } - return nil - }(), - } - }))). - Exec(ctx) - return err -} - -func (store *Store) GetLastLog(ctx context.Context) (*ledger.ChainedLog, error) { - ret, err := fetch[*Logs](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - OrderExpr("id desc"). - Where("ledger = ?", store.name). - Limit(1) - }) - if err != nil { - return nil, err - } - - return ret.ToCore(), nil -} - -func (store *Store) GetLogs(ctx context.Context, q GetLogsQuery) (*bunpaginate.Cursor[ledger.ChainedLog], error) { - logs, err := paginateWithColumn[PaginatedQueryOptions[any], Logs](store, ctx, - (*bunpaginate.ColumnPaginatedQuery[PaginatedQueryOptions[any]])(&q), - store.logsQueryBuilder(q.Options), - ) - if err != nil { - return nil, err - } - - return bunpaginate.MapCursor(logs, func(from Logs) ledger.ChainedLog { - return *from.ToCore() - }), nil -} - -func (store *Store) ReadLogWithIdempotencyKey(ctx context.Context, key string) (*ledger.ChainedLog, error) { - ret, err := fetch[*Logs](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - OrderExpr("id desc"). - Limit(1). - Where("idempotency_key = ?", key). - Where("ledger = ?", store.name) - }) - if err != nil { - return nil, err - } - - return ret.ToCore(), nil -} - -type GetLogsQuery bunpaginate.ColumnPaginatedQuery[PaginatedQueryOptions[any]] - -func (q GetLogsQuery) WithOrder(order bunpaginate.Order) GetLogsQuery { - q.Order = order - return q -} - -func NewGetLogsQuery(options PaginatedQueryOptions[any]) GetLogsQuery { - return GetLogsQuery{ - PageSize: options.PageSize, - Column: "id", - Order: bunpaginate.OrderDesc, - Options: options, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/logs_test.go b/components/ledger/internal/storage/ledgerstore/logs_test.go deleted file mode 100644 index 333c2858a7..0000000000 --- a/components/ledger/internal/storage/ledgerstore/logs_test.go +++ /dev/null @@ -1,379 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - "fmt" - "math/big" - "testing" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/stretchr/testify/require" -) - -func TestGetLastLog(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - lastLog, err := store.GetLastLog(context.Background()) - require.True(t, sqlutils.IsNotFoundError(err)) - require.Nil(t, lastLog) - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx1", - Timestamp: now.Add(-3 * time.Hour), - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - }, - } - - logTx := ledger.NewTransactionLog(&tx1.Transaction, map[string]metadata.Metadata{}).ChainLog(nil) - appendLog(t, store, logTx) - - lastLog, err = store.GetLastLog(context.Background()) - require.NoError(t, err) - require.NotNil(t, lastLog) - - require.Equal(t, tx1.Postings, lastLog.Data.(ledger.NewTransactionLogPayload).Transaction.Postings) - require.Equal(t, tx1.Reference, lastLog.Data.(ledger.NewTransactionLogPayload).Transaction.Reference) - require.Equal(t, tx1.Timestamp, lastLog.Data.(ledger.NewTransactionLogPayload).Transaction.Timestamp) -} - -func TestReadLogWithIdempotencyKey(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - - logTx := ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), - ), - map[string]metadata.Metadata{}, - ) - log := logTx.WithIdempotencyKey("test") - - ret := appendLog(t, store, log.ChainLog(nil)) - - lastLog, err := store.ReadLogWithIdempotencyKey(context.Background(), "test") - require.NoError(t, err) - require.NotNil(t, lastLog) - require.Equal(t, *ret, *lastLog) -} - -func TestGetLogs(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx1", - Timestamp: now.Add(-3 * time.Hour), - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - }, - } - tx2 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx2", - Timestamp: now.Add(-2 * time.Hour), - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(200), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(200), - Output: big.NewInt(0), - }, - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, - } - tx3 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(2), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "central_bank", - Destination: "users:1", - Amount: big.NewInt(1), - Asset: "USD", - }, - }, - Reference: "tx3", - Metadata: metadata.Metadata{ - "priority": "high", - }, - Timestamp: now.Add(-1 * time.Hour), - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "central_bank": { - "USD": { - Input: big.NewInt(200), - Output: big.NewInt(0), - }, - }, - "users:1": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "central_bank": { - "USD": { - Input: big.NewInt(200), - Output: big.NewInt(1), - }, - }, - "users:1": { - "USD": { - Input: big.NewInt(1), - Output: big.NewInt(0), - }, - }, - }, - } - - var previousLog *ledger.ChainedLog - for _, tx := range []ledger.ExpandedTransaction{tx1, tx2, tx3} { - newLog := ledger.NewTransactionLog(&tx.Transaction, map[string]metadata.Metadata{}). - WithDate(tx.Timestamp). - ChainLog(previousLog) - appendLog(t, store, newLog) - previousLog = newLog - } - - cursor, err := store.GetLogs(context.Background(), NewGetLogsQuery(NewPaginatedQueryOptions[any](nil))) - require.NoError(t, err) - require.Equal(t, bunpaginate.QueryDefaultPageSize, cursor.PageSize) - - require.Equal(t, 3, len(cursor.Data)) - require.Equal(t, big.NewInt(2), cursor.Data[0].ID) - require.Equal(t, tx3.Postings, cursor.Data[0].Data.(ledger.NewTransactionLogPayload).Transaction.Postings) - require.Equal(t, tx3.Reference, cursor.Data[0].Data.(ledger.NewTransactionLogPayload).Transaction.Reference) - require.Equal(t, tx3.Timestamp, cursor.Data[0].Data.(ledger.NewTransactionLogPayload).Transaction.Timestamp) - - cursor, err = store.GetLogs(context.Background(), NewGetLogsQuery(NewPaginatedQueryOptions[any](nil).WithPageSize(1))) - require.NoError(t, err) - // Should get only the first log. - require.Equal(t, 1, cursor.PageSize) - require.Equal(t, big.NewInt(2), cursor.Data[0].ID) - - cursor, err = store.GetLogs(context.Background(), NewGetLogsQuery(NewPaginatedQueryOptions[any](nil). - WithQueryBuilder(query.And( - query.Gte("date", now.Add(-2*time.Hour)), - query.Lt("date", now.Add(-time.Hour)), - )). - WithPageSize(10), - )) - require.NoError(t, err) - require.Equal(t, 10, cursor.PageSize) - // Should get only the second log, as StartTime is inclusive and EndTime exclusive. - require.Len(t, cursor.Data, 1) - require.Equal(t, big.NewInt(1), cursor.Data[0].ID) -} - -func TestGetBalance(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - - const ( - batchNumber = 100 - batchSize = 10 - input = 100 - output = 10 - ) - - logs := make([]*ledger.ChainedLog, 0) - var previousLog *ledger.ChainedLog - for i := 0; i < batchNumber; i++ { - for j := 0; j < batchSize; j++ { - chainedLog := ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", fmt.Sprintf("account:%d", j), "EUR/2", big.NewInt(input)), - ledger.NewPosting(fmt.Sprintf("account:%d", j), "starbucks", "EUR/2", big.NewInt(output)), - ).WithIDUint64(uint64(i*batchSize+j)), - map[string]metadata.Metadata{}, - ).ChainLog(previousLog) - logs = append(logs, chainedLog) - previousLog = chainedLog - } - } - err := store.InsertLogs(context.Background(), logs...) - require.NoError(t, err) - - balance, err := store.GetBalance(context.Background(), "account:1", "EUR/2") - require.NoError(t, err) - require.Equal(t, big.NewInt((input-output)*batchNumber), balance) -} - -func BenchmarkLogsInsertion(b *testing.B) { - - ctx := logging.TestingContext() - store := newLedgerStore(b) - - b.ResetTimer() - - var lastLog *ledger.ChainedLog - for i := 0; i < b.N; i++ { - log := ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings(ledger.NewPosting( - "world", fmt.Sprintf("user:%d", i), "USD/2", big.NewInt(1000), - )).WithID(big.NewInt(int64(i))), - map[string]metadata.Metadata{}, - ).ChainLog(lastLog) - lastLog = log - require.NoError(b, store.InsertLogs(ctx, log)) - } - b.StopTimer() -} - -func BenchmarkLogsInsertionReusingAccount(b *testing.B) { - - ctx := logging.TestingContext() - store := newLedgerStore(b) - - b.ResetTimer() - - var lastLog *ledger.ChainedLog - for i := 0; i < b.N; i += 2 { - batch := make([]*ledger.ChainedLog, 0) - appendLog := func(log *ledger.Log) *ledger.ChainedLog { - chainedLog := log.ChainLog(lastLog) - batch = append(batch, chainedLog) - lastLog = chainedLog - return chainedLog - } - require.NoError(b, store.InsertLogs(ctx, appendLog(ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings(ledger.NewPosting( - "world", fmt.Sprintf("user:%d", i), "USD/2", big.NewInt(1000), - )).WithID(big.NewInt(int64(i))), - map[string]metadata.Metadata{}, - )))) - require.NoError(b, store.InsertLogs(ctx, appendLog(ledger.NewTransactionLog( - ledger.NewTransaction().WithPostings(ledger.NewPosting( - fmt.Sprintf("user:%d", i), "another:account", "USD/2", big.NewInt(1000), - )).WithID(big.NewInt(int64(i+1))), - map[string]metadata.Metadata{}, - )))) - } - b.StopTimer() -} diff --git a/components/ledger/internal/storage/ledgerstore/main_test.go b/components/ledger/internal/storage/ledgerstore/main_test.go deleted file mode 100644 index 36b06c8f91..0000000000 --- a/components/ledger/internal/storage/ledgerstore/main_test.go +++ /dev/null @@ -1,106 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - "database/sql" - "fmt" - "os" - "testing" - "time" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/uptrace/bun/dialect/pgdialect" - - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - ledger "github.com/formancehq/ledger/internal" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - srv *pgtesting.PostgresServer - bunDB *bun.DB -) - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - db, err := sql.Open("postgres", srv.GetDSN()) - if err != nil { - logging.Error(err) - os.Exit(1) - } - - bunDB = bun.NewDB(db, pgdialect.New()) - - return m.Run() - }) -} - -type T interface { - require.TestingT - Helper() - Cleanup(func()) -} - -func newBucket(t T, hooks ...bun.QueryHook) *Bucket { - name := uuid.NewString() - ctx := logging.TestingContext() - - pgDatabase := srv.NewDatabase(t) - - connectionOptions := bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDatabase.ConnString(), - MaxIdleConns: 40, - MaxOpenConns: 40, - ConnMaxIdleTime: time.Minute, - } - - bucket, err := ConnectToBucket(ctx, connectionOptions, name, hooks...) - require.NoError(t, err) - t.Cleanup(func() { - _ = bucket.Close() - }) - - require.NoError(t, bucket.Migrate(ctx)) - - return bucket -} - -func newLedgerStore(t T, hooks ...bun.QueryHook) *Store { - t.Helper() - - ledgerName := uuid.NewString() - ctx := logging.TestingContext() - - _, err := bunDB.ExecContext(ctx, fmt.Sprintf(`create schema if not exists "%s"`, ledgerName)) - require.NoError(t, err) - - t.Cleanup(func() { - _, err = bunDB.ExecContext(ctx, fmt.Sprintf(`drop schema "%s" cascade`, ledgerName)) - require.NoError(t, err) - }) - - bucket := newBucket(t, hooks...) - - store, err := bucket.CreateLedgerStore(ledgerName) - require.NoError(t, err) - - return store -} - -func appendLog(t *testing.T, store *Store, log *ledger.ChainedLog) *ledger.ChainedLog { - err := store.InsertLogs(context.Background(), log) - require.NoError(t, err) - return log -} diff --git a/components/ledger/internal/storage/ledgerstore/migrations/0-init-schema.sql b/components/ledger/internal/storage/ledgerstore/migrations/0-init-schema.sql deleted file mode 100644 index 68666b0522..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/0-init-schema.sql +++ /dev/null @@ -1,793 +0,0 @@ -create aggregate aggregate_objects(jsonb) ( - sfunc = jsonb_concat, - stype = jsonb, - initcond = '{}' - ); - -create function first_agg(anyelement, anyelement) - returns anyelement - language sql - immutable - strict - parallel safe -as -$$ -select $1 -$$; - -create aggregate first (anyelement) ( - sfunc = first_agg, - stype = anyelement, - parallel = safe - ); - -create function array_distinct(anyarray) - returns anyarray - language sql - immutable -as -$$ -select array_agg(distinct x) -from unnest($1) t(x); -$$; - -/** Define types **/ -create type account_with_volumes as -( - address varchar, - metadata jsonb, - volumes jsonb -); - -create type volumes as -( - inputs numeric, - outputs numeric -); - -create type volumes_with_asset as -( - asset varchar, - volumes volumes -); - -/** Define tables **/ -create table transactions -( - seq bigserial primary key, - ledger varchar not null, - id numeric not null, - timestamp timestamp without time zone not null, - reference varchar, - reverted_at timestamp without time zone, - updated_at timestamp without time zone, - postings varchar not null, - sources jsonb, - destinations jsonb, - sources_arrays jsonb, - destinations_arrays jsonb, - metadata jsonb not null default '{}'::jsonb -); - -create unique index transactions_ledger on transactions (ledger, id); -create index transactions_date on transactions (timestamp); -create index transactions_metadata_index on transactions using gin (metadata jsonb_path_ops); -create index transactions_sources on transactions using gin (sources jsonb_path_ops); -create index transactions_destinations on transactions using gin (destinations jsonb_path_ops); -create index transactions_sources_arrays on transactions using gin (sources_arrays jsonb_path_ops); -create index transactions_destinations_arrays on transactions using gin (destinations_arrays jsonb_path_ops); - -create table transactions_metadata -( - seq bigserial, - ledger varchar not null, - transactions_seq bigint references transactions (seq), - revision numeric default 0 not null, - date timestamp not null, - metadata jsonb not null default '{}'::jsonb, - - primary key (seq) -); - -create index transactions_metadata_metadata on transactions_metadata using gin (metadata jsonb_path_ops); -create unique index transactions_metadata_ledger on transactions_metadata (ledger, transactions_seq, revision); -create index transactions_metadata_revisions on transactions_metadata(transactions_seq asc, revision desc) include (metadata, date); - -create table accounts -( - seq bigserial primary key, - ledger varchar not null, - address varchar not null, - address_array jsonb not null, - insertion_date timestamp not null, - updated_at timestamp not null, - metadata jsonb not null default '{}'::jsonb -); - -create unique index accounts_ledger on accounts (ledger, address) include (seq); -create index accounts_address_array on accounts using gin (address_array jsonb_ops); -create index accounts_address_array_length on accounts (jsonb_array_length(address_array)); - -create table accounts_metadata -( - seq bigserial primary key, - ledger varchar not null, - accounts_seq bigint references accounts (seq), - metadata jsonb not null default '{}'::jsonb, - revision numeric default 0, - date timestamp -); - -create unique index accounts_metadata_ledger on accounts_metadata (ledger, accounts_seq, revision); -create index accounts_metadata_metadata on accounts_metadata using gin (metadata jsonb_path_ops); -create index accounts_metadata_revisions on accounts_metadata(accounts_seq asc, revision desc) include (metadata, date); - -create table moves -( - seq bigserial not null primary key, - ledger varchar not null, - transactions_seq bigint not null references transactions (seq), - accounts_seq bigint not null references accounts (seq), - account_address varchar not null, - account_address_array jsonb not null, - asset varchar not null, - amount numeric not null, - insertion_date timestamp not null, - effective_date timestamp not null, - post_commit_volumes volumes not null, - post_commit_effective_volumes volumes default null, - is_source boolean not null -); - -create index moves_ledger on moves (ledger); -create index moves_range_dates on moves (account_address, asset, effective_date); -create index moves_account_address on moves (account_address); -create index moves_account_address_array on moves using gin (account_address_array jsonb_ops); -create index moves_account_address_array_length on moves (jsonb_array_length(account_address_array)); -create index moves_date on moves (effective_date); -create index moves_asset on moves (asset); -create index moves_post_commit_volumes on moves (accounts_seq, asset, seq); -create index moves_effective_post_commit_volumes on moves (accounts_seq, asset, effective_date desc); - -create type log_type as enum - ('NEW_TRANSACTION', - 'REVERTED_TRANSACTION', - 'SET_METADATA', - 'DELETE_METADATA' - ); - -create table logs -( - seq bigserial primary key, - ledger varchar not null, - id numeric not null, - type log_type not null, - hash bytea not null, - date timestamp not null, - data jsonb not null, - idempotency_key varchar(255) -); - -create unique index logs_ledger on logs (ledger, id); - -/** Define index **/ - -create function balance_from_volumes(v volumes) - returns numeric - language sql - immutable -as -$$ -select v.inputs - v.outputs -$$; - -/** Define write functions **/ - --- given the input : "a:b:c", the function will produce : '{"0": "a", "1": "b", "2": "c", "3": null}' -create function explode_address(_address varchar) - returns jsonb - language sql - immutable -as -$$ -select aggregate_objects(jsonb_build_object(data.number - 1, data.value)) -from (select row_number() over () as number, v.value - from (select unnest(string_to_array(_address, ':')) as value - union all - select null) v) data -$$; - -create function get_transaction(_ledger varchar, _id numeric, _before timestamp default null) - returns setof transactions - language sql - stable -as -$$ -select * -from transactions t -where (_before is null or t.timestamp <= _before) - and t.id = _id - and ledger = _ledger -order by id desc -limit 1; -$$; - --- a simple 'select distinct asset from moves' would be more simple --- but Postgres is extremely inefficient with distinct --- so the query implementation use a "hack" to emulate skip scan feature which Postgres lack natively --- see https://wiki.postgresql.org/wiki/Loose_indexscan for more information -create function get_all_assets(_ledger varchar) - returns setof varchar - language sql -as -$$ -with recursive t as (select min(asset) as asset - from moves - where ledger = _ledger - union all - select (select min(asset) - from moves - where asset > t.asset - and ledger = _ledger) - from t - where t.asset is not null) -select asset -from t -where asset is not null -union all -select null -where exists(select 1 from moves where asset is null and ledger = _ledger) -$$; - -create function get_latest_move_for_account_and_asset(_ledger varchar, _account_address varchar, _asset varchar, - _before timestamp default null) - returns setof moves - language sql - stable -as -$$ -select * -from moves s -where (_before is null or s.effective_date <= _before) - and s.account_address = _account_address - and s.asset = _asset - and ledger = _ledger -order by effective_date desc, seq desc -limit 1; -$$; - -create function upsert_account(_ledger varchar, _address varchar, _metadata jsonb, _date timestamp) - returns void - language plpgsql -as -$$ -begin - insert into accounts(ledger, address, address_array, insertion_date, metadata, updated_at) - values (_ledger, _address, to_json(string_to_array(_address, ':')), _date, coalesce(_metadata, '{}'::jsonb), _date) - on conflict (ledger, address) do update - set metadata = accounts.metadata || coalesce(_metadata, '{}'::jsonb), - updated_at = _date - where not accounts.metadata @> coalesce(_metadata, '{}'::jsonb); -end; -$$; - -create function delete_account_metadata(_ledger varchar, _address varchar, _key varchar, _date timestamp) - returns void - language plpgsql -as -$$ -begin - update accounts - set metadata = metadata - _key, - updated_at = _date - where address = _address - and ledger = _ledger; -end -$$; - -create function update_transaction_metadata(_ledger varchar, _id numeric, _metadata jsonb, _date timestamp) - returns void - language plpgsql -as -$$ -begin - update transactions - set metadata = metadata || _metadata, - updated_at = _date - where id = _id - and ledger = _ledger; -- todo: add fill factor on transactions table ? -end; -$$; - -create function delete_transaction_metadata(_ledger varchar, _id numeric, _key varchar, _date timestamp) - returns void - language plpgsql -as -$$ -begin - update transactions - set metadata = metadata - _key, - updated_at = _date - where id = _id - and ledger = _ledger; -end; -$$; - -create function revert_transaction(_ledger varchar, _id numeric, _date timestamp) - returns void - language sql -as -$$ -update transactions -set reverted_at = _date -where id = _id - and ledger = _ledger; -$$; - - -create or replace function insert_move( - _transactions_seq bigint, - _ledger varchar, - _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, - _account_address varchar, - _asset varchar, - _amount numeric, - _is_source bool, - _account_exists bool) - returns void - language plpgsql -as -$$ -declare - _post_commit_volumes volumes = (0, 0)::volumes; - _effective_post_commit_volumes volumes = (0, 0)::volumes; - _seq bigint; - _account_seq bigint; -begin - - -- todo: lock if we enable parallelism - -- perform * - -- from accounts - -- where address = _account_address - -- for update; - - select seq from accounts where ledger = _ledger and address = _account_address into _account_seq; - - if _account_exists then - select (post_commit_volumes).inputs, (post_commit_volumes).outputs - into _post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - order by seq desc - limit 1; - - if not found then - _post_commit_volumes = (0, 0)::volumes; - _effective_post_commit_volumes = (0, 0)::volumes; - else - select (post_commit_effective_volumes).inputs, (post_commit_effective_volumes).outputs into _effective_post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - and effective_date <= _effective_date - order by effective_date desc, seq desc - limit 1; - end if; - end if; - - if _is_source then - _post_commit_volumes.outputs = _post_commit_volumes.outputs + _amount; - _effective_post_commit_volumes.outputs = _effective_post_commit_volumes.outputs + _amount; - else - _post_commit_volumes.inputs = _post_commit_volumes.inputs + _amount; - _effective_post_commit_volumes.inputs = _effective_post_commit_volumes.inputs + _amount; - end if; - - insert into moves (ledger, - insertion_date, - effective_date, - accounts_seq, - account_address, - asset, - transactions_seq, - amount, - is_source, - account_address_array, - post_commit_volumes, - post_commit_effective_volumes) - values (_ledger, - _insertion_date, - _effective_date, - _account_seq, - _account_address, - _asset, - _transactions_seq, - _amount, - _is_source, - (select to_json(string_to_array(_account_address, ':'))), - _post_commit_volumes, - _effective_post_commit_volumes) - returning seq into _seq; - - if _account_exists then - update moves - set post_commit_effective_volumes = - ((post_commit_effective_volumes).inputs + case when _is_source then 0 else _amount end, - (post_commit_effective_volumes).outputs + case when _is_source then _amount else 0 end - ) - where accounts_seq = _account_seq - and asset = _asset - and effective_date > _effective_date; - - update moves - set post_commit_effective_volumes = - ((post_commit_effective_volumes).inputs + case when _is_source then 0 else _amount end, - (post_commit_effective_volumes).outputs + case when _is_source then _amount else 0 end - ) - where accounts_seq = _account_seq - and asset = _asset - and effective_date = _effective_date - and seq > _seq; - end if; -end; -$$; - -create function insert_posting(_transaction_seq bigint, _ledger varchar, _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, posting jsonb, _account_metadata jsonb) - returns void - language plpgsql -as -$$ -declare - _source_exists bool; - _destination_exists bool; -begin - - select true from accounts where ledger = _ledger and address = posting ->> 'source' into _source_exists; - select true from accounts where ledger = _ledger and address = posting ->> 'destination' into _destination_exists; - - perform upsert_account(_ledger, posting ->> 'source', _account_metadata -> (posting ->> 'source'), _insertion_date); - perform upsert_account(_ledger, posting ->> 'destination', _account_metadata -> (posting ->> 'destination'), - _insertion_date); - - -- todo: sometimes the balance is known at commit time (for sources != world), we need to forward the value to populate the pre_commit_aggregated_input and output - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'source', posting ->> 'asset', (posting ->> 'amount')::numeric, true, - _source_exists); - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'destination', posting ->> 'asset', (posting ->> 'amount')::numeric, false, - _destination_exists); -end; -$$; - --- todo: maybe we could avoid plpgsql functions -create function insert_transaction(_ledger varchar, data jsonb, _date timestamp without time zone, - _account_metadata jsonb) - returns void - language plpgsql -as -$$ -declare - posting jsonb; - _seq bigint; -begin - insert into transactions (ledger, id, timestamp, updated_at, reference, postings, sources, - destinations, sources_arrays, destinations_arrays, metadata) - values (_ledger, - (data ->> 'id')::numeric, - (data ->> 'timestamp')::timestamp without time zone, - (data ->> 'timestamp')::timestamp without time zone, - data ->> 'reference', - jsonb_pretty(data -> 'postings'), - (select to_jsonb(array_agg(v ->> 'source')) as value - from jsonb_array_elements(data -> 'postings') v), - (select to_jsonb(array_agg(v ->> 'destination')) as value - from jsonb_array_elements(data -> 'postings') v), - (select to_jsonb(array_agg(explode_address(v ->> 'source'))) as value - from jsonb_array_elements(data -> 'postings') v), - (select to_jsonb(array_agg(explode_address(v ->> 'destination'))) as value - from jsonb_array_elements(data -> 'postings') v), - coalesce(data -> 'metadata', '{}'::jsonb)) - returning seq into _seq; - - for posting in (select jsonb_array_elements(data -> 'postings')) - loop - -- todo: sometimes the balance is known at commit time (for sources != world), we need to forward the value to populate the pre_commit_aggregated_input and output - perform insert_posting(_seq, _ledger, _date, (data ->> 'timestamp')::timestamp without time zone, posting, - _account_metadata); - end loop; - - if data -> 'metadata' is not null and data ->> 'metadata' <> '()' then - insert into transactions_metadata (ledger, transactions_seq, revision, date, metadata) - values (_ledger, - _seq, - 0, - (data ->> 'timestamp')::timestamp without time zone, - coalesce(data -> 'metadata', '{}'::jsonb)); - end if; -end -$$; - -create function handle_log() returns trigger - security definer - language plpgsql -as -$$ -declare - _key varchar; - _value jsonb; -begin - if new.type = 'NEW_TRANSACTION' then - perform insert_transaction(new.ledger, new.data -> 'transaction', new.date, new.data -> 'accountMetadata'); - for _key, _value in (select * from jsonb_each_text(new.data -> 'accountMetadata')) - loop - perform upsert_account(new.ledger, _key, _value, - (new.data -> 'transaction' ->> 'timestamp')::timestamp); - end loop; - end if; - if new.type = 'REVERTED_TRANSACTION' then - perform insert_transaction(new.ledger, new.data -> 'transaction', new.date, '{}'::jsonb); - perform revert_transaction(new.ledger, (new.data ->> 'revertedTransactionID')::numeric, - (new.data -> 'transaction' ->> 'timestamp')::timestamp); - end if; - if new.type = 'SET_METADATA' then - if new.data ->> 'targetType' = 'TRANSACTION' then - perform update_transaction_metadata(new.ledger, (new.data ->> 'targetId')::numeric, new.data -> 'metadata', - new.date); - else - perform upsert_account(new.ledger, (new.data ->> 'targetId')::varchar, new.data -> 'metadata', new.date); - end if; - end if; - if new.type = 'DELETE_METADATA' then - if new.data ->> 'targetType' = 'TRANSACTION' then - perform delete_transaction_metadata(new.ledger, (new.data ->> 'targetId')::numeric, new.data ->> 'key', - new.date); - else - perform delete_account_metadata(new.ledger, (new.data ->> 'targetId')::varchar, new.data ->> 'key', - new.date); - end if; - end if; - - return new; -end; -$$; - -create function update_account_metadata_history() returns trigger - security definer - language plpgsql -as -$$ -begin - insert into accounts_metadata (ledger, accounts_seq, revision, date, metadata) - values (new.ledger, new.seq, (select revision + 1 - from accounts_metadata - where accounts_metadata.accounts_seq = new.seq - order by revision desc - limit 1), new.updated_at, new.metadata); - - return new; -end; -$$; - -create function insert_account_metadata_history() returns trigger - security definer - language plpgsql -as -$$ -begin - insert into accounts_metadata (ledger, accounts_seq, revision, date, metadata) - values (new.ledger, new.seq, 1, new.insertion_date, new.metadata); - - return new; -end; -$$; - -create function update_transaction_metadata_history() returns trigger - security definer - language plpgsql -as -$$ -begin - insert into transactions_metadata (ledger, transactions_seq, revision, date, metadata) - values (new.ledger, new.seq, (select revision + 1 - from transactions_metadata - where transactions_metadata.transactions_seq = new.seq - order by revision desc - limit 1), new.updated_at, new.metadata); - - return new; -end; -$$; - -create function insert_transaction_metadata_history() returns trigger - security definer - language plpgsql -as -$$ -begin - insert into transactions_metadata (ledger, transactions_seq, revision, date, metadata) - values (new.ledger, new.seq, 1, new.timestamp, new.metadata); - - return new; -end; -$$; - -create or replace function get_all_account_effective_volumes(_ledger varchar, _account varchar, _before timestamp default null) - returns setof volumes_with_asset - language sql - stable -as -$$ -with all_assets as (select v.v as asset - from get_all_assets(_ledger) v), - moves as (select m.* - from all_assets assets - join lateral ( - select * - from moves s - where (_before is null or s.effective_date <= _before) - and s.account_address = _account - and s.asset = assets.asset - and s.ledger = _ledger - order by effective_date desc, seq desc - limit 1 - ) m on true) -select moves.asset, moves.post_commit_effective_volumes -from moves -$$; - -create or replace function get_all_account_volumes(_ledger varchar, _account varchar, _before timestamp default null) - returns setof volumes_with_asset - language sql - stable -as -$$ -with all_assets as (select v.v as asset - from get_all_assets(_ledger) v), - moves as (select m.* - from all_assets assets - join lateral ( - select * - from moves s - where (_before is null or s.insertion_date <= _before) - and s.account_address = _account - and s.asset = assets.asset - and s.ledger = _ledger - order by seq desc - limit 1 - ) m on true) -select moves.asset, moves.post_commit_volumes -from moves -$$; - -create function volumes_to_jsonb(v volumes_with_asset) - returns jsonb - language sql - immutable -as -$$ -select ('{"' || v.asset || '": {"input": ' || (v.volumes).inputs || ', "output": ' || (v.volumes).outputs || '}}')::jsonb -$$; - -create function get_account_aggregated_effective_volumes(_ledger varchar, _account_address varchar, - _before timestamp default null) - returns jsonb - language sql - stable -as -$$ -select aggregate_objects(volumes_to_jsonb(volumes_with_asset)) -from get_all_account_effective_volumes(_ledger, _account_address, _before := _before) volumes_with_asset -$$; - -create function get_account_aggregated_volumes(_ledger varchar, _account_address varchar, - _before timestamp default null) - returns jsonb - language sql - stable - parallel safe -as -$$ -select aggregate_objects(volumes_to_jsonb(volumes_with_asset)) -from get_all_account_volumes(_ledger, _account_address, _before := _before) volumes_with_asset -$$; - -create function get_account_balance(_ledger varchar, _account varchar, _asset varchar, _before timestamp default null) - returns numeric - language sql - stable -as -$$ -select (post_commit_volumes).inputs - (post_commit_volumes).outputs -from moves s -where (_before is null or s.effective_date <= _before) - and s.account_address = _account - and s.asset = _asset - and s.ledger = _ledger -order by seq desc -limit 1 -$$; - -create function aggregate_ledger_volumes( - _ledger varchar, - _before timestamp default null, - _accounts varchar[] default null, - _assets varchar[] default null -) - returns setof volumes_with_asset - language sql - stable -as -$$ -with moves as (select distinct on (m.account_address, m.asset) m.* - from moves m - where (_before is null or m.effective_date <= _before) - and (_accounts is null or account_address = any (_accounts)) - and (_assets is null or asset = any (_assets)) - and m.ledger = _ledger - order by account_address, asset, m.seq desc) -select v.asset, - (sum((v.post_commit_effective_volumes).inputs), sum((v.post_commit_effective_volumes).outputs)) -from moves v -group by v.asset -$$; - -create function get_aggregated_effective_volumes_for_transaction(_ledger varchar, tx numeric) returns jsonb - stable - language sql -as -$$ -select aggregate_objects(jsonb_build_object(data.account_address, data.aggregated)) -from (select distinct on (move.account_address, move.asset) move.account_address, - volumes_to_jsonb((move.asset, first(move.post_commit_effective_volumes))) as aggregated - from moves move - where move.transactions_seq = tx - and ledger = _ledger - group by move.account_address, move.asset) data -$$; - -create function get_aggregated_volumes_for_transaction(_ledger varchar, tx numeric) returns jsonb - stable - language sql -as -$$ -select aggregate_objects(jsonb_build_object(data.account_address, data.aggregated)) -from (select distinct on (move.account_address, move.asset) move.account_address, - volumes_to_jsonb((move.asset, first(move.post_commit_volumes))) as aggregated - from moves move - where move.transactions_seq = tx - and ledger = _ledger - group by move.account_address, move.asset) data -$$; - -create trigger "insert_log" - after insert - on "logs" - for each row -execute procedure handle_log(); - -create trigger "update_account" - after update - on "accounts" - for each row -execute procedure update_account_metadata_history(); - -create trigger "insert_account" - after insert - on "accounts" - for each row -execute procedure insert_account_metadata_history(); - -create trigger "update_transaction" - after update - on "transactions" - for each row -execute procedure update_transaction_metadata_history(); - -create trigger "insert_transaction" - after insert - on "transactions" - for each row -execute procedure insert_transaction_metadata_history(); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/1-fix-trigger.sql b/components/ledger/internal/storage/ledgerstore/migrations/1-fix-trigger.sql deleted file mode 100644 index 2ee0f37675..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/1-fix-trigger.sql +++ /dev/null @@ -1,31 +0,0 @@ -create or replace function insert_posting(_transaction_seq bigint, _ledger varchar, _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, posting jsonb, _account_metadata jsonb) - returns void - language plpgsql -as -$$ -declare - _source_exists bool; - _destination_exists bool; -begin - - select true from accounts where ledger = _ledger and address = posting ->> 'source' into _source_exists; - if posting ->>'source' = posting->>'destination' then - _destination_exists = true; - else - select true from accounts where ledger = _ledger and address = posting ->> 'destination' into _destination_exists; - end if; - - perform upsert_account(_ledger, posting ->> 'source', _account_metadata -> (posting ->> 'source'), _insertion_date); - perform upsert_account(_ledger, posting ->> 'destination', _account_metadata -> (posting ->> 'destination'), - _insertion_date); - - -- todo: sometimes the balance is known at commit time (for sources != world), we need to forward the value to populate the pre_commit_aggregated_input and output - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'source', posting ->> 'asset', (posting ->> 'amount')::numeric, true, - _source_exists); - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'destination', posting ->> 'asset', (posting ->> 'amount')::numeric, false, - _destination_exists); -end; -$$; \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/10-fillfactor-on-moves.sql b/components/ledger/internal/storage/ledgerstore/migrations/10-fillfactor-on-moves.sql deleted file mode 100644 index 689434e0fb..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/10-fillfactor-on-moves.sql +++ /dev/null @@ -1 +0,0 @@ -alter table moves set (fillfactor = 80); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/2-fix-volumes-aggregation.sql b/components/ledger/internal/storage/ledgerstore/migrations/2-fix-volumes-aggregation.sql deleted file mode 100644 index f137545194..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/2-fix-volumes-aggregation.sql +++ /dev/null @@ -1,23 +0,0 @@ -create or replace function get_all_account_volumes(_ledger varchar, _account varchar, _before timestamp default null) - returns setof volumes_with_asset - language sql - stable -as -$$ -with all_assets as (select v.v as asset - from get_all_assets(_ledger) v), - moves as (select m.* - from all_assets assets - join lateral ( - select * - from moves s - where (_before is null or s.effective_date <= _before) - and s.account_address = _account - and s.asset = assets.asset - and s.ledger = _ledger - order by seq desc - limit 1 - ) m on true) -select moves.asset, moves.post_commit_volumes -from moves -$$; \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/3-fix-trigger-inserting-backdated-transactions.sql b/components/ledger/internal/storage/ledgerstore/migrations/3-fix-trigger-inserting-backdated-transactions.sql deleted file mode 100644 index cbc196fabc..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/3-fix-trigger-inserting-backdated-transactions.sql +++ /dev/null @@ -1,105 +0,0 @@ -create or replace function insert_move( - _transactions_seq bigint, - _ledger varchar, - _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, - _account_address varchar, - _asset varchar, - _amount numeric, - _is_source bool, - _account_exists bool) - returns void - language plpgsql -as -$$ -declare - _post_commit_volumes volumes = (0, 0)::volumes; - _effective_post_commit_volumes volumes = (0, 0)::volumes; - _seq bigint; - _account_seq bigint; -begin - select seq from accounts where ledger = _ledger and address = _account_address into _account_seq; - - if _account_exists then - select (post_commit_volumes).inputs, (post_commit_volumes).outputs - into _post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - order by seq desc - limit 1; - - if not found then - _post_commit_volumes = (0, 0)::volumes; - _effective_post_commit_volumes = (0, 0)::volumes; - else - select (post_commit_effective_volumes).inputs, (post_commit_effective_volumes).outputs into _effective_post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - and effective_date <= _effective_date - order by effective_date desc, seq desc - limit 1; - - if not found then - _effective_post_commit_volumes = (0, 0)::volumes; - end if; - end if; - end if; - - if _is_source then - _post_commit_volumes.outputs = _post_commit_volumes.outputs + _amount; - _effective_post_commit_volumes.outputs = _effective_post_commit_volumes.outputs + _amount; - else - _post_commit_volumes.inputs = _post_commit_volumes.inputs + _amount; - _effective_post_commit_volumes.inputs = _effective_post_commit_volumes.inputs + _amount; - end if; - - insert into moves (ledger, - insertion_date, - effective_date, - accounts_seq, - account_address, - asset, - transactions_seq, - amount, - is_source, - account_address_array, - post_commit_volumes, - post_commit_effective_volumes) - values (_ledger, - _insertion_date, - _effective_date, - _account_seq, - _account_address, - _asset, - _transactions_seq, - _amount, - _is_source, - (select to_json(string_to_array(_account_address, ':'))), - _post_commit_volumes, - _effective_post_commit_volumes) - returning seq into _seq; - - if _account_exists then - update moves - set post_commit_effective_volumes = - ((post_commit_effective_volumes).inputs + case when _is_source then 0 else _amount end, - (post_commit_effective_volumes).outputs + case when _is_source then _amount else 0 end - ) - where accounts_seq = _account_seq - and asset = _asset - and effective_date > _effective_date; - - update moves - set post_commit_effective_volumes = - ((post_commit_effective_volumes).inputs + case when _is_source then 0 else _amount end, - (post_commit_effective_volumes).outputs + case when _is_source then _amount else 0 end - ) - where accounts_seq = _account_seq - and asset = _asset - and effective_date = _effective_date - and seq < _seq; - end if; -end; -$$; \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/4-add-account-first-usage-column.sql b/components/ledger/internal/storage/ledgerstore/migrations/4-add-account-first-usage-column.sql deleted file mode 100644 index 873cdcc458..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/4-add-account-first-usage-column.sql +++ /dev/null @@ -1,226 +0,0 @@ -alter table accounts -add column first_usage timestamp without time zone; - -create or replace function insert_move( - _transactions_seq bigint, - _ledger varchar, - _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, - _account_address varchar, - _asset varchar, - _amount numeric, - _is_source bool, - _account_exists bool) - returns void - language plpgsql -as -$$ -declare - _post_commit_volumes volumes = (0, 0)::volumes; - _effective_post_commit_volumes volumes = (0, 0)::volumes; - _seq bigint; - _account_seq bigint; -begin - - select seq from accounts where ledger = _ledger and address = _account_address into _account_seq; - - if _account_exists then - select (post_commit_volumes).inputs, (post_commit_volumes).outputs - into _post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - order by seq desc - limit 1; - - if not found then - _post_commit_volumes = (0, 0)::volumes; - _effective_post_commit_volumes = (0, 0)::volumes; - else - select (post_commit_effective_volumes).inputs, (post_commit_effective_volumes).outputs into _effective_post_commit_volumes - from moves - where accounts_seq = _account_seq - and asset = _asset - and effective_date <= _effective_date - order by effective_date desc, seq desc - limit 1; - - if not found then - _effective_post_commit_volumes = (0, 0)::volumes; - end if; - end if; - end if; - - if _is_source then - _post_commit_volumes.outputs = _post_commit_volumes.outputs + _amount; - _effective_post_commit_volumes.outputs = _effective_post_commit_volumes.outputs + _amount; - else - _post_commit_volumes.inputs = _post_commit_volumes.inputs + _amount; - _effective_post_commit_volumes.inputs = _effective_post_commit_volumes.inputs + _amount; - end if; - - insert into moves (ledger, - insertion_date, - effective_date, - accounts_seq, - account_address, - asset, - transactions_seq, - amount, - is_source, - account_address_array, - post_commit_volumes, - post_commit_effective_volumes) - values (_ledger, - _insertion_date, - _effective_date, - _account_seq, - _account_address, - _asset, - _transactions_seq, - _amount, - _is_source, - (select to_json(string_to_array(_account_address, ':'))), - _post_commit_volumes, - _effective_post_commit_volumes) - returning seq into _seq; - - if _account_exists then - update moves - set post_commit_effective_volumes = - ((post_commit_effective_volumes).inputs + case when _is_source then 0 else _amount end, - (post_commit_effective_volumes).outputs + case when _is_source then _amount else 0 end - ) - where accounts_seq = _account_seq - and asset = _asset - and effective_date > _effective_date; - end if; -end; -$$; - -create or replace function upsert_account(_ledger varchar, _address varchar, _metadata jsonb, _date timestamp, _first_usage timestamp) - returns void - language plpgsql -as -$$ -begin - insert into accounts(ledger, address, address_array, insertion_date, metadata, updated_at, first_usage) - values (_ledger, _address, to_json(string_to_array(_address, ':')), _date, coalesce(_metadata, '{}'::jsonb), _date, _first_usage) - on conflict (ledger, address) do update - set metadata = accounts.metadata || coalesce(_metadata, '{}'::jsonb), - updated_at = _date, - first_usage = case when accounts.first_usage < _first_usage then accounts.first_usage else _first_usage end - where not accounts.metadata @> coalesce(_metadata, '{}'::jsonb) or accounts.first_usage > _first_usage; -end; -$$; - -create or replace function insert_posting(_transaction_seq bigint, _ledger varchar, _insertion_date timestamp without time zone, - _effective_date timestamp without time zone, posting jsonb, _account_metadata jsonb) - returns void - language plpgsql -as -$$ -declare - _source_exists bool; - _destination_exists bool; -begin - - select true from accounts where ledger = _ledger and address = posting ->> 'source' into _source_exists; - perform upsert_account(_ledger, posting ->> 'source', _account_metadata -> (posting ->> 'source'), _insertion_date, _effective_date); - - select true from accounts where ledger = _ledger and address = posting ->> 'destination' into _destination_exists; - perform upsert_account(_ledger, posting ->> 'destination', _account_metadata -> (posting ->> 'destination'), _insertion_date, _effective_date); - - -- todo: sometimes the balance is known at commit time (for sources != world), we need to forward the value to populate the pre_commit_aggregated_input and output - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'source', posting ->> 'asset', (posting ->> 'amount')::numeric, true, - _source_exists); - perform insert_move(_transaction_seq, _ledger, _insertion_date, _effective_date, - posting ->> 'destination', posting ->> 'asset', (posting ->> 'amount')::numeric, false, - _destination_exists); -end; -$$; - -create or replace function handle_log() returns trigger - security definer - language plpgsql -as -$$ -declare - _key varchar; - _value jsonb; -begin - if new.type = 'NEW_TRANSACTION' then - perform insert_transaction(new.ledger, new.data -> 'transaction', new.date, new.data -> 'accountMetadata'); - for _key, _value in (select * from jsonb_each_text(new.data -> 'accountMetadata')) - loop - perform upsert_account(new.ledger, _key, _value, - (new.data -> 'transaction' ->> 'timestamp')::timestamp, - (new.data -> 'transaction' ->> 'timestamp')::timestamp); - end loop; - end if; - if new.type = 'REVERTED_TRANSACTION' then - perform insert_transaction(new.ledger, new.data -> 'transaction', new.date, '{}'::jsonb); - perform revert_transaction(new.ledger, (new.data ->> 'revertedTransactionID')::numeric, - (new.data -> 'transaction' ->> 'timestamp')::timestamp); - end if; - if new.type = 'SET_METADATA' then - if new.data ->> 'targetType' = 'TRANSACTION' then - perform update_transaction_metadata(new.ledger, (new.data ->> 'targetId')::numeric, new.data -> 'metadata', - new.date); - else - perform upsert_account(new.ledger, (new.data ->> 'targetId')::varchar, new.data -> 'metadata', new.date, new.date); - end if; - end if; - if new.type = 'DELETE_METADATA' then - if new.data ->> 'targetType' = 'TRANSACTION' then - perform delete_transaction_metadata(new.ledger, (new.data ->> 'targetId')::numeric, new.data ->> 'key', - new.date); - else - perform delete_account_metadata(new.ledger, (new.data ->> 'targetId')::varchar, new.data ->> 'key', - new.date); - end if; - end if; - - return new; -end; -$$; - -create or replace function get_all_account_volumes(_ledger varchar, _account varchar, _before timestamp default null) - returns setof volumes_with_asset - language sql - stable -as -$$ -with all_assets as (select v.v as asset - from get_all_assets(_ledger) v), - moves as (select m.* - from all_assets assets - join lateral ( - select * - from moves s - where (_before is null or s.insertion_date <= _before) - and s.account_address = _account - and s.asset = assets.asset - and s.ledger = _ledger - order by seq desc - limit 1 - ) m on true) -select moves.asset, moves.post_commit_volumes -from moves -$$; - -drop function upsert_account(_ledger varchar, _address varchar, _metadata jsonb, _date timestamp); - -create index accounts_first_usage on accounts (first_usage); - -update accounts -set first_usage = ( - select min(effective_date) - from moves m - where m.accounts_seq = accounts.seq - union all - select accounts.insertion_date - limit 1 -) -where first_usage is null; \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/5-add-idempotency-key-index.sql b/components/ledger/internal/storage/ledgerstore/migrations/5-add-idempotency-key-index.sql deleted file mode 100644 index b44c5459de..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/5-add-idempotency-key-index.sql +++ /dev/null @@ -1 +0,0 @@ -create index logs_idempotency_key on logs (idempotency_key); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/6-add-reference-index.sql b/components/ledger/internal/storage/ledgerstore/migrations/6-add-reference-index.sql deleted file mode 100644 index 89b0ed6f81..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/6-add-reference-index.sql +++ /dev/null @@ -1 +0,0 @@ -create index transactions_reference on transactions (reference); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/7-add-ik-unique-index.sql b/components/ledger/internal/storage/ledgerstore/migrations/7-add-ik-unique-index.sql deleted file mode 100644 index 92ed590856..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/7-add-ik-unique-index.sql +++ /dev/null @@ -1,20 +0,0 @@ -update logs -set idempotency_key = null -where idempotency_key = ''; - -update logs -set idempotency_key = null -where id in ( - select unnest(duplicateLogIds.ids[2:]) as id - from ( - select array_agg(id order by id) as ids - from logs l - where idempotency_key is not null - group by idempotency_key - having count(*) > 1 - ) duplicateLogIds -); - -drop index logs_idempotency_key; - -create unique index logs_idempotency_key on logs (idempotency_key); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql b/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql deleted file mode 100644 index 1093bf9c01..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql +++ /dev/null @@ -1,3 +0,0 @@ -drop index logs_idempotency_key; - -create unique index logs_idempotency_key on logs (ledger, idempotency_key); \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations/9-fix-incorrect-volumes-aggregation.sql b/components/ledger/internal/storage/ledgerstore/migrations/9-fix-incorrect-volumes-aggregation.sql deleted file mode 100644 index 6b7af53cbd..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations/9-fix-incorrect-volumes-aggregation.sql +++ /dev/null @@ -1,33 +0,0 @@ -create or replace function get_aggregated_volumes_for_transaction(_ledger varchar, tx numeric) returns jsonb - stable - language sql -as -$$ -select aggregate_objects(jsonb_build_object(data.account_address, data.aggregated)) -from ( - select distinct on (move.account_address, move.asset) - move.account_address, - volumes_to_jsonb((move.asset, first(move.post_commit_volumes))) as aggregated - from (select * from moves order by seq desc) move - where move.transactions_seq = tx and - ledger = _ledger - group by move.account_address, move.asset -) data -$$; - -create or replace function get_aggregated_effective_volumes_for_transaction(_ledger varchar, tx numeric) returns jsonb - stable - language sql -as -$$ -select aggregate_objects(jsonb_build_object(data.account_address, data.aggregated)) -from ( - select distinct on (move.account_address, move.asset) - move.account_address, - volumes_to_jsonb((move.asset, first(move.post_commit_effective_volumes))) as aggregated - from (select * from moves order by seq desc) move - where move.transactions_seq = tx - and ledger = _ledger - group by move.account_address, move.asset -) data -$$; \ No newline at end of file diff --git a/components/ledger/internal/storage/ledgerstore/migrations_v1.go b/components/ledger/internal/storage/ledgerstore/migrations_v1.go deleted file mode 100644 index f44d9d1cbf..0000000000 --- a/components/ledger/internal/storage/ledgerstore/migrations_v1.go +++ /dev/null @@ -1,203 +0,0 @@ -package ledgerstore - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - ledger "github.com/formancehq/ledger/internal" - "github.com/lib/pq" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -var ( - batchSize uint64 = 10000 - oldSchemaRenameSuffix = "_save_v2_0_0" -) - -type LogV1 struct { - ID uint64 `bun:"id,unique,type:bigint"` - Type string `bun:"type,type:varchar"` - Hash string `bun:"hash,type:varchar"` - Date time.Time `bun:"date,type:timestamptz"` - Data json.RawMessage `bun:"data,type:jsonb"` -} - -func readLogsRange( - ctx context.Context, - schema string, - sqlTx bun.Tx, - idMin, idMax uint64, -) ([]LogV1, error) { - rawLogs := make([]LogV1, 0) - if err := sqlTx. - NewSelect(). - Table(fmt.Sprintf(`%s.log`, schema)). - Where("id >= ?", idMin). - Where("id < ?", idMax). - Scan(ctx, &rawLogs); err != nil { - return nil, err - } - - return rawLogs, nil -} - -func convertMetadata(ret map[string]any) map[string]any { - oldMetadata := ret["metadata"].(map[string]any) - newMetadata := make(map[string]string) - for k, v := range oldMetadata { - switch v := v.(type) { - case map[string]any: - if len(v) == 2 && v["type"] != nil && v["value"] != nil { - switch v["type"] { - case "asset", "string", "account": - newMetadata[k] = v["value"].(string) - case "monetary": - newMetadata[k] = fmt.Sprintf("%s %d", - v["value"].(map[string]any)["asset"].(string), - int(v["value"].(map[string]any)["amount"].(float64)), - ) - case "portion": - newMetadata[k] = v["value"].(map[string]any)["specific"].(string) - case "number": - newMetadata[k] = fmt.Sprint(v["value"]) - } - } else { - newMetadata[k] = fmt.Sprint(v) - } - default: - newMetadata[k] = fmt.Sprint(v) - } - } - ret["metadata"] = newMetadata - - return ret -} - -func convertTransaction(ret map[string]any) map[string]any { - ret = convertMetadata(ret) - ret["id"] = ret["txid"] - delete(ret, "txid") - - return ret -} - -func (l *LogV1) ToLogsV2() (Logs, error) { - logType := ledger.LogTypeFromString(l.Type) - - ret := make(map[string]any) - if err := json.Unmarshal(l.Data, &ret); err != nil { - panic(err) - } - - var data any - switch logType { - case ledger.NewTransactionLogType: - data = map[string]any{ - "transaction": convertTransaction(ret), - "accountMetadata": map[string]any{}, - } - case ledger.SetMetadataLogType: - data = convertMetadata(ret) - case ledger.RevertedTransactionLogType: - data = l.Data - default: - panic("unknown type " + logType.String()) - } - - asJson, err := json.Marshal(data) - if err != nil { - panic(err) - } - - return Logs{ - ID: (*bunpaginate.BigInt)(big.NewInt(int64(l.ID))), - Type: logType.String(), - Hash: []byte(l.Hash), - Date: l.Date, - Data: asJson, - }, nil -} - -func batchLogs( - ctx context.Context, - schema string, - sqlTx bun.Tx, - logs []Logs, -) error { - // Beware: COPY query is not supported by bun if the pgx driver is used. - stmt, err := sqlTx.PrepareContext(ctx, pq.CopyInSchema( - schema, - "logs", - "ledger", "id", "type", "hash", "date", "data", - )) - if err != nil { - return err - } - - for _, l := range logs { - _, err = stmt.ExecContext(ctx, schema, l.ID, l.Type, l.Hash, l.Date, RawMessage(l.Data)) - if err != nil { - return err - } - } - - _, err = stmt.ExecContext(ctx) - if err != nil { - return err - } - - err = stmt.Close() - if err != nil { - return err - } - - return nil -} - -func migrateLogs( - ctx context.Context, - schemaV1Name string, - schemaV2Name string, - sqlTx bun.Tx, -) error { - - var idMin uint64 - var idMax = idMin + batchSize - for { - logs, err := readLogsRange(ctx, schemaV1Name, sqlTx, idMin, idMax) - if err != nil { - return errors.Wrap(err, "reading logs from old table") - } - - if len(logs) == 0 { - break - } - - logsV2 := make([]Logs, 0, len(logs)) - for _, l := range logs { - logV2, err := l.ToLogsV2() - if err != nil { - return err - } - - logsV2 = append(logsV2, logV2) - } - - err = batchLogs(ctx, schemaV2Name, sqlTx, logsV2) - if err != nil { - return err - } - - idMin = idMax - idMax = idMin + batchSize - } - - return nil -} diff --git a/components/ledger/internal/storage/ledgerstore/store.go b/components/ledger/internal/storage/ledgerstore/store.go deleted file mode 100644 index 3504a49387..0000000000 --- a/components/ledger/internal/storage/ledgerstore/store.go +++ /dev/null @@ -1,42 +0,0 @@ -package ledgerstore - -import ( - "context" - - "github.com/formancehq/go-libs/migrations" - - _ "github.com/jackc/pgx/v5/stdlib" - "github.com/uptrace/bun" -) - -type Store struct { - bucket *Bucket - - name string -} - -func (store *Store) Name() string { - return store.name -} - -func (store *Store) GetDB() *bun.DB { - return store.bucket.db -} - -func (store *Store) IsUpToDate(ctx context.Context) (bool, error) { - return store.bucket.IsUpToDate(ctx) -} - -func (store *Store) GetMigrationsInfo(ctx context.Context) ([]migrations.Info, error) { - return store.bucket.GetMigrationsInfo(ctx) -} - -func New( - bucket *Bucket, - name string, -) (*Store, error) { - return &Store{ - bucket: bucket, - name: name, - }, nil -} diff --git a/components/ledger/internal/storage/ledgerstore/store_benchmarks_test.go b/components/ledger/internal/storage/ledgerstore/store_benchmarks_test.go deleted file mode 100644 index 93b4a5a441..0000000000 --- a/components/ledger/internal/storage/ledgerstore/store_benchmarks_test.go +++ /dev/null @@ -1,579 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "math/big" - "os" - "testing" - "text/tabwriter" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunexplain" - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/stretchr/testify/require" - "github.com/uptrace/bun" -) - -var nbTransactions = flag.Int("transactions", 10000, "number of transactions to create") -var batch = flag.Int("batch", 1000, "logs batching") -var ledgers = flag.Int("ledgers", 100, "number of ledger for multi ledgers benchmarks") - -type bunContextHook struct{} - -func (b bunContextHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context { - hooks := ctx.Value("hooks") - if hooks == nil { - return ctx - } - - for _, hook := range hooks.([]bun.QueryHook) { - ctx = hook.BeforeQuery(ctx, event) - } - - return ctx -} - -func (b bunContextHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) { - hooks := ctx.Value("hooks") - if hooks == nil { - return - } - - for _, hook := range hooks.([]bun.QueryHook) { - hook.AfterQuery(ctx, event) - } - - return -} - -var _ bun.QueryHook = &bunContextHook{} - -func contextWithHook(ctx context.Context, hooks ...bun.QueryHook) context.Context { - return context.WithValue(ctx, "hooks", hooks) -} - -type scenarioInfo struct { - nbAccounts int -} - -type scenario struct { - name string - setup func(ctx context.Context, b *testing.B, store *Store) *scenarioInfo -} - -var now = time.Now() - -var scenarios = []scenario{ - { - name: "nominal", - setup: func(ctx context.Context, b *testing.B, store *Store) *scenarioInfo { - var lastLog *ledger.ChainedLog - for i := 0; i < *nbTransactions/(*batch); i++ { - logs := make([]*ledger.ChainedLog, 0) - appendLog := func(log *ledger.Log) { - chainedLog := log.ChainLog(lastLog) - logs = append(logs, chainedLog) - lastLog = chainedLog - } - for j := 0; j < (*batch); j += 2 { - provision := big.NewInt(10000) - itemPrice := provision.Div(provision, big.NewInt(2)) - fees := itemPrice.Div(itemPrice, big.NewInt(100)) // 1% - - appendLog(ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting( - "world", fmt.Sprintf("player:%d", j/2), "USD/2", provision, - )). - WithID(big.NewInt(int64(i*(*batch)+j))). - WithDate(now.Add(time.Minute*time.Duration(i*(*batch)+j))), - map[string]metadata.Metadata{}, - )) - appendLog(ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting(fmt.Sprintf("player:%d", j/2), "seller", "USD/2", itemPrice), - ledger.NewPosting("seller", "fees", "USD/2", fees), - ). - WithID(big.NewInt(int64(i*(*batch)+j+1))). - WithDate(now.Add(time.Minute*time.Duration(i*(*batch)+j))), - map[string]metadata.Metadata{}, - )) - status := "pending" - if j%8 == 0 { - status = "terminated" - } - appendLog(ledger.NewSetMetadataLog(now.Add(time.Minute*time.Duration(i*(*batch)+j)), ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeTransaction, - TargetID: big.NewInt(int64(i*(*batch) + j + 1)), - Metadata: map[string]string{ - "status": status, - }, - })) - } - require.NoError(b, store.InsertLogs(ctx, logs...)) - } - - nbAccounts := *batch / 2 - - for i := 0; i < nbAccounts; i++ { - lastLog = ledger.NewSetMetadataLog(now, ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: fmt.Sprintf("player:%d", i), - Metadata: map[string]string{ - "level": fmt.Sprint(i % 4), - }, - }).ChainLog(lastLog) - require.NoError(b, store.InsertLogs(ctx, lastLog)) - } - - return &scenarioInfo{ - nbAccounts: nbAccounts, - } - }, - }, - { - name: "multi-ledger", - setup: func(ctx context.Context, b *testing.B, store *Store) *scenarioInfo { - var lastLog *ledger.ChainedLog - - nbAccounts := *batch / 2 - loadData := func(store *Store) { - for i := 0; i < *nbTransactions/(*batch); i++ { - logs := make([]*ledger.ChainedLog, 0) - appendLog := func(log *ledger.Log) { - chainedLog := log.ChainLog(lastLog) - logs = append(logs, chainedLog) - lastLog = chainedLog - } - for j := 0; j < (*batch); j += 2 { - provision := big.NewInt(10000) - itemPrice := provision.Div(provision, big.NewInt(2)) - fees := itemPrice.Div(itemPrice, big.NewInt(100)) // 1% - - appendLog(ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting( - "world", fmt.Sprintf("player:%d", j/2), "USD/2", provision, - )). - WithID(big.NewInt(int64(i*(*batch)+j))). - WithDate(now.Add(time.Minute*time.Duration(i*(*batch)+j))), - map[string]metadata.Metadata{}, - )) - appendLog(ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings( - ledger.NewPosting(fmt.Sprintf("player:%d", j/2), "seller", "USD/2", itemPrice), - ledger.NewPosting("seller", "fees", "USD/2", fees), - ). - WithID(big.NewInt(int64(i*(*batch)+j+1))). - WithDate(now.Add(time.Minute*time.Duration(i*(*batch)+j))), - map[string]metadata.Metadata{}, - )) - status := "pending" - if j%8 == 0 { - status = "terminated" - } - appendLog(ledger.NewSetMetadataLog(now.Add(time.Minute*time.Duration(i*(*batch)+j)), ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeTransaction, - TargetID: big.NewInt(int64(i*(*batch) + j + 1)), - Metadata: map[string]string{ - "status": status, - }, - })) - } - require.NoError(b, store.InsertLogs(ctx, logs...)) - } - - for i := 0; i < nbAccounts; i++ { - lastLog = ledger.NewSetMetadataLog(now, ledger.SetMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeAccount, - TargetID: fmt.Sprintf("player:%d", i), - Metadata: map[string]string{ - "level": fmt.Sprint(i % 4), - }, - }).ChainLog(lastLog) - require.NoError(b, store.InsertLogs(ctx, lastLog)) - } - } - - for i := 0; i < *ledgers; i++ { - store := newLedgerStore(b) - loadData(store) - } - loadData(store) - - return &scenarioInfo{ - nbAccounts: nbAccounts, - } - }, - }, -} - -func reportMetrics(ctx context.Context, b *testing.B, store *Store) { - type stat struct { - RelID string `bun:"relid"` - IndexRelID string `bun:"indexrelid"` - RelName string `bun:"relname"` - IndexRelName string `bun:"indexrelname"` - IdxScan int `bun:"idxscan"` - IdxTupRead int `bun:"idx_tup_read"` - IdxTupFetch int `bun:"idx_tup_fetch"` - } - ret := make([]stat, 0) - err := store.GetDB().NewSelect(). - Table("pg_stat_user_indexes"). - Where("schemaname = ?", store.name). - Scan(ctx, &ret) - require.NoError(b, err) - - tabWriter := tabwriter.NewWriter(os.Stderr, 8, 8, 0, '\t', 0) - defer func() { - require.NoError(b, tabWriter.Flush()) - }() - _, err = fmt.Fprintf(tabWriter, "IndexRelName\tIdxScan\tIdxTypRead\tIdxTupFetch\r\n") - require.NoError(b, err) - - _, err = fmt.Fprintf(tabWriter, "---\t---\r\n") - require.NoError(b, err) - - for _, s := range ret { - _, err := fmt.Fprintf(tabWriter, "%s\t%d\t%d\t%d\r\n", s.IndexRelName, s.IdxScan, s.IdxTupRead, s.IdxTupFetch) - require.NoError(b, err) - } -} - -func reportTableSizes(ctx context.Context, b *testing.B, store *Store) { - - tabWriter := tabwriter.NewWriter(os.Stderr, 12, 8, 0, '\t', 0) - defer func() { - require.NoError(b, tabWriter.Flush()) - }() - _, err := fmt.Fprintf(tabWriter, "Table\tTotal size\tTable size\tRelation size\tIndexes size\tMain size\tFSM size\tVM size\tInit size\r\n") - require.NoError(b, err) - - _, err = fmt.Fprintf(tabWriter, "---\t---\t---\t---\t---\t---\t---\t---\r\n") - require.NoError(b, err) - - for _, table := range []string{ - "transactions", "accounts", "moves", "logs", "transactions_metadata", "accounts_metadata", - } { - totalRelationSize := "" - err := store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_total_relation_size('%s'))`, table)). - Scan(&totalRelationSize) - require.NoError(b, err) - - tableSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_table_size('%s'))`, table)). - Scan(&tableSize) - require.NoError(b, err) - - relationSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_relation_size('%s'))`, table)). - Scan(&relationSize) - require.NoError(b, err) - - indexesSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_indexes_size('%s'))`, table)). - Scan(&indexesSize) - require.NoError(b, err) - - mainSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_relation_size('%s', 'main'))`, table)). - Scan(&mainSize) - require.NoError(b, err) - - fsmSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_relation_size('%s', 'fsm'))`, table)). - Scan(&fsmSize) - require.NoError(b, err) - - vmSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_relation_size('%s', 'vm'))`, table)). - Scan(&vmSize) - require.NoError(b, err) - - initSize := "" - err = store.GetDB().DB.QueryRowContext(ctx, fmt.Sprintf(`select pg_size_pretty(pg_relation_size('%s', 'init'))`, table)). - Scan(&initSize) - require.NoError(b, err) - - _, err = fmt.Fprintf(tabWriter, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\r\n", - table, totalRelationSize, tableSize, relationSize, indexesSize, mainSize, fsmSize, vmSize, initSize) - require.NoError(b, err) - } -} - -func BenchmarkList(b *testing.B) { - - ctx := logging.TestingContext() - - for _, scenario := range scenarios { - b.Run(scenario.name, func(b *testing.B) { - store := newLedgerStore(b, &bunContextHook{}) - info := scenario.setup(ctx, b, store) - - defer func() { - if testing.Verbose() { - reportMetrics(ctx, b, store) - reportTableSizes(ctx, b, store) - } - }() - - _, err := store.GetDB().Exec("VACUUM FULL ANALYZE") - require.NoError(b, err) - - runAllWithPIT := func(b *testing.B, pit *time.Time) { - b.Run("transactions", func(b *testing.B) { - benchmarksReadTransactions(b, ctx, store, info, pit) - }) - b.Run("accounts", func(b *testing.B) { - benchmarksReadAccounts(b, ctx, store, pit) - }) - b.Run("aggregates", func(b *testing.B) { - benchmarksGetAggregatedBalances(b, ctx, store, pit) - }) - } - runAllWithPIT(b, nil) - b.Run("using pit", func(b *testing.B) { - // Use pit with the more recent, this way we force the storage to use a join - // Doing this allowing to test the worst case - runAllWithPIT(b, pointer.For(now.Add(time.Minute*time.Duration(*nbTransactions)))) - }) - }) - } -} - -func benchmarksReadTransactions(b *testing.B, ctx context.Context, store *Store, info *scenarioInfo, pit *time.Time) { - type testCase struct { - name string - query query.Builder - allowEmptyResponse bool - expandVolumes bool - expandEffectiveVolumes bool - } - - testCases := []testCase{ - { - name: "no query", - }, - { - name: "using an exact address", - query: query.Match("account", fmt.Sprintf("player:%d", info.nbAccounts-1)), // Last inserted account - }, - { - name: "using an address segment", - query: query.Match("account", fmt.Sprintf(":%d", info.nbAccounts-1)), - }, - { - name: "using a metadata metadata", - query: query.Match("metadata[status]", "terminated"), - }, - { - name: "using non existent account by exact address", - query: query.Match("account", fmt.Sprintf("player:%d", info)), - allowEmptyResponse: true, - }, - { - name: "using non existent metadata", - query: query.Match("metadata[foo]", "bar"), - allowEmptyResponse: true, - }, - { - name: "with expand volumes", - expandVolumes: true, - }, - { - name: "with expand effective volumes", - expandEffectiveVolumes: true, - }, - } - - for _, t := range testCases { - t := t - b.Run(t.name, func(b *testing.B) { - var q GetTransactionsQuery - for i := 0; i < b.N; i++ { - q = NewGetTransactionsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{ - PageSize: 100, - QueryBuilder: t.query, - Options: PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: pit, - }, - }, - }) - if t.expandVolumes { - q = q.WithExpandVolumes() - } - if t.expandEffectiveVolumes { - q = q.WithExpandEffectiveVolumes() - } - ret, err := store.GetTransactions(ctx, q) - require.NoError(b, err) - if !t.allowEmptyResponse && len(ret.Data) == 0 { - require.Fail(b, "response should not be empty") - } - } - - explainRequest(ctx, b, func(ctx context.Context) { - _, err := store.GetTransactions(ctx, q) - require.NoError(b, err) - }) - }) - } -} - -func benchmarksReadAccounts(b *testing.B, ctx context.Context, store *Store, pit *time.Time) { - type testCase struct { - name string - query query.Builder - allowEmptyResponse bool - expandVolumes, expandEffectiveVolumes bool - } - - testCases := []testCase{ - { - name: "with no query", - }, - { - name: "filtering on address segment", - query: query.Match("address", ":0"), - }, - { - name: "filtering on metadata", - query: query.Match("metadata[level]", "2"), - }, - { - name: "with expand volumes", - expandVolumes: true, - }, - { - name: "with expand effective volumes", - expandEffectiveVolumes: true, - }, - } - - for _, t := range testCases { - t := t - b.Run(t.name, func(b *testing.B) { - var q GetAccountsQuery - for i := 0; i < b.N; i++ { - q = NewGetAccountsQuery(PaginatedQueryOptions[PITFilterWithVolumes]{ - PageSize: 100, - QueryBuilder: t.query, - Options: PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: pit, - }, - }, - }) - if t.expandVolumes { - q = q.WithExpandVolumes() - } - if t.expandEffectiveVolumes { - q = q.WithExpandEffectiveVolumes() - } - ret, err := store.GetAccountsWithVolumes(ctx, q) - require.NoError(b, err) - if !t.allowEmptyResponse && len(ret.Data) == 0 { - require.Fail(b, "response should not be empty") - } - - } - - explainRequest(ctx, b, func(ctx context.Context) { - _, err := store.GetAccountsWithVolumes(ctx, q) - require.NoError(b, err) - }) - }) - } -} - -func benchmarksGetAggregatedBalances(b *testing.B, ctx context.Context, store *Store, pit *time.Time) { - type testCase struct { - name string - query query.Builder - allowEmptyResponse bool - } - - testCases := []testCase{ - { - name: "with no query", - }, - { - name: "filtering on exact account address", - query: query.Match("address", "player:0"), - }, - { - name: "filtering on account address segment", - query: query.Match("address", ":0"), - }, - { - name: "filtering on metadata", - query: query.Match("metadata[level]", "2"), - }, - } - - for _, t := range testCases { - t := t - b.Run(t.name, func(b *testing.B) { - var q GetAggregatedBalanceQuery - for i := 0; i < b.N; i++ { - q = NewGetAggregatedBalancesQuery(PITFilter{ - PIT: pit, - }, t.query, false) - ret, err := store.GetAggregatedBalances(ctx, q) - require.NoError(b, err) - if !t.allowEmptyResponse && len(ret) == 0 { - require.Fail(b, "response should not be empty") - } - } - - explainRequest(ctx, b, func(ctx context.Context) { - _, err := store.GetAggregatedBalances(ctx, q) - require.NoError(b, err) - }) - }) - } -} - -func explainRequest(ctx context.Context, b *testing.B, f func(ctx context.Context)) { - var ( - explained string - jsonExplained string - ) - additionalHooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - additionalHooks = append(additionalHooks, bunexplain.NewExplainHook(bunexplain.WithListener(func(data string) { - explained = data - }))) - } - additionalHooks = append(additionalHooks, bunexplain.NewExplainHook( - bunexplain.WithListener(func(data string) { - jsonExplained = data - }), - bunexplain.WithJSONFormat(), - )) - ctx = contextWithHook(ctx, additionalHooks...) - f(ctx) - - if testing.Verbose() { - fmt.Println(explained) - } - jsonQueryPlan := make([]any, 0) - - require.NoError(b, json.Unmarshal([]byte(jsonExplained), &jsonQueryPlan)) - b.ReportMetric(jsonQueryPlan[0].(map[string]any)["Plan"].(map[string]any)["Total Cost"].(float64), "cost") -} diff --git a/components/ledger/internal/storage/ledgerstore/store_test.go b/components/ledger/internal/storage/ledgerstore/store_test.go deleted file mode 100644 index b3f2cc43f9..0000000000 --- a/components/ledger/internal/storage/ledgerstore/store_test.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - - "github.com/formancehq/go-libs/collectionutils" - "github.com/formancehq/go-libs/metadata" - ledger "github.com/formancehq/ledger/internal" -) - -// TODO: remove that -func insertTransactions(ctx context.Context, s *Store, txs ...ledger.Transaction) error { - var previous *ledger.ChainedLog - logs := collectionutils.Map(txs, func(from ledger.Transaction) *ledger.ChainedLog { - previous = ledger.NewTransactionLog(&from, map[string]metadata.Metadata{}).ChainLog(previous) - return previous - }) - return s.InsertLogs(ctx, logs...) -} diff --git a/components/ledger/internal/storage/ledgerstore/transactions.go b/components/ledger/internal/storage/ledgerstore/transactions.go deleted file mode 100644 index 6157388fd3..0000000000 --- a/components/ledger/internal/storage/ledgerstore/transactions.go +++ /dev/null @@ -1,445 +0,0 @@ -package ledgerstore - -import ( - "context" - "database/sql/driver" - "encoding/json" - "errors" - "fmt" - "math/big" - "regexp" - "strings" - - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/uptrace/bun" -) - -const ( - MovesTableName = "moves" -) - -var ( - metadataRegex = regexp.MustCompile("metadata\\[(.+)\\]") -) - -type Transaction struct { - bun.BaseModel `bun:"transactions,alias:transactions"` - - ID *bunpaginate.BigInt `bun:"id,type:numeric"` - Timestamp time.Time `bun:"timestamp,type:timestamp without time zone"` - Reference string `bun:"reference,type:varchar,unique,nullzero"` - Postings []ledger.Posting `bun:"postings,type:jsonb"` - Metadata metadata.Metadata `bun:"metadata,type:jsonb,default:'{}'"` - RevertedAt *time.Time `bun:"reverted_at"` - LastUpdate *time.Time `bun:"last_update"` -} - -func (t *Transaction) toCore() *ledger.Transaction { - return &ledger.Transaction{ - TransactionData: ledger.TransactionData{ - Reference: t.Reference, - Metadata: t.Metadata, - Timestamp: t.Timestamp, - Postings: t.Postings, - }, - ID: (*big.Int)(t.ID), - Reverted: t.RevertedAt != nil && !t.RevertedAt.IsZero(), - } -} - -type ExpandedTransaction struct { - Transaction - bun.BaseModel `bun:"transactions,alias:transactions"` - - ID *bunpaginate.BigInt `bun:"id,type:numeric"` - Timestamp time.Time `bun:"timestamp,type:timestamp without time zone"` - Reference string `bun:"reference,type:varchar,unique,nullzero"` - Postings []ledger.Posting `bun:"postings,type:jsonb"` - Metadata metadata.Metadata `bun:"metadata,type:jsonb,default:'{}'"` - PostCommitEffectiveVolumes ledger.AccountsAssetsVolumes `bun:"post_commit_effective_volumes,type:jsonb"` - PostCommitVolumes ledger.AccountsAssetsVolumes `bun:"post_commit_volumes,type:jsonb"` - RevertedAt *time.Time `bun:"reverted_at"` - LastUpdate *time.Time `bun:"last_update"` -} - -func (t *ExpandedTransaction) toCore() *ledger.ExpandedTransaction { - var ( - preCommitEffectiveVolumes ledger.AccountsAssetsVolumes - preCommitVolumes ledger.AccountsAssetsVolumes - ) - if t.PostCommitEffectiveVolumes != nil { - preCommitEffectiveVolumes = t.PostCommitEffectiveVolumes.Copy() - for _, posting := range t.Postings { - preCommitEffectiveVolumes.AddOutput(posting.Source, posting.Asset, big.NewInt(0).Neg(posting.Amount)) - preCommitEffectiveVolumes.AddInput(posting.Destination, posting.Asset, big.NewInt(0).Neg(posting.Amount)) - } - } - if t.PostCommitVolumes != nil { - preCommitVolumes = t.PostCommitVolumes.Copy() - for _, posting := range t.Postings { - preCommitVolumes.AddOutput(posting.Source, posting.Asset, big.NewInt(0).Neg(posting.Amount)) - preCommitVolumes.AddInput(posting.Destination, posting.Asset, big.NewInt(0).Neg(posting.Amount)) - } - } - return &ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - TransactionData: ledger.TransactionData{ - Reference: t.Reference, - Metadata: t.Metadata, - Timestamp: t.Timestamp, - Postings: t.Postings, - }, - ID: (*big.Int)(t.ID), - Reverted: t.RevertedAt != nil && !t.RevertedAt.IsZero(), - }, - PreCommitEffectiveVolumes: preCommitEffectiveVolumes, - PostCommitEffectiveVolumes: t.PostCommitEffectiveVolumes, - PreCommitVolumes: preCommitVolumes, - PostCommitVolumes: t.PostCommitVolumes, - } -} - -type account string - -var _ driver.Valuer = account("") - -func (m1 account) Value() (driver.Value, error) { - ret, err := json.Marshal(strings.Split(string(m1), ":")) - if err != nil { - return nil, err - } - return string(ret), nil -} - -// Scan - Implement the database/sql scanner interface -func (m1 *account) Scan(value interface{}) error { - if value == nil { - return nil - } - v, err := driver.String.ConvertValue(value) - if err != nil { - return err - } - - array := make([]string, 0) - switch vv := v.(type) { - case []uint8: - err = json.Unmarshal(vv, &array) - case string: - err = json.Unmarshal([]byte(vv), &array) - default: - panic("not handled type") - } - if err != nil { - return err - } - *m1 = account(strings.Join(array, ":")) - return nil -} - -func (store *Store) buildTransactionQuery(p PITFilterWithVolumes, query *bun.SelectQuery) *bun.SelectQuery { - - selectMetadata := query.NewSelect(). - Table("transactions_metadata"). - Where("transactions.seq = transactions_metadata.transactions_seq"). - Order("revision desc"). - Limit(1) - - if p.PIT != nil && !p.PIT.IsZero() { - selectMetadata = selectMetadata.Where("date <= ?", p.PIT) - } - - query = query. - ModelTableExpr("transactions"). - Where("transactions.ledger = ?", store.name) - - if p.PIT != nil && !p.PIT.IsZero() { - query = query. - Where("timestamp <= ?", p.PIT). - ColumnExpr("transactions.*"). - Column("transactions_metadata.metadata"). - Join(fmt.Sprintf(`left join lateral (%s) as transactions_metadata on true`, selectMetadata.String())). - ColumnExpr(fmt.Sprintf("case when reverted_at is not null and reverted_at > '%s' then null else reverted_at end", p.PIT.Format(time.DateFormat))) - } else { - query = query.Column("transactions.metadata", "transactions.*") - } - - if p.ExpandEffectiveVolumes { - query = query.ColumnExpr("get_aggregated_effective_volumes_for_transaction(?, transactions.seq) as post_commit_effective_volumes", store.name) - } - if p.ExpandVolumes { - query = query.ColumnExpr("get_aggregated_volumes_for_transaction(?, transactions.seq) as post_commit_volumes", store.name) - } - return query -} - -func (store *Store) transactionQueryContext(qb query.Builder, q GetTransactionsQuery) (string, []any, error) { - - return qb.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "reference" || key == "timestamp": - return fmt.Sprintf("%s %s ?", key, query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - case key == "reverted": - if operator != "$match" { - return "", nil, newErrInvalidQuery("'reverted' column can only be used with $match") - } - switch value := value.(type) { - case bool: - ret := "reverted_at is" - if value { - ret += " not" - } - return ret + " null", nil, nil - default: - return "", nil, newErrInvalidQuery("'reverted' can only be used with bool value") - } - case key == "account": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, newErrInvalidQuery("'account' column can only be used with $match") - } - switch address := value.(type) { - case string: - return filterAccountAddressOnTransactions(address, true, true), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'account'", address) - } - case key == "source": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, errors.New("'source' column can only be used with $match") - } - switch address := value.(type) { - case string: - return filterAccountAddressOnTransactions(address, true, false), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'source'", address) - } - case key == "destination": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, errors.New("'destination' column can only be used with $match") - } - switch address := value.(type) { - case string: - return filterAccountAddressOnTransactions(address, false, true), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'destination'", address) - } - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, newErrInvalidQuery("'account' column can only be used with $match") - } - match := metadataRegex.FindAllStringSubmatch(key, 3) - - key := "metadata" - if q.Options.Options.PIT != nil && !q.Options.Options.PIT.IsZero() { - key = "transactions_metadata.metadata" - } - - return key + " @> ?", []any{map[string]any{ - match[0][1]: value, - }}, nil - - case key == "metadata": - if operator != "$exists" { - return "", nil, newErrInvalidQuery("'metadata' key filter can only be used with $exists") - } - if q.Options.Options.PIT != nil && !q.Options.Options.PIT.IsZero() { - key = "transactions_metadata.metadata" - } - - return fmt.Sprintf("%s -> ? IS NOT NULL", key), []any{value}, nil - default: - return "", nil, newErrInvalidQuery("unknown key '%s' when building query", key) - } - })) -} - -func (store *Store) buildTransactionListQuery(selectQuery *bun.SelectQuery, q PaginatedQueryOptions[PITFilterWithVolumes], where string, args []any) *bun.SelectQuery { - - selectQuery = store.buildTransactionQuery(q.Options, selectQuery) - if where != "" { - return selectQuery.Where(where, args...) - } - - return selectQuery -} - -func (store *Store) GetTransactions(ctx context.Context, q GetTransactionsQuery) (*bunpaginate.Cursor[ledger.ExpandedTransaction], error) { - - var ( - where string - args []any - err error - ) - if q.Options.QueryBuilder != nil { - where, args, err = store.transactionQueryContext(q.Options.QueryBuilder, q) - if err != nil { - return nil, err - } - } - - transactions, err := paginateWithColumn[PaginatedQueryOptions[PITFilterWithVolumes], ExpandedTransaction](store, ctx, - (*bunpaginate.ColumnPaginatedQuery[PaginatedQueryOptions[PITFilterWithVolumes]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildTransactionListQuery(query, q.Options, where, args) - }, - ) - if err != nil { - return nil, err - } - - return bunpaginate.MapCursor(transactions, func(from ExpandedTransaction) ledger.ExpandedTransaction { - return *from.toCore() - }), nil -} - -func (store *Store) CountTransactions(ctx context.Context, q GetTransactionsQuery) (int, error) { - - var ( - where string - args []any - err error - ) - - if q.Options.QueryBuilder != nil { - where, args, err = store.transactionQueryContext(q.Options.QueryBuilder, q) - if err != nil { - return 0, err - } - } - - return count[ExpandedTransaction](store, true, ctx, func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildTransactionListQuery(query, q.Options, where, args) - }) -} - -func (store *Store) GetTransactionWithVolumes(ctx context.Context, filter GetTransactionQuery) (*ledger.ExpandedTransaction, error) { - ret, err := fetch[*ExpandedTransaction](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildTransactionQuery(filter.PITFilterWithVolumes, query). - Where("transactions.id = ?", filter.ID). - Limit(1) - }) - if err != nil { - return nil, err - } - - return ret.toCore(), nil -} - -func (store *Store) GetTransaction(ctx context.Context, txId *big.Int) (*ledger.Transaction, error) { - tx, err := fetch[*Transaction](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - ColumnExpr(`transactions.id, transactions.reference, transactions.postings, transactions.timestamp, transactions.reverted_at, tm.metadata`). - Join("left join transactions_metadata tm on tm.transactions_seq = transactions.seq"). - Where("transactions.id = ?", (*bunpaginate.BigInt)(txId)). - Where("transactions.ledger = ?", store.name). - Order("tm.revision desc"). - Limit(1) - }) - if err != nil { - return nil, err - } - - return tx.toCore(), nil -} - -func (store *Store) GetTransactionByReference(ctx context.Context, ref string) (*ledger.ExpandedTransaction, error) { - ret, err := fetch[*ExpandedTransaction](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - ColumnExpr(`transactions.id, transactions.reference, transactions.postings, transactions.timestamp, transactions.reverted_at, tm.metadata`). - Join("left join transactions_metadata tm on tm.transactions_seq = transactions.seq"). - Where("transactions.reference = ?", ref). - Where("transactions.ledger = ?", store.name). - Order("tm.revision desc"). - Limit(1) - }) - if err != nil { - return nil, err - } - - return ret.toCore(), nil -} - -func (store *Store) GetLastTransaction(ctx context.Context) (*ledger.ExpandedTransaction, error) { - ret, err := fetch[*ExpandedTransaction](store, true, ctx, - func(query *bun.SelectQuery) *bun.SelectQuery { - return query. - ColumnExpr(`transactions.id, transactions.reference, transactions.postings, transactions.timestamp, transactions.reverted_at, tm.metadata`). - Join("left join transactions_metadata tm on tm.transactions_seq = transactions.seq"). - Order("transactions.seq desc", "tm.revision desc"). - Where("transactions.ledger = ?", store.name). - Limit(1) - }) - if err != nil { - return nil, err - } - - return ret.toCore(), nil -} - -type GetTransactionsQuery bunpaginate.ColumnPaginatedQuery[PaginatedQueryOptions[PITFilterWithVolumes]] - -func (q GetTransactionsQuery) WithExpandVolumes() GetTransactionsQuery { - q.Options.Options.ExpandVolumes = true - - return q -} - -func (q GetTransactionsQuery) WithExpandEffectiveVolumes() GetTransactionsQuery { - q.Options.Options.ExpandEffectiveVolumes = true - - return q -} - -func (q GetTransactionsQuery) WithColumn(column string) GetTransactionsQuery { - ret := pointer.For((bunpaginate.ColumnPaginatedQuery[PaginatedQueryOptions[PITFilterWithVolumes]])(q)) - ret = ret.WithColumn(column) - - return GetTransactionsQuery(*ret) -} - -func NewGetTransactionsQuery(options PaginatedQueryOptions[PITFilterWithVolumes]) GetTransactionsQuery { - return GetTransactionsQuery{ - PageSize: options.PageSize, - Column: "id", - Order: bunpaginate.OrderDesc, - Options: options, - } -} - -type GetTransactionQuery struct { - PITFilterWithVolumes - ID *big.Int -} - -func (q GetTransactionQuery) WithExpandVolumes() GetTransactionQuery { - q.ExpandVolumes = true - - return q -} - -func (q GetTransactionQuery) WithExpandEffectiveVolumes() GetTransactionQuery { - q.ExpandEffectiveVolumes = true - - return q -} - -func NewGetTransactionQuery(id *big.Int) GetTransactionQuery { - return GetTransactionQuery{ - PITFilterWithVolumes: PITFilterWithVolumes{}, - ID: id, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/transactions_test.go b/components/ledger/internal/storage/ledgerstore/transactions_test.go deleted file mode 100644 index 90fd9e11aa..0000000000 --- a/components/ledger/internal/storage/ledgerstore/transactions_test.go +++ /dev/null @@ -1,1169 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "context" - "math/big" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/pointer" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - internaltesting "github.com/formancehq/ledger/internal/testing" - "github.com/stretchr/testify/require" -) - -func expandLogs(logs ...*ledger.Log) []ledger.ExpandedTransaction { - ret := make([]ledger.ExpandedTransaction, 0) - accumulatedVolumes := ledger.AccountsAssetsVolumes{} - - appendTx := func(tx *ledger.Transaction) { - expandedTx := &ledger.ExpandedTransaction{ - Transaction: *tx, - } - for _, posting := range tx.Postings { - expandedTx.PreCommitVolumes.AddInput(posting.Destination, posting.Asset, accumulatedVolumes.GetVolumes(posting.Destination, posting.Asset).Input) - expandedTx.PreCommitVolumes.AddOutput(posting.Destination, posting.Asset, accumulatedVolumes.GetVolumes(posting.Destination, posting.Asset).Output) - expandedTx.PreCommitVolumes.AddOutput(posting.Source, posting.Asset, accumulatedVolumes.GetVolumes(posting.Source, posting.Asset).Output) - expandedTx.PreCommitVolumes.AddInput(posting.Source, posting.Asset, accumulatedVolumes.GetVolumes(posting.Source, posting.Asset).Input) - } - for _, posting := range tx.Postings { - accumulatedVolumes.AddOutput(posting.Source, posting.Asset, posting.Amount) - accumulatedVolumes.AddInput(posting.Destination, posting.Asset, posting.Amount) - } - for _, posting := range tx.Postings { - expandedTx.PostCommitVolumes.AddInput(posting.Destination, posting.Asset, accumulatedVolumes.GetVolumes(posting.Destination, posting.Asset).Input) - expandedTx.PostCommitVolumes.AddOutput(posting.Destination, posting.Asset, accumulatedVolumes.GetVolumes(posting.Destination, posting.Asset).Output) - expandedTx.PostCommitVolumes.AddOutput(posting.Source, posting.Asset, accumulatedVolumes.GetVolumes(posting.Source, posting.Asset).Output) - expandedTx.PostCommitVolumes.AddInput(posting.Source, posting.Asset, accumulatedVolumes.GetVolumes(posting.Source, posting.Asset).Input) - } - ret = append(ret, *expandedTx) - } - - for _, log := range logs { - switch payload := log.Data.(type) { - case ledger.NewTransactionLogPayload: - appendTx(payload.Transaction) - case ledger.RevertedTransactionLogPayload: - appendTx(payload.RevertTransaction) - ret[payload.RevertedTransactionID.Uint64()].Reverted = true - case ledger.SetMetadataLogPayload: - ret[payload.TargetID.(*big.Int).Uint64()].Metadata = ret[payload.TargetID.(*big.Int).Uint64()].Metadata.Merge(payload.Metadata) - } - } - - return ret -} - -func Reverse[T any](values ...T) []T { - ret := make([]T, len(values)) - for i := 0; i < len(values)/2; i++ { - ret[i], ret[len(values)-i-1] = values[len(values)-i-1], values[i] - } - if len(values)%2 == 1 { - ret[(len(values)-1)/2] = values[(len(values)-1)/2] - } - return ret -} - -func TestGetTransactionWithVolumes(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx1", - Timestamp: now.Add(-3 * time.Hour), - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - }, - } - tx2 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx2", - Timestamp: now.Add(-2 * time.Hour), - }, - }, - PostCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(200), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(200), - Output: big.NewInt(0), - }, - }, - }, - PreCommitVolumes: ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, - } - - require.NoError(t, insertTransactions(ctx, store, tx1.Transaction, tx2.Transaction)) - - tx, err := store.GetTransactionWithVolumes(ctx, NewGetTransactionQuery(tx1.ID). - WithExpandVolumes(). - WithExpandEffectiveVolumes()) - require.NoError(t, err) - require.Equal(t, tx1.Postings, tx.Postings) - require.Equal(t, tx1.Reference, tx.Reference) - require.Equal(t, tx1.Timestamp, tx.Timestamp) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, tx.PostCommitVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(0), - }, - }, - }, tx.PreCommitVolumes) - - tx, err = store.GetTransactionWithVolumes(ctx, NewGetTransactionQuery(tx2.ID). - WithExpandVolumes(). - WithExpandEffectiveVolumes()) - require.Equal(t, tx2.Postings, tx.Postings) - require.Equal(t, tx2.Reference, tx.Reference) - require.Equal(t, tx2.Timestamp, tx.Timestamp) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(200), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(200), - Output: big.NewInt(0), - }, - }, - }, tx.PostCommitVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "world": { - "USD": { - Input: big.NewInt(0), - Output: big.NewInt(100), - }, - }, - "central_bank": { - "USD": { - Input: big.NewInt(100), - Output: big.NewInt(0), - }, - }, - }, tx.PreCommitVolumes) -} - -func TestGetTransaction(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx1", - Timestamp: now.Add(-3 * time.Hour), - }, - } - tx2 := ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx2", - Timestamp: now.Add(-2 * time.Hour), - }, - } - - require.NoError(t, insertTransactions(context.Background(), store, tx1, tx2)) - - tx, err := store.GetTransaction(context.Background(), tx1.ID) - require.NoError(t, err) - require.Equal(t, tx1.Postings, tx.Postings) - require.Equal(t, tx1.Reference, tx.Reference) - require.Equal(t, tx1.Timestamp, tx.Timestamp) -} - -func TestGetTransactionByReference(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx1", - Timestamp: now.Add(-3 * time.Hour), - }, - } - tx2 := ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: []ledger.Posting{ - { - Source: "world", - Destination: "central_bank", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Reference: "tx2", - Timestamp: now.Add(-2 * time.Hour), - }, - } - - require.NoError(t, insertTransactions(context.Background(), store, tx1, tx2)) - - tx, err := store.GetTransactionByReference(context.Background(), "tx1") - require.NoError(t, err) - require.Equal(t, tx1.Postings, tx.Postings) - require.Equal(t, tx1.Reference, tx.Reference) - require.Equal(t, tx1.Timestamp, tx.Timestamp) -} - -func TestInsertTransactions(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - t.Run("success inserting transaction", func(t *testing.T) { - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "world", - Destination: "bob", - Amount: big.NewInt(10), - Asset: "USD", - }, - }, - Timestamp: now.Add(-3 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - "bob": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(110), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(100), - }, - "bob": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(10), - }, - }, - } - - err := insertTransactions(ctx, store, tx1.Transaction) - require.NoError(t, err, "inserting transaction should not fail") - - tx, err := store.GetTransactionWithVolumes(ctx, NewGetTransactionQuery(big.NewInt(0)). - WithExpandVolumes()) - require.NoError(t, err) - internaltesting.RequireEqual(t, tx1, *tx) - }) - - t.Run("success inserting multiple transactions", func(t *testing.T) { - t.Parallel() - tx2 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "polo", - Amount: big.NewInt(200), - Asset: "USD", - }, - }, - Timestamp: now.Add(-2 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(110), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(310), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(200), - }, - }, - } - - tx3 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(2), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "gfyrag", - Amount: big.NewInt(150), - Asset: "USD", - }, - }, - Timestamp: now.Add(-1 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(310), - }, - "gfyrag": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(460), - }, - "gfyrag": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(150), - }, - }, - } - - require.NoError(t, store.InsertLogs(context.Background(), - ledger.NewTransactionLog(&tx2.Transaction, map[string]metadata.Metadata{}).ChainLog(nil).WithID(2), - ledger.NewTransactionLog(&tx3.Transaction, map[string]metadata.Metadata{}).ChainLog(nil).WithID(3), - )) - - tx, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(big.NewInt(1)).WithExpandVolumes()) - require.NoError(t, err, "getting transaction should not fail") - internaltesting.RequireEqual(t, tx2, *tx) - - tx, err = store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(big.NewInt(2)).WithExpandVolumes()) - require.NoError(t, err, "getting transaction should not fail") - internaltesting.RequireEqual(t, tx3, *tx) - }) -} - -func TestCountTransactions(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Timestamp: now.Add(-3 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(100), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(100), - }, - }, - } - tx2 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "polo", - Amount: big.NewInt(200), - Asset: "USD", - }, - }, - Timestamp: now.Add(-2 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(100), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(300), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(200), - }, - }, - } - - tx3 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(2), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "gfyrag", - Amount: big.NewInt(150), - Asset: "USD", - }, - }, - Timestamp: now.Add(-1 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(300), - }, - "gfyrag": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(450), - }, - "gfyrag": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(150), - }, - }, - } - - err := insertTransactions(context.Background(), store, tx1.Transaction, tx2.Transaction, tx3.Transaction) - require.NoError(t, err, "inserting transaction should not fail") - - count, err := store.CountTransactions(context.Background(), NewGetTransactionsQuery(NewPaginatedQueryOptions(PITFilterWithVolumes{}))) - require.NoError(t, err, "counting transactions should not fail") - require.Equal(t, 3, count, "count should be equal") -} - -func TestUpdateTransactionsMetadata(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Timestamp: now.Add(-3 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(100), - }, - "alice": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(100), - }, - }, - } - tx2 := ledger.ExpandedTransaction{ - Transaction: ledger.Transaction{ - ID: big.NewInt(1), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "polo", - Amount: big.NewInt(200), - Asset: "USD", - }, - }, - Timestamp: now.Add(-2 * time.Hour), - Metadata: metadata.Metadata{}, - }, - }, - PreCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(100), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes(), - }, - }, - PostCommitVolumes: map[string]ledger.VolumesByAssets{ - "world": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithOutputInt64(300), - }, - "polo": map[string]*ledger.Volumes{ - "USD": ledger.NewEmptyVolumes().WithInputInt64(200), - }, - }, - } - - err := insertTransactions(context.Background(), store, tx1.Transaction, tx2.Transaction) - require.NoError(t, err, "inserting transaction should not fail") - - err = store.InsertLogs(context.Background(), - ledger.NewSetMetadataOnTransactionLog(time.Now(), tx1.ID, metadata.Metadata{"foo1": "bar2"}).ChainLog(nil).WithID(3), - ledger.NewSetMetadataOnTransactionLog(time.Now(), tx2.ID, metadata.Metadata{"foo2": "bar2"}).ChainLog(nil).WithID(4), - ) - require.NoError(t, err, "updating multiple transaction metadata should not fail") - - tx, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(big.NewInt(0)).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err, "getting transaction should not fail") - require.Equal(t, tx.Metadata, metadata.Metadata{"foo1": "bar2"}, "metadata should be equal") - - tx, err = store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(big.NewInt(1)).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err, "getting transaction should not fail") - require.Equal(t, tx.Metadata, metadata.Metadata{"foo2": "bar2"}, "metadata should be equal") -} - -func TestDeleteTransactionsMetadata(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.Transaction{ - ID: big.NewInt(0), - TransactionData: ledger.TransactionData{ - Postings: ledger.Postings{ - { - Source: "world", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "USD", - }, - }, - Timestamp: now.Add(-3 * time.Hour), - Metadata: metadata.Metadata{}, - }, - } - - require.NoError(t, store.InsertLogs(context.Background(), - ledger.NewTransactionLog(&tx1, map[string]metadata.Metadata{}).ChainLog(nil).WithID(1), - ledger.NewSetMetadataOnTransactionLog(time.Now(), tx1.ID, metadata.Metadata{"foo1": "bar1", "foo2": "bar2"}).ChainLog(nil).WithID(2), - )) - - tx, err := store.GetTransaction(context.Background(), tx1.ID) - require.NoError(t, err) - require.Equal(t, tx.Metadata, metadata.Metadata{"foo1": "bar1", "foo2": "bar2"}) - - require.NoError(t, store.InsertLogs(context.Background(), - ledger.NewDeleteMetadataLog(time.Now(), ledger.DeleteMetadataLogPayload{ - TargetType: ledger.MetaTargetTypeTransaction, - TargetID: tx1.ID, - Key: "foo1", - }).ChainLog(nil).WithID(3), - )) - - tx, err = store.GetTransaction(context.Background(), tx1.ID) - require.NoError(t, err) - require.Equal(t, metadata.Metadata{"foo2": "bar2"}, tx.Metadata) -} - -func TestInsertTransactionInPast(t *testing.T) { - t.Parallel() - - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD/2", big.NewInt(100)), - ).WithDate(now) - - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user1", "USD/2", big.NewInt(50)), - ).WithDate(now.Add(time.Hour)).WithIDUint64(1) - - // Insert in past must modify pre/post commit volumes of tx2 - tx3 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user2", "USD/2", big.NewInt(50)), - ).WithDate(now.Add(30 * time.Minute)).WithIDUint64(2) - - // Insert before the oldest tx must update first_usage of involved accounts - tx4 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD/2", big.NewInt(100)), - ).WithDate(now.Add(-time.Minute)).WithIDUint64(3) - - require.NoError(t, store.InsertLogs(ctx, - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}).ChainLog(nil).WithID(1), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}).ChainLog(nil).WithID(2), - ledger.NewTransactionLog(tx3, map[string]metadata.Metadata{}).ChainLog(nil).WithID(3), - ledger.NewTransactionLog(tx4, map[string]metadata.Metadata{}).ChainLog(nil).WithID(4), - )) - - tx2FromDatabase, err := store.GetTransactionWithVolumes(ctx, NewGetTransactionQuery(tx2.ID).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err) - - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(200, 50), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(0, 0), - }, - }, tx2FromDatabase.PreCommitEffectiveVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(200, 100), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(50, 0), - }, - }, tx2FromDatabase.PostCommitEffectiveVolumes) - - account, err := store.GetAccount(ctx, "bank") - require.NoError(t, err) - require.Equal(t, tx4.Timestamp, account.FirstUsage) -} - -func TestInsertTransactionInPastInOneBatch(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD/2", big.NewInt(100)), - ).WithDate(now) - - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user1", "USD/2", big.NewInt(50)), - ).WithDate(now.Add(time.Hour)).WithIDUint64(1) - - // Insert in past must modify pre/post commit volumes of tx2 - tx3 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user2", "USD/2", big.NewInt(50)), - ).WithDate(now.Add(30 * time.Minute)).WithIDUint64(2) - - require.NoError(t, insertTransactions(context.Background(), store, *tx1, *tx2, *tx3)) - - tx2FromDatabase, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(tx2.ID).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err) - - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 50), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(0, 0), - }, - }, tx2FromDatabase.PreCommitEffectiveVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 100), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(50, 0), - }, - }, tx2FromDatabase.PostCommitEffectiveVolumes) -} - -func TestInsertTwoTransactionAtSameDateInSameBatch(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD/2", big.NewInt(100)), - ).WithDate(now.Add(-time.Hour)) - - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user1", "USD/2", big.NewInt(10)), - ).WithDate(now).WithIDUint64(1) - - tx3 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user2", "USD/2", big.NewInt(10)), - ).WithDate(now).WithIDUint64(2) - - require.NoError(t, insertTransactions(context.Background(), store, *tx1, *tx2, *tx3)) - - tx2FromDatabase, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(tx2.ID).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err) - - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 10), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(10, 0), - }, - }, tx2FromDatabase.PostCommitVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 0), - }, - "user1": { - "USD/2": ledger.NewVolumesInt64(0, 0), - }, - }, tx2FromDatabase.PreCommitVolumes) - - tx3FromDatabase, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(tx3.ID).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err) - - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 10), - }, - "user2": { - "USD/2": ledger.NewVolumesInt64(0, 0), - }, - }, tx3FromDatabase.PreCommitVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 20), - }, - "user2": { - "USD/2": ledger.NewVolumesInt64(10, 0), - }, - }, tx3FromDatabase.PostCommitVolumes) -} - -func TestInsertTwoTransactionAtSameDateInTwoBatch(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - - tx1 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("world", "bank", "USD/2", big.NewInt(100)), - ).WithDate(now.Add(-time.Hour)) - - tx2 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user1", "USD/2", big.NewInt(10)), - ).WithDate(now).WithIDUint64(1) - - require.NoError(t, insertTransactions(context.Background(), store, *tx1, *tx2)) - - tx3 := ledger.NewTransaction().WithPostings( - ledger.NewPosting("bank", "user2", "USD/2", big.NewInt(10)), - ).WithDate(now).WithIDUint64(2) - - require.NoError(t, store.InsertLogs(context.Background(), - ledger.NewTransactionLog(tx3, map[string]metadata.Metadata{}).ChainLog(nil).WithID(3), - )) - - tx3FromDatabase, err := store.GetTransactionWithVolumes(context.Background(), NewGetTransactionQuery(tx3.ID).WithExpandVolumes().WithExpandEffectiveVolumes()) - require.NoError(t, err) - - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 10), - }, - "user2": { - "USD/2": ledger.NewVolumesInt64(0, 0), - }, - }, tx3FromDatabase.PreCommitVolumes) - internaltesting.RequireEqual(t, ledger.AccountsAssetsVolumes{ - "bank": { - "USD/2": ledger.NewVolumesInt64(100, 20), - }, - "user2": { - "USD/2": ledger.NewVolumesInt64(10, 0), - }, - }, tx3FromDatabase.PostCommitVolumes) -} - -func TestGetTransactions(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - tx1 := ledger.NewTransaction(). - WithIDUint64(0). - WithPostings( - ledger.NewPosting("world", "alice", "USD", big.NewInt(100)), - ). - WithMetadata(metadata.Metadata{"category": "1"}). - WithDate(now.Add(-3 * time.Hour)) - tx2 := ledger.NewTransaction(). - WithIDUint64(1). - WithPostings( - ledger.NewPosting("world", "bob", "USD", big.NewInt(100)), - ). - WithMetadata(metadata.Metadata{"category": "2"}). - WithDate(now.Add(-2 * time.Hour)) - tx3 := ledger.NewTransaction(). - WithIDUint64(2). - WithPostings( - ledger.NewPosting("world", "users:marley", "USD", big.NewInt(100)), - ). - WithMetadata(metadata.Metadata{"category": "3"}). - WithDate(now.Add(-time.Hour)) - tx4 := ledger.NewTransaction(). - WithIDUint64(3). - WithPostings( - ledger.NewPosting("users:marley", "world", "USD", big.NewInt(100)), - ). - WithDate(now) - tx5 := ledger.NewTransaction(). - WithIDUint64(4). - WithPostings( - ledger.NewPosting("users:marley", "sellers:amazon", "USD", big.NewInt(100)), - ). - WithDate(now) - - logs := []*ledger.Log{ - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx3, map[string]metadata.Metadata{}), - ledger.NewRevertedTransactionLog(time.Now(), tx3.ID, tx4), - ledger.NewSetMetadataOnTransactionLog(time.Now(), tx3.ID, metadata.Metadata{ - "additional_metadata": "true", - }), - ledger.NewTransactionLog(tx5, map[string]metadata.Metadata{}), - } - - require.NoError(t, store.InsertLogs(ctx, ledger.ChainLogs(logs...)...)) - - type testCase struct { - name string - query PaginatedQueryOptions[PITFilterWithVolumes] - expected *bunpaginate.Cursor[ledger.ExpandedTransaction] - expectError error - } - testCases := []testCase{ - { - name: "nominal", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: Reverse(expandLogs(logs...)...), - }, - }, - { - name: "address filter", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("account", "bob")), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: expandLogs(logs...)[1:2], - }, - }, - { - name: "address filter using segments matching two addresses by individual segments", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("account", "users:amazon")), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: []ledger.ExpandedTransaction{}, - }, - }, - { - name: "address filter using segment", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("account", "users:")), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: Reverse(expandLogs(logs...)[2:]...), - }, - }, - { - name: "filter using metadata", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("metadata[category]", "2")), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: expandLogs(logs...)[1:2], - }, - }, - { - name: "using point in time", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{ - PITFilter: PITFilter{ - PIT: pointer.For(now.Add(-time.Hour)), - }, - }), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: Reverse(expandLogs(logs[:3]...)...), - }, - }, - { - name: "filter using invalid key", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("invalid", "2")), - expectError: &errInvalidQuery{}, - }, - { - name: "reverted transactions", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Match("reverted", true)), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: expandLogs(logs...)[2:3], - }, - }, - { - name: "filter using exists metadata", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Exists("metadata", "category")), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: Reverse(expandLogs(logs...)[0:3]...), - }, - }, - { - name: "filter using exists metadata2", - query: NewPaginatedQueryOptions(PITFilterWithVolumes{}). - WithQueryBuilder(query.Not(query.Exists("metadata", "category"))), - expected: &bunpaginate.Cursor[ledger.ExpandedTransaction]{ - PageSize: 15, - HasMore: false, - Data: Reverse(expandLogs(logs...)[3:5]...), - }, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - tc.query.Options.ExpandVolumes = true - tc.query.Options.ExpandEffectiveVolumes = false - cursor, err := store.GetTransactions(ctx, NewGetTransactionsQuery(tc.query)) - if tc.expectError != nil { - require.True(t, errors.Is(err, tc.expectError)) - } else { - require.NoError(t, err) - require.Len(t, cursor.Data, len(tc.expected.Data)) - internaltesting.RequireEqual(t, *tc.expected, *cursor) - - count, err := store.CountTransactions(ctx, NewGetTransactionsQuery(tc.query)) - require.NoError(t, err) - - require.EqualValues(t, len(tc.expected.Data), count) - } - }) - } -} - -func TestGetLastTransaction(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - ctx := logging.TestingContext() - - tx1 := ledger.NewTransaction(). - WithIDUint64(0). - WithPostings( - ledger.NewPosting("world", "alice", "USD", big.NewInt(100)), - ) - tx2 := ledger.NewTransaction(). - WithIDUint64(1). - WithPostings( - ledger.NewPosting("world", "bob", "USD", big.NewInt(100)), - ) - tx3 := ledger.NewTransaction(). - WithIDUint64(2). - WithPostings( - ledger.NewPosting("world", "users:marley", "USD", big.NewInt(100)), - ) - - logs := []*ledger.Log{ - ledger.NewTransactionLog(tx1, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx2, map[string]metadata.Metadata{}), - ledger.NewTransactionLog(tx3, map[string]metadata.Metadata{}), - } - - require.NoError(t, store.InsertLogs(ctx, ledger.ChainLogs(logs...)...)) - - tx, err := store.GetLastTransaction(ctx) - require.NoError(t, err) - require.Equal(t, *tx3, tx.Transaction) -} - -func TestTransactionFromWorldToWorld(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - ctx := logging.TestingContext() - - tx := ledger.NewTransaction(). - WithIDUint64(0). - WithPostings( - ledger.NewPosting("world", "world", "USD", big.NewInt(100)), - ) - require.NoError(t, store.InsertLogs(ctx, ledger.ChainLogs(ledger.NewTransactionLog(tx, map[string]metadata.Metadata{}))...)) - - account, err := store.GetAccountWithVolumes(ctx, NewGetAccountQuery("world").WithExpandVolumes()) - require.NoError(t, err) - internaltesting.RequireEqual(t, big.NewInt(0), account.Volumes.Balances()["USD"]) -} diff --git a/components/ledger/internal/storage/ledgerstore/utils.go b/components/ledger/internal/storage/ledgerstore/utils.go deleted file mode 100644 index 76bb74f2e3..0000000000 --- a/components/ledger/internal/storage/ledgerstore/utils.go +++ /dev/null @@ -1,256 +0,0 @@ -package ledgerstore - -import ( - "context" - "encoding/json" - "fmt" - "reflect" - "strings" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/query" - "github.com/uptrace/bun" -) - -func fetch[T any](s *Store, addModel bool, ctx context.Context, builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (T, error) { - - var ret T - ret = reflect.New(reflect.TypeOf(ret).Elem()).Interface().(T) - - query := s.bucket.db.NewSelect() - - if addModel { - query = query.Model(ret) - } - - for _, builder := range builders { - query = query.Apply(builder) - } - - if err := query.Scan(ctx, ret); err != nil { - return ret, sqlutils.PostgresError(err) - } - - return ret, nil -} - -func paginateWithOffset[FILTERS any, RETURN any](s *Store, ctx context.Context, - q *bunpaginate.OffsetPaginatedQuery[FILTERS], builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (*bunpaginate.Cursor[RETURN], error) { - - query := s.bucket.db.NewSelect() - for _, builder := range builders { - query = query.Apply(builder) - } - return bunpaginate.UsingOffset[FILTERS, RETURN](ctx, query, *q) -} - -func paginateWithOffsetWithoutModel[FILTERS any, RETURN any](s *Store, ctx context.Context, - q *bunpaginate.OffsetPaginatedQuery[FILTERS], builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (*bunpaginate.Cursor[RETURN], error) { - - query := s.bucket.db.NewSelect() - for _, builder := range builders { - query = query.Apply(builder) - } - - return bunpaginate.UsingOffset[FILTERS, RETURN](ctx, query, *q) -} - -func paginateWithColumn[FILTERS any, RETURN any](s *Store, ctx context.Context, q *bunpaginate.ColumnPaginatedQuery[FILTERS], builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (*bunpaginate.Cursor[RETURN], error) { - query := s.bucket.db.NewSelect() - for _, builder := range builders { - query = query.Apply(builder) - } - - ret, err := bunpaginate.UsingColumn[FILTERS, RETURN](ctx, query, *q) - if err != nil { - return nil, sqlutils.PostgresError(err) - } - - return ret, nil -} - -func count[T any](s *Store, addModel bool, ctx context.Context, builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (int, error) { - query := s.bucket.db.NewSelect() - if addModel { - query = query.Model((*T)(nil)) - } - for _, builder := range builders { - query = query.Apply(builder) - } - return s.bucket.db.NewSelect(). - TableExpr("(" + query.String() + ") data"). - Count(ctx) -} - -func filterAccountAddress(address, key string) string { - parts := make([]string, 0) - src := strings.Split(address, ":") - - needSegmentCheck := false - for _, segment := range src { - needSegmentCheck = segment == "" - if needSegmentCheck { - break - } - } - - if needSegmentCheck { - parts = append(parts, fmt.Sprintf("jsonb_array_length(%s_array) = %d", key, len(src))) - - for i, segment := range src { - if len(segment) == 0 { - continue - } - parts = append(parts, fmt.Sprintf("%s_array @@ ('$[%d] == \"%s\"')::jsonpath", key, i, segment)) - } - } else { - parts = append(parts, fmt.Sprintf("%s = '%s'", key, address)) - } - - return strings.Join(parts, " and ") -} - -func filterAccountAddressOnTransactions(address string, source, destination bool) string { - src := strings.Split(address, ":") - - needSegmentCheck := false - for _, segment := range src { - needSegmentCheck = segment == "" - if needSegmentCheck { - break - } - } - - if needSegmentCheck { - m := map[string]any{ - fmt.Sprint(len(src)): nil, - } - parts := make([]string, 0) - - for i, segment := range src { - if len(segment) == 0 { - continue - } - m[fmt.Sprint(i)] = segment - } - - data, err := json.Marshal([]any{m}) - if err != nil { - panic(err) - } - - if source { - parts = append(parts, fmt.Sprintf("sources_arrays @> '%s'", string(data))) - } - if destination { - parts = append(parts, fmt.Sprintf("destinations_arrays @> '%s'", string(data))) - } - return strings.Join(parts, " or ") - } else { - data, err := json.Marshal([]string{address}) - if err != nil { - panic(err) - } - - parts := make([]string, 0) - if source { - parts = append(parts, fmt.Sprintf("sources @> '%s'", string(data))) - } - if destination { - parts = append(parts, fmt.Sprintf("destinations @> '%s'", string(data))) - } - return strings.Join(parts, " or ") - } -} - -func filterPIT(pit *time.Time, column string) func(query *bun.SelectQuery) *bun.SelectQuery { - return func(query *bun.SelectQuery) *bun.SelectQuery { - if pit == nil || pit.IsZero() { - return query - } - return query.Where(fmt.Sprintf("%s <= ?", column), pit) - } -} - -func filterOOT(oot *time.Time, column string) func(query *bun.SelectQuery) *bun.SelectQuery { - return func(query *bun.SelectQuery) *bun.SelectQuery { - if oot == nil || oot.IsZero() { - return query - } - return query.Where(fmt.Sprintf("%s >= ?", column), oot) - } -} - -type PaginatedQueryOptions[T any] struct { - QueryBuilder query.Builder `json:"qb"` - PageSize uint64 `json:"pageSize"` - Options T `json:"options"` -} - -func (v *PaginatedQueryOptions[T]) UnmarshalJSON(data []byte) error { - type aux struct { - QueryBuilder json.RawMessage `json:"qb"` - PageSize uint64 `json:"pageSize"` - Options T `json:"options"` - } - x := &aux{} - if err := json.Unmarshal(data, x); err != nil { - return err - } - - *v = PaginatedQueryOptions[T]{ - PageSize: x.PageSize, - Options: x.Options, - } - - var err error - if x.QueryBuilder != nil { - v.QueryBuilder, err = query.ParseJSON(string(x.QueryBuilder)) - if err != nil { - return err - } - } - - return nil -} - -func (opts PaginatedQueryOptions[T]) WithQueryBuilder(qb query.Builder) PaginatedQueryOptions[T] { - opts.QueryBuilder = qb - - return opts -} - -func (opts PaginatedQueryOptions[T]) WithPageSize(pageSize uint64) PaginatedQueryOptions[T] { - opts.PageSize = pageSize - - return opts -} - -func NewPaginatedQueryOptions[T any](options T) PaginatedQueryOptions[T] { - return PaginatedQueryOptions[T]{ - Options: options, - PageSize: bunpaginate.QueryDefaultPageSize, - } -} - -type PITFilter struct { - PIT *time.Time `json:"pit"` - OOT *time.Time `json:"oot"` -} - -type PITFilterWithVolumes struct { - PITFilter - ExpandVolumes bool `json:"volumes"` - ExpandEffectiveVolumes bool `json:"effectiveVolumes"` -} - -type FiltersForVolumes struct { - PITFilter - UseInsertionDate bool - GroupLvl uint -} diff --git a/components/ledger/internal/storage/ledgerstore/volumes.go b/components/ledger/internal/storage/ledgerstore/volumes.go deleted file mode 100644 index 832223fe52..0000000000 --- a/components/ledger/internal/storage/ledgerstore/volumes.go +++ /dev/null @@ -1,184 +0,0 @@ -package ledgerstore - -import ( - "context" - "fmt" - "regexp" - - "github.com/formancehq/go-libs/bun/bunpaginate" - lquery "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/uptrace/bun" -) - -func (store *Store) volumesQueryContext(q GetVolumesWithBalancesQuery) (string, []any, bool, error) { - - metadataRegex := regexp.MustCompile("metadata\\[(.+)\\]") - balanceRegex := regexp.MustCompile("balance\\[(.*)\\]") - var ( - subQuery string - args []any - err error - ) - - var useMetadata = false - - if q.Options.QueryBuilder != nil { - subQuery, args, err = q.Options.QueryBuilder.Build(lquery.ContextFn(func(key, operator string, value any) (string, []any, error) { - - convertOperatorToSQL := func() string { - switch operator { - case "$match": - return "=" - case "$lt": - return "<" - case "$gt": - return ">" - case "$lte": - return "<=" - case "$gte": - return ">=" - } - panic("unreachable") - } - - switch { - case key == "account" || key == "address": - // TODO: Should allow comparison operator only if segments not used - if operator != "$match" { - return "", nil, newErrInvalidQuery(fmt.Sprintf("'%s' column can only be used with $match", key)) - } - - switch address := value.(type) { - case string: - return filterAccountAddress(address, "account_address"), nil, nil - default: - return "", nil, newErrInvalidQuery("unexpected type %T for column 'address'", address) - } - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, newErrInvalidQuery("'metadata' column can only be used with $match") - } - useMetadata = true - match := metadataRegex.FindAllStringSubmatch(key, 3) - key := "metadata" - - return key + " @> ?", []any{map[string]any{ - match[0][1]: value, - }}, nil - case key == "metadata": - if operator != "$exists" { - return "", nil, newErrInvalidQuery("'metadata' key filter can only be used with $exists") - } - useMetadata = true - key := "metadata" - - return fmt.Sprintf("%s -> ? IS NOT NULL", key), []any{value}, nil - case balanceRegex.Match([]byte(key)): - match := balanceRegex.FindAllStringSubmatch(key, 2) - return fmt.Sprintf(`balance %s ? and asset = ?`, convertOperatorToSQL()), []any{value, match[0][1]}, nil - default: - return "", nil, newErrInvalidQuery("unknown key '%s' when building query", key) - } - })) - if err != nil { - return "", nil, false, err - } - } - - return subQuery, args, useMetadata, nil - -} - -func (store *Store) buildVolumesWithBalancesQuery(query *bun.SelectQuery, q GetVolumesWithBalancesQuery, where string, args []any, useMetadata bool) *bun.SelectQuery { - - filtersForVolumes := q.Options.Options - dateFilterColumn := "effective_date" - - if filtersForVolumes.UseInsertionDate { - dateFilterColumn = "insertion_date" - } - - query = query. - Column("account_address_array"). - Column("account_address"). - Column("asset"). - ColumnExpr("sum(case when not is_source then amount else 0 end) as input"). - ColumnExpr("sum(case when is_source then amount else 0 end) as output"). - ColumnExpr("sum(case when not is_source then amount else -amount end) as balance"). - ModelTableExpr("moves") - - if useMetadata { - query = query.ColumnExpr("accounts.metadata as metadata"). - Join(`join lateral ( - select metadata - from accounts a - where a.seq = moves.accounts_seq - ) accounts on true`).Group("metadata") - } - - query = query. - Where("ledger = ?", store.name). - Apply(filterPIT(filtersForVolumes.PIT, dateFilterColumn)). - Apply(filterOOT(filtersForVolumes.OOT, dateFilterColumn)). - GroupExpr("account_address, account_address_array, asset") - - globalQuery := query.NewSelect() - globalQuery = globalQuery. - With("query", query). - ModelTableExpr("query") - - if filtersForVolumes.GroupLvl > 0 { - globalQuery = globalQuery. - ColumnExpr(fmt.Sprintf(`(array_to_string((string_to_array(account_address, ':'))[1:LEAST(array_length(string_to_array(account_address, ':'),1),%d)],':')) as account`, filtersForVolumes.GroupLvl)). - ColumnExpr("asset"). - ColumnExpr("sum(input) as input"). - ColumnExpr("sum(output) as output"). - ColumnExpr("sum(balance) as balance"). - GroupExpr("account, asset") - } else { - globalQuery = globalQuery.ColumnExpr("account_address as account, asset, input, output, balance") - } - - if useMetadata { - globalQuery = globalQuery.Column("metadata") - } - - if where != "" { - globalQuery.Where(where, args...) - } - - return globalQuery -} - -func (store *Store) GetVolumesWithBalances(ctx context.Context, q GetVolumesWithBalancesQuery) (*bunpaginate.Cursor[ledger.VolumesWithBalanceByAssetByAccount], error) { - var ( - where string - args []any - err error - useMetadata bool - ) - if q.Options.QueryBuilder != nil { - where, args, useMetadata, err = store.volumesQueryContext(q) - if err != nil { - return nil, err - } - } - - return paginateWithOffsetWithoutModel[PaginatedQueryOptions[FiltersForVolumes], ledger.VolumesWithBalanceByAssetByAccount]( - store, ctx, (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[FiltersForVolumes]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - return store.buildVolumesWithBalancesQuery(query, q, where, args, useMetadata) - }, - ) -} - -type GetVolumesWithBalancesQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[FiltersForVolumes]] - -func NewGetVolumesWithBalancesQuery(opts PaginatedQueryOptions[FiltersForVolumes]) GetVolumesWithBalancesQuery { - return GetVolumesWithBalancesQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} diff --git a/components/ledger/internal/storage/ledgerstore/volumes_test.go b/components/ledger/internal/storage/ledgerstore/volumes_test.go deleted file mode 100644 index fc6fe8e78c..0000000000 --- a/components/ledger/internal/storage/ledgerstore/volumes_test.go +++ /dev/null @@ -1,634 +0,0 @@ -//go:build it - -package ledgerstore - -import ( - "math/big" - "testing" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/query" - ledger "github.com/formancehq/ledger/internal" - "github.com/stretchr/testify/require" -) - -func TestGetVolumesWithBalances(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - previousPIT := now.Add(-2 * time.Minute) - futurPIT := now.Add(2 * time.Minute) - - previousOOT := now.Add(-2 * time.Minute) - futurOOT := now.Add(2 * time.Minute) - - require.NoError(t, store.InsertLogs(ctx, - ledger.ChainLogs( - ledger.NewSetMetadataOnAccountLog(time.Now(), "account:1", metadata.Metadata{"category": "1"}).WithDate(now), - ledger.NewSetMetadataOnAccountLog(time.Now(), "account:2", metadata.Metadata{"category": "2"}).WithDate(now), - ledger.NewSetMetadataOnAccountLog(time.Now(), "world", metadata.Metadata{"foo": "bar"}).WithDate(now), - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). - WithDate(now.Add(-4*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(4*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(100))). - WithIDUint64(1). - WithDate(now.Add(-3*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(3*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("account:1", "bank", "USD", big.NewInt(50))). - WithDate(now.Add(-2*time.Minute)). - WithIDUint64(2), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(2*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1", "USD", big.NewInt(0))). - WithDate(now.Add(-time.Minute)). - WithIDUint64(3), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(1*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2", "USD", big.NewInt(50))). - WithDate(now).WithIDUint64(4), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2", "USD", big.NewInt(50))). - WithIDUint64(5). - WithDate(now.Add(1*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(-1*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("account:2", "bank", "USD", big.NewInt(50))). - WithDate(now.Add(2*time.Minute)). - WithIDUint64(6), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(-2*time.Minute)), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2", "USD", big.NewInt(25))). - WithDate(now.Add(3*time.Minute)). - WithIDUint64(7), - map[string]metadata.Metadata{}, - ).WithDate(now.Add(-3*time.Minute)), - )..., - )) - - t.Run("Get All Volumes with Balance for Insertion date", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions(FiltersForVolumes{UseInsertionDate: true}))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for Effective date", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions(FiltersForVolumes{UseInsertionDate: false}))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for Insertion date with previous pit", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &previousPIT, OOT: nil}, - UseInsertionDate: true, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:2", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(25), - Output: big.NewInt(50), - Balance: big.NewInt(-25), - }, - }, volumes.Data[0]) - }) - - t.Run("Get All Volumes with Balance for Insertion date with futur pit", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &futurPIT, OOT: nil}, - UseInsertionDate: true, - }))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for Insertion date with previous oot", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: nil, OOT: &previousOOT}, - UseInsertionDate: true, - }))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for Insertion date with futur oot", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: nil, OOT: &futurOOT}, - UseInsertionDate: true, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(200), - Output: big.NewInt(50), - Balance: big.NewInt(150), - }, - }, volumes.Data[0]) - }) - - t.Run("Get All Volumes with Balance for Effective date with previous pit", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &previousPIT, OOT: nil}, - UseInsertionDate: false, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(200), - Output: big.NewInt(50), - Balance: big.NewInt(150), - }, - }, volumes.Data[0]) - }) - - t.Run("Get All Volumes with Balance for Effective date with futur pit", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &futurPIT, OOT: nil}, - UseInsertionDate: false, - }))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for Effective date with previous oot", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: nil, OOT: &previousOOT}, - UseInsertionDate: false, - }))) - require.NoError(t, err) - - require.Len(t, volumes.Data, 4) - }) - - t.Run("Get All Volumes with Balance for effective date with futur oot", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: nil, OOT: &futurOOT}, - UseInsertionDate: false, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:2", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(25), - Output: big.NewInt(50), - Balance: big.NewInt(-25), - }, - }, volumes.Data[0]) - }) - - t.Run("Get All Volumes with Balance for insertion date with futur PIT and now OOT", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &futurPIT, OOT: &now}, - UseInsertionDate: true, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 4) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(0), - Output: big.NewInt(50), - Balance: big.NewInt(-50), - }, - }, volumes.Data[0]) - - }) - - t.Run("Get All Volumes with Balance for insertion date with previous OOT and now PIT", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &now, OOT: &previousOOT}, - UseInsertionDate: true, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:2", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(100), - Output: big.NewInt(50), - Balance: big.NewInt(50), - }, - }, volumes.Data[0]) - - }) - - t.Run("Get All Volumes with Balance for effective date with futur PIT and now OOT", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &futurPIT, OOT: &now}, - UseInsertionDate: false, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:2", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(100), - Output: big.NewInt(50), - Balance: big.NewInt(50), - }, - }, volumes.Data[0]) - }) - - t.Run("Get All Volumes with Balance for insertion date with previous OOT and now PIT", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery(NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &now, OOT: &previousOOT}, - UseInsertionDate: false, - }))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 4) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(0), - Output: big.NewInt(50), - Balance: big.NewInt(-50), - }, - }, volumes.Data[0]) - - }) - - t.Run("Get account1 volume and Balance for insertion date with previous OOT and now PIT", func(t *testing.T) { - t.Parallel() - - volumes, err := store.GetVolumesWithBalances(ctx, - NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{PIT: &now, OOT: &previousOOT}, - UseInsertionDate: false, - }).WithQueryBuilder(query.Match("account", "account:1"))), - ) - - require.NoError(t, err) - require.Len(t, volumes.Data, 1) - require.Equal(t, ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(0), - Output: big.NewInt(50), - Balance: big.NewInt(-50), - }, - }, volumes.Data[0]) - - }) - - t.Run("Using Metadata regex", func(t *testing.T) { - t.Parallel() - - volumes, err := store.GetVolumesWithBalances(ctx, - NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{}).WithQueryBuilder(query.Match("metadata[foo]", "bar"))), - ) - - require.NoError(t, err) - require.Len(t, volumes.Data, 1) - - }) - - t.Run("Using exists metadata filter 1", func(t *testing.T) { - t.Parallel() - - volumes, err := store.GetVolumesWithBalances(ctx, - NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{}).WithQueryBuilder(query.Exists("metadata", "category"))), - ) - - require.NoError(t, err) - require.Len(t, volumes.Data, 2) - - }) - - t.Run("Using exists metadata filter 2", func(t *testing.T) { - t.Parallel() - - volumes, err := store.GetVolumesWithBalances(ctx, - NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{}).WithQueryBuilder(query.Exists("metadata", "foo"))), - ) - - require.NoError(t, err) - require.Len(t, volumes.Data, 1) - - }) - -} - -func TestAggGetVolumesWithBalances(t *testing.T) { - t.Parallel() - store := newLedgerStore(t) - now := time.Now() - ctx := logging.TestingContext() - - // previousPIT := now.Add(-2 * time.Minute) - futurPIT := now.Add(2 * time.Minute) - - previousOOT := now.Add(-2 * time.Minute) - // futurOOT := now.Add(2 * time.Minute) - - require.NoError(t, store.InsertLogs(ctx, - ledger.ChainLogs( - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1:2", "USD", big.NewInt(100))). - WithDate(now.Add(-4*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1:1", "EUR", big.NewInt(100))). - WithIDUint64(1). - WithDate(now.Add(-3*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1:2", "EUR", big.NewInt(50))). - WithDate(now.Add(-2*time.Minute)). - WithIDUint64(2), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:1:3", "USD", big.NewInt(0))). - WithDate(now.Add(-time.Minute)). - WithIDUint64(3), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2:1", "USD", big.NewInt(50))). - WithDate(now).WithIDUint64(4), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2:2", "USD", big.NewInt(50))). - WithIDUint64(5). - WithDate(now.Add(1*time.Minute)), - map[string]metadata.Metadata{}, - ).WithDate(now), - - ledger.NewTransactionLog( - ledger.NewTransaction(). - WithPostings(ledger.NewPosting("world", "account:2:3", "EUR", big.NewInt(25))). - WithDate(now.Add(3*time.Minute)). - WithIDUint64(7), - map[string]metadata.Metadata{}, - ).WithDate(now), - )..., - )) - - t.Run("Aggregation Volumes with Balance for GroupLvl 0", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - UseInsertionDate: true, - GroupLvl: 0, - }).WithQueryBuilder(query.Match("account", "account::")))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 7) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 1", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - UseInsertionDate: true, - GroupLvl: 1, - }).WithQueryBuilder(query.Match("account", "account::")))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 2) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 2", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - UseInsertionDate: true, - GroupLvl: 2, - }).WithQueryBuilder(query.Match("account", "account::")))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 4) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 3", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - UseInsertionDate: true, - GroupLvl: 3, - }).WithQueryBuilder(query.Match("account", "account::")))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 7) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 1 && PIT && OOT && effectiveDate", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{ - PIT: &futurPIT, - OOT: &previousOOT, - }, - UseInsertionDate: false, - GroupLvl: 1, - }).WithQueryBuilder(query.Match("account", "account::")))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 2) - require.Equal(t, volumes.Data[0], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account", - Asset: "EUR", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(50), - Output: big.NewInt(0), - Balance: big.NewInt(50), - }, - }) - require.Equal(t, volumes.Data[1], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(100), - Output: big.NewInt(0), - Balance: big.NewInt(100), - }, - }) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 1 && PIT && OOT && effectiveDate && Balance Filter 1", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{ - PIT: &futurPIT, - OOT: &previousOOT, - }, - UseInsertionDate: false, - GroupLvl: 1, - }).WithQueryBuilder( - query.And(query.Match("account", "account::"), query.Gte("balance[EUR]", 50))))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 1) - require.Equal(t, volumes.Data[0], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account", - Asset: "EUR", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(50), - Output: big.NewInt(0), - Balance: big.NewInt(50), - }, - }) - }) - - t.Run("Aggregation Volumes with Balance for GroupLvl 1 && Balance Filter 2", func(t *testing.T) { - t.Parallel() - volumes, err := store.GetVolumesWithBalances(ctx, NewGetVolumesWithBalancesQuery( - NewPaginatedQueryOptions( - FiltersForVolumes{ - PITFilter: PITFilter{}, - UseInsertionDate: true, - GroupLvl: 2, - }).WithQueryBuilder( - query.Or( - query.Match("account", "account:1:"), - query.Lte("balance[USD]", 0))))) - - require.NoError(t, err) - require.Len(t, volumes.Data, 3) - require.Equal(t, volumes.Data[0], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "EUR", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(150), - Output: big.NewInt(0), - Balance: big.NewInt(150), - }, - }) - require.Equal(t, volumes.Data[1], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "account:1", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(100), - Output: big.NewInt(0), - Balance: big.NewInt(100), - }, - }) - require.Equal(t, volumes.Data[2], ledger.VolumesWithBalanceByAssetByAccount{ - Account: "world", - Asset: "USD", - VolumesWithBalance: ledger.VolumesWithBalance{ - Input: big.NewInt(0), - Output: big.NewInt(200), - Balance: big.NewInt(-200), - }, - }) - }) - -} diff --git a/components/ledger/internal/storage/migrate_ledger_v1_test.go b/components/ledger/internal/storage/migrate_ledger_v1_test.go deleted file mode 100644 index c3c51ffa78..0000000000 --- a/components/ledger/internal/storage/migrate_ledger_v1_test.go +++ /dev/null @@ -1,64 +0,0 @@ -//go:build it - -package storage_test - -import ( - "database/sql" - "os" - "path/filepath" - "testing" - - "github.com/formancehq/go-libs/testing/docker" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/formancehq/ledger/internal/storage/ledgerstore" - "github.com/formancehq/ledger/internal/storage/systemstore" - "github.com/stretchr/testify/require" -) - -func TestMigrateLedgerV1(t *testing.T) { - dockerPool := docker.NewPool(t, logging.Testing()) - srv := pgtesting.CreatePostgresServer(t, dockerPool) - - db, err := sql.Open("postgres", srv.GetDSN()) - require.NoError(t, err) - - data, err := os.ReadFile(filepath.Join("testdata", "v1-dump.sql")) - require.NoError(t, err) - - _, err = db.Exec(string(data)) - require.NoError(t, err) - - ctx := logging.TestingContext() - - d := driver.New(bunconnect.ConnectionOptions{ - DatabaseSourceName: srv.GetDSN(), - }) - require.NoError(t, d.Initialize(ctx)) - - ledgers, err := d.GetSystemStore().ListLedgers(ctx, systemstore.ListLedgersQuery{}) - require.NoError(t, err) - - for _, ledger := range ledgers.Data { - require.NotEmpty(t, ledger.Bucket) - require.Equal(t, ledger.Name, ledger.Bucket) - - bucket, err := d.OpenBucket(ctx, ledger.Bucket) - require.NoError(t, err) - require.NoError(t, bucket.Migrate(ctx)) - - store, err := bucket.GetLedgerStore(ledger.Name) - require.NoError(t, err) - - txs, err := store.GetTransactions(ctx, ledgerstore.NewGetTransactionsQuery(ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes]{})) - require.NoError(t, err) - require.NotEmpty(t, txs) - - accounts, err := store.GetAccountsWithVolumes(ctx, ledgerstore.NewGetAccountsQuery(ledgerstore.PaginatedQueryOptions[ledgerstore.PITFilterWithVolumes]{})) - require.NoError(t, err) - require.NotEmpty(t, accounts) - } -} diff --git a/components/ledger/internal/storage/sqlutils/errors.go b/components/ledger/internal/storage/sqlutils/errors.go deleted file mode 100644 index 07cc5b2740..0000000000 --- a/components/ledger/internal/storage/sqlutils/errors.go +++ /dev/null @@ -1,70 +0,0 @@ -package sqlutils - -import ( - "database/sql" - - "github.com/lib/pq" - "github.com/pkg/errors" -) - -// postgresError is an helper to wrap postgres errors into storage errors -func PostgresError(err error) error { - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return ErrNotFound - } - - switch pge := err.(type) { - case *pq.Error: - switch pge.Code { - case "23505": - return newErrConstraintsFailed(err) - case "53300": - return newErrTooManyClient(err) - } - } - - return err - } - - return nil -} - -var ( - ErrNotFound = errors.New("not found") - ErrBucketAlreadyExists = errors.New("bucket already exists") - ErrStoreAlreadyExists = errors.New("store already exists") - ErrStoreNotFound = errors.New("store not found") -) - -func IsNotFoundError(err error) bool { - return errors.Is(err, ErrNotFound) -} - -type errConstraintsFailed struct { - err error -} - -func (e errConstraintsFailed) Error() string { - return e.err.Error() -} - -func newErrConstraintsFailed(err error) *errConstraintsFailed { - return &errConstraintsFailed{ - err: err, - } -} - -type errTooManyClient struct { - err error -} - -func (e errTooManyClient) Error() string { - return e.err.Error() -} - -func newErrTooManyClient(err error) *errTooManyClient { - return &errTooManyClient{ - err: err, - } -} diff --git a/components/ledger/internal/storage/storagetesting/storage.go b/components/ledger/internal/storage/storagetesting/storage.go deleted file mode 100644 index 53afdf244d..0000000000 --- a/components/ledger/internal/storage/storagetesting/storage.go +++ /dev/null @@ -1,34 +0,0 @@ -package storagetesting - -import ( - "context" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/docker" - - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/formancehq/go-libs/testing/platform/pgtesting" - "github.com/formancehq/ledger/internal/storage/driver" - "github.com/stretchr/testify/require" -) - -func StorageDriver(t docker.T) *driver.Driver { - pgServer := pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - pgDatabase := pgServer.NewDatabase(t) - - d := driver.New(bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDatabase.ConnString(), - MaxIdleConns: 40, - MaxOpenConns: 40, - ConnMaxIdleTime: time.Minute, - }) - - require.NoError(t, d.Initialize(context.Background())) - t.Cleanup(func() { - require.NoError(t, d.Close()) - }) - - return d -} diff --git a/components/ledger/internal/storage/systemstore/configuration.go b/components/ledger/internal/storage/systemstore/configuration.go deleted file mode 100644 index 59c11e9a64..0000000000 --- a/components/ledger/internal/storage/systemstore/configuration.go +++ /dev/null @@ -1,53 +0,0 @@ -package systemstore - -import ( - "context" - - "github.com/formancehq/go-libs/time" - - storageerrors "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/uptrace/bun" -) - -type configuration struct { - bun.BaseModel `bun:"_system.configuration,alias:configuration"` - - Key string `bun:"key,type:varchar(255),pk"` // Primary key - Value string `bun:"value,type:text"` - AddedAt time.Time `bun:"addedAt,type:timestamp"` -} - -func (s *Store) GetConfiguration(ctx context.Context, key string) (string, error) { - query := s.db.NewSelect(). - Model((*configuration)(nil)). - Column("value"). - Where("key = ?", key). - Limit(1). - String() - - row := s.db.QueryRowContext(ctx, query) - if row.Err() != nil { - return "", storageerrors.PostgresError(row.Err()) - } - var value string - if err := row.Scan(&value); err != nil { - return "", storageerrors.PostgresError(err) - } - - return value, nil -} - -func (s *Store) InsertConfiguration(ctx context.Context, key, value string) error { - config := &configuration{ - Key: key, - Value: value, - AddedAt: time.Now(), - } - - _, err := s.db.NewInsert(). - Model(config). - Exec(ctx) - - return storageerrors.PostgresError(err) -} diff --git a/components/ledger/internal/storage/systemstore/ledgers.go b/components/ledger/internal/storage/systemstore/ledgers.go deleted file mode 100644 index 85e5fb438f..0000000000 --- a/components/ledger/internal/storage/systemstore/ledgers.go +++ /dev/null @@ -1,127 +0,0 @@ -package systemstore - -import ( - "context" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -const ( - StateInitializing = "initializing" - StateInUse = "in-use" -) - -type Ledger struct { - bun.BaseModel `bun:"_system.ledgers,alias:ledgers"` - - Name string `bun:"ledger,type:varchar(255),pk" json:"name"` // Primary key - AddedAt time.Time `bun:"addedat,type:timestamp" json:"addedAt"` - Bucket string `bun:"bucket,type:varchar(255)" json:"bucket"` - Metadata map[string]string `bun:"metadata,type:jsonb" json:"metadata"` - State string `bun:"state,type:varchar(255)" json:"-"` -} - -type PaginatedQueryOptions struct { - PageSize uint64 `json:"pageSize"` -} - -type ListLedgersQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions] - -func (query ListLedgersQuery) WithPageSize(pageSize uint64) ListLedgersQuery { - query.PageSize = pageSize - return query -} - -func NewListLedgersQuery(pageSize uint64) ListLedgersQuery { - return ListLedgersQuery{ - PageSize: pageSize, - } -} - -func (s *Store) ListLedgers(ctx context.Context, q ListLedgersQuery) (*bunpaginate.Cursor[Ledger], error) { - query := s.db.NewSelect(). - Column("ledger", "bucket", "addedat", "metadata", "state"). - Order("addedat asc") - - return bunpaginate.UsingOffset[PaginatedQueryOptions, Ledger](ctx, query, bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions](q)) -} - -func (s *Store) DeleteLedger(ctx context.Context, name string) error { - _, err := s.db.NewDelete(). - Model((*Ledger)(nil)). - Where("ledger = ?", name). - Exec(ctx) - - return errors.Wrap(sqlutils.PostgresError(err), "delete ledger from system store") -} - -func (s *Store) RegisterLedger(ctx context.Context, l *Ledger) (bool, error) { - return RegisterLedger(ctx, s.db, l) -} - -func (s *Store) GetLedger(ctx context.Context, name string) (*Ledger, error) { - ret := &Ledger{} - if err := s.db.NewSelect(). - Model(ret). - Column("ledger", "bucket", "addedat", "metadata", "state"). - Where("ledger = ?", name). - Scan(ctx); err != nil { - return nil, sqlutils.PostgresError(err) - } - - return ret, nil -} - -func (s *Store) UpdateLedgerMetadata(ctx context.Context, name string, m metadata.Metadata) error { - _, err := s.db.NewUpdate(). - Model(&Ledger{}). - Set("metadata = metadata || ?", m). - Where("ledger = ?", name). - Exec(ctx) - return err -} - -func (s *Store) UpdateLedgerState(ctx context.Context, name string, state string) error { - _, err := s.db.NewUpdate(). - Model(&Ledger{}). - Set("state = ?", state). - Where("ledger = ?", name). - Exec(ctx) - return err -} - -func (s *Store) DeleteLedgerMetadata(ctx context.Context, name string, key string) error { - _, err := s.db.NewUpdate(). - Model(&Ledger{}). - Set("metadata = metadata - ?", key). - Where("ledger = ?", name). - Exec(ctx) - return err -} - -func RegisterLedger(ctx context.Context, db bun.IDB, l *Ledger) (bool, error) { - if l.Metadata == nil { - l.Metadata = map[string]string{} - } - ret, err := db.NewInsert(). - Model(l). - Ignore(). - Exec(ctx) - if err != nil { - return false, sqlutils.PostgresError(err) - } - - affected, err := ret.RowsAffected() - if err != nil { - return false, sqlutils.PostgresError(err) - } - - return affected > 0, nil -} diff --git a/components/ledger/internal/storage/systemstore/ledgers_test.go b/components/ledger/internal/storage/systemstore/ledgers_test.go deleted file mode 100644 index be6f8781d5..0000000000 --- a/components/ledger/internal/storage/systemstore/ledgers_test.go +++ /dev/null @@ -1,121 +0,0 @@ -//go:build it - -package systemstore - -import ( - "fmt" - "testing" - - "github.com/google/uuid" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/logging" - "github.com/stretchr/testify/require" -) - -func newSystemStore(t *testing.T) *Store { - t.Parallel() - t.Helper() - ctx := logging.TestingContext() - - pgServer := srv.NewDatabase(t) - - store, err := Connect(ctx, bunconnect.ConnectionOptions{ - DatabaseSourceName: pgServer.ConnString(), - }) - require.NoError(t, err) - t.Cleanup(func() { - require.NoError(t, store.Close()) - }) - - require.NoError(t, Migrate(ctx, store.DB())) - - return store -} - -func TestListLedgers(t *testing.T) { - ctx := logging.TestingContext() - store := newSystemStore(t) - - ledgers := make([]Ledger, 0) - pageSize := uint64(2) - count := uint64(10) - now := time.Now() - for i := uint64(0); i < count; i++ { - m := map[string]string{} - if i%2 == 0 { - m["foo"] = "bar" - } - ledger := Ledger{ - Name: fmt.Sprintf("ledger%d", i), - AddedAt: now.Add(time.Duration(i) * time.Second), - Metadata: m, - } - ledgers = append(ledgers, ledger) - _, err := store.RegisterLedger(ctx, &ledger) - require.NoError(t, err) - } - - cursor, err := store.ListLedgers(ctx, NewListLedgersQuery(pageSize)) - require.NoError(t, err) - require.Len(t, cursor.Data, int(pageSize)) - require.Equal(t, ledgers[:pageSize], cursor.Data) - - for i := pageSize; i < count; i += pageSize { - query := ListLedgersQuery{} - require.NoError(t, bunpaginate.UnmarshalCursor(cursor.Next, &query)) - - cursor, err = store.ListLedgers(ctx, query) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.Equal(t, ledgers[i:i+pageSize], cursor.Data) - } -} - -func TestUpdateLedgerMetadata(t *testing.T) { - ctx := logging.TestingContext() - store := newSystemStore(t) - - ledger := &Ledger{ - Name: uuid.NewString(), - AddedAt: time.Now(), - } - _, err := store.RegisterLedger(ctx, ledger) - require.NoError(t, err) - - addedMetadata := map[string]string{ - "foo": "bar", - } - err = store.UpdateLedgerMetadata(ctx, ledger.Name, addedMetadata) - require.NoError(t, err) - - ledgerFromDB, err := store.GetLedger(ctx, ledger.Name) - require.NoError(t, err) - require.Equal(t, addedMetadata, ledgerFromDB.Metadata) -} - -func TestDeleteLedgerMetadata(t *testing.T) { - ctx := logging.TestingContext() - store := newSystemStore(t) - - ledger := &Ledger{ - Name: uuid.NewString(), - AddedAt: time.Now(), - Metadata: map[string]string{ - "foo": "bar", - }, - } - _, err := store.RegisterLedger(ctx, ledger) - require.NoError(t, err) - - err = store.DeleteLedgerMetadata(ctx, ledger.Name, "foo") - require.NoError(t, err) - - ledgerFromDB, err := store.GetLedger(ctx, ledger.Name) - require.NoError(t, err) - require.Equal(t, map[string]string{}, ledgerFromDB.Metadata) -} diff --git a/components/ledger/internal/storage/systemstore/main_test.go b/components/ledger/internal/storage/systemstore/main_test.go deleted file mode 100644 index 0f62887fe0..0000000000 --- a/components/ledger/internal/storage/systemstore/main_test.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build it - -package systemstore - -import ( - "testing" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -var srv *pgtesting.PostgresServer - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/components/ledger/internal/storage/systemstore/migrations.go b/components/ledger/internal/storage/systemstore/migrations.go deleted file mode 100644 index cac5245651..0000000000 --- a/components/ledger/internal/storage/systemstore/migrations.go +++ /dev/null @@ -1,127 +0,0 @@ -package systemstore - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/pkg/errors" - - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/formancehq/go-libs/migrations" - "github.com/uptrace/bun" -) - -func Migrate(ctx context.Context, db bun.IDB) error { - migrator := migrations.NewMigrator(migrations.WithSchema(Schema, true)) - migrator.RegisterMigrations( - migrations.Migration{ - Name: "Init schema", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - - logging.FromContext(ctx).Infof("Checking if ledger v1 upgrade") - exists, err := tx.NewSelect(). - TableExpr("information_schema.columns"). - Where("table_name = 'ledgers'"). - Exists(ctx) - if err != nil { - return err - } - - if exists { - logging.FromContext(ctx).Infof("Detect ledger v1 installation, trigger migration") - _, err := tx.NewAddColumn(). - Table("ledgers"). - ColumnExpr("bucket varchar(255)"). - Exec(ctx) - if err != nil { - return errors.Wrap(err, "adding 'bucket' column") - } - _, err = tx.NewUpdate(). - Table("ledgers"). - Set("bucket = ledger"). - Where("1 = 1"). - Exec(ctx) - return errors.Wrap(err, "setting 'bucket' column") - } - - _, err = tx.NewCreateTable(). - Model((*Ledger)(nil)). - IfNotExists(). - Exec(ctx) - if err != nil { - return sqlutils.PostgresError(err) - } - - _, err = tx.NewCreateTable(). - Model((*configuration)(nil)). - IfNotExists(). - Exec(ctx) - return sqlutils.PostgresError(err) - }, - }, - migrations.Migration{ - Name: "Add ledger, bucket naming constraints 63 chars", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - _, err := tx.ExecContext(ctx, ` - alter table ledgers - add column if not exists ledger varchar(63), - add column if not exists bucket varchar(63); - - alter table ledgers - alter column ledger type varchar(63), - alter column bucket type varchar(63); - `) - if err != nil { - return err - } - return nil - }, - }, - migrations.Migration{ - Name: "Add ledger metadata", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - _, err := tx.ExecContext(ctx, ` - alter table ledgers - add column if not exists metadata jsonb; - `) - if err != nil { - return err - } - return nil - }, - }, - migrations.Migration{ - Name: "Fix empty ledger metadata", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - _, err := tx.ExecContext(ctx, ` - update ledgers - set metadata = '{}'::jsonb - where metadata is null; - `) - if err != nil { - return err - } - return nil - }, - }, - migrations.Migration{ - Name: "Add ledger state", - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - _, err := tx.ExecContext(ctx, ` - alter table ledgers - add column if not exists state varchar(255) default 'initializing'; - - update ledgers - set state = 'in-use' - where state = ''; - `) - if err != nil { - return err - } - return nil - }, - }, - ) - return migrator.Up(ctx, db) -} diff --git a/components/ledger/internal/storage/systemstore/store.go b/components/ledger/internal/storage/systemstore/store.go deleted file mode 100644 index f76e8e6e45..0000000000 --- a/components/ledger/internal/storage/systemstore/store.go +++ /dev/null @@ -1,40 +0,0 @@ -package systemstore - -import ( - "context" - "fmt" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/ledger/internal/storage/sqlutils" - - "github.com/uptrace/bun" -) - -const Schema = "_system" - -type Store struct { - db *bun.DB -} - -func Connect(ctx context.Context, connectionOptions bunconnect.ConnectionOptions, hooks ...bun.QueryHook) (*Store, error) { - - db, err := bunconnect.OpenDBWithSchema(ctx, connectionOptions, Schema, hooks...) - if err != nil { - return nil, sqlutils.PostgresError(err) - } - - _, err = db.ExecContext(ctx, fmt.Sprintf(`create schema if not exists "%s"`, Schema)) - if err != nil { - return nil, sqlutils.PostgresError(err) - } - - return &Store{db: db}, nil -} - -func (s *Store) DB() *bun.DB { - return s.db -} - -func (s *Store) Close() error { - return s.db.Close() -} diff --git a/components/ledger/internal/storage/testdata/v1-dump.sql b/components/ledger/internal/storage/testdata/v1-dump.sql deleted file mode 100644 index f8ce01ee4e..0000000000 --- a/components/ledger/internal/storage/testdata/v1-dump.sql +++ /dev/null @@ -1,959 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 13.8 --- Dumped by pg_dump version 16.1 - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - --- --- Name: _system; Type: SCHEMA; Schema: - --- - -CREATE SCHEMA _system; - --- --- Name: default; Type: SCHEMA; Schema: - --- - -CREATE SCHEMA "default"; - --- --- Name: public; Type: SCHEMA; Schema: - --- - --- *not* creating schema, since initdb creates it - --- --- Name: wallets-002; Type: SCHEMA; Schema: - --- - -CREATE SCHEMA "wallets-002"; - --- --- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - --- - -CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; - - --- --- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; - - --- --- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - --- - -CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; - - --- --- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: --- - -COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; - - --- --- Name: compute_hashes(); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".compute_hashes() RETURNS void - LANGUAGE plpgsql - AS $$ DECLARE r record; BEGIN /* Create JSON object manually as it needs to be in canonical form */ FOR r IN (select id, '{"data":' || "default".normaliz(data::jsonb) || ',"date":"' || to_char (date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') || '","hash":"","id":' || id || ',"type":"' || type || '"}' as canonical from "default".log) LOOP UPDATE "default".log set hash = (select encode(digest( COALESCE((select '{"data":' || "default".normaliz(data::jsonb) || ',"date":"' || to_char (date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') || '","hash":"' || hash || '","id":' || id || ',"type":"' || type || '"}' from "default".log where id = r.id - 1), 'null') || r.canonical, 'sha256' ), 'hex')) WHERE id = r.id; END LOOP; END $$; - --- --- Name: compute_volumes(); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".compute_volumes() RETURNS trigger - LANGUAGE plpgsql - AS $$ DECLARE p record; BEGIN FOR p IN ( SELECT t.postings->>'source' as source, t.postings->>'asset' as asset, sum ((t.postings->>'amount')::bigint) as amount FROM ( SELECT jsonb_array_elements(((newtable.data::jsonb)->>'postings')::jsonb) as postings FROM newtable WHERE newtable.type = 'NEW_TRANSACTION' ) t GROUP BY source, asset ) LOOP INSERT INTO "default".accounts (address, metadata) VALUES (p.source, '{}') ON CONFLICT DO NOTHING; INSERT INTO "default".volumes (account, asset, input, output) VALUES (p.source, p.asset, 0, p.amount::bigint) ON CONFLICT (account, asset) DO UPDATE SET output = p.amount::bigint + ( SELECT output FROM "default".volumes WHERE account = p.source AND asset = p.asset ); END LOOP; FOR p IN ( SELECT t.postings->>'destination' as destination, t.postings->>'asset' as asset, sum ((t.postings->>'amount')::bigint) as amount FROM ( SELECT jsonb_array_elements(((newtable.data::jsonb)->>'postings')::jsonb) as postings FROM newtable WHERE newtable.type = 'NEW_TRANSACTION' ) t GROUP BY destination, asset ) LOOP INSERT INTO "default".accounts (address, metadata) VALUES (p.destination, '{}') ON CONFLICT DO NOTHING; INSERT INTO "default".volumes (account, asset, input, output) VALUES (p.destination, p.asset, p.amount::bigint, 0) ON CONFLICT (account, asset) DO UPDATE SET input = p.amount::bigint + ( SELECT input FROM "default".volumes WHERE account = p.destination AND asset = p.asset ); END LOOP; RETURN NULL; END $$; - - --- --- Name: handle_log_entry(); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".handle_log_entry() RETURNS trigger - LANGUAGE plpgsql - AS $$ BEGIN if NEW.type = 'NEW_TRANSACTION' THEN INSERT INTO "default".transactions(id, timestamp, reference, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES ( (NEW.data ->> 'txid')::bigint, (NEW.data ->> 'timestamp')::varchar, CASE WHEN (NEW.data ->> 'reference')::varchar = '' THEN NULL ELSE (NEW.data ->> 'reference')::varchar END, (NEW.data ->> 'postings')::jsonb, CASE WHEN (NEW.data ->> 'metadata')::jsonb IS NULL THEN '{}' ELSE (NEW.data ->> 'metadata')::jsonb END, (NEW.data ->> 'preCommitVolumes')::jsonb, (NEW.data ->> 'postCommitVolumes')::jsonb ); END IF; if NEW.type = 'SET_METADATA' THEN if NEW.data ->> 'targetType' = 'TRANSACTION' THEN UPDATE "default".transactions SET metadata = metadata || (NEW.data ->> 'metadata')::jsonb WHERE id = (NEW.data ->> 'targetId')::bigint; END IF; if NEW.data ->> 'targetType' = 'ACCOUNT' THEN INSERT INTO "default".accounts (address, metadata) VALUES ((NEW.data ->> 'targetId')::varchar, (NEW.data ->> 'metadata')::jsonb) ON CONFLICT (address) DO UPDATE SET metadata = accounts.metadata || (NEW.data ->> 'metadata')::jsonb; END IF; END IF; RETURN NEW; END; $$; - - --- --- Name: is_valid_json(text); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".is_valid_json(p_json text) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN RETURN (p_json::jsonb IS NOT NULL); EXCEPTION WHEN others THEN RETURN false; END; $$; - - --- --- Name: meta_compare(jsonb, boolean, text[]); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".meta_compare(metadata jsonb, value boolean, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path(metadata, variadic path)::bool = value::bool; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: meta_compare(jsonb, numeric, text[]); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".meta_compare(metadata jsonb, value numeric, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path(metadata, variadic path)::numeric = value::numeric; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: meta_compare(jsonb, character varying, text[]); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".meta_compare(metadata jsonb, value character varying, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path_text(metadata, variadic path)::varchar = value::varchar; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: normaliz(jsonb); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".normaliz(v jsonb) RETURNS text - LANGUAGE plpgsql - AS $$ DECLARE r record; t jsonb; BEGIN if jsonb_typeof(v) = 'object' then return ( SELECT COALESCE('{' || string_agg(keyValue, ',') || '}', '{}') FROM ( SELECT '"' || key || '":' || value as keyValue FROM ( SELECT key, (CASE WHEN "default".is_valid_json((select v ->> key)) THEN (select "default".normaliz((select v ->> key)::jsonb)) ELSE '"' || (select v ->> key) || '"' END) as value FROM ( SELECT jsonb_object_keys(v) as key ) t order by key ) t ) t ); end if; if jsonb_typeof(v) = 'array' then return ( select COALESCE('[' || string_agg(items, ',') || ']', '[]') from ( select "default".normaliz(item) as items from jsonb_array_elements(v) item ) t ); end if; if jsonb_typeof(v) = 'string' then return v::text; end if; if jsonb_typeof(v) = 'number' then return v::bigint; end if; if jsonb_typeof(v) = 'boolean' then return v::boolean; end if; return ''; END $$; - --- --- Name: use_account(jsonb, character varying); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".use_account(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $$ SELECT bool_or(v.value) from ( SELECT "default".use_account_as_source(postings, account) AS value UNION SELECT "default".use_account_as_destination(postings, account) AS value ) v $$; - --- --- Name: use_account_as_destination(jsonb, character varying); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".use_account_as_destination(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $_$ select bool_or(v.value::bool) from ( select jsonb_extract_path_text(jsonb_array_elements(postings), 'destination') ~ ('^' || account || '$') as value) as v; $_$; - --- --- Name: use_account_as_source(jsonb, character varying); Type: FUNCTION; Schema: default --- - -CREATE FUNCTION "default".use_account_as_source(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $_$ select bool_or(v.value::bool) from ( select jsonb_extract_path_text(jsonb_array_elements(postings), 'source') ~ ('^' || account || '$') as value) as v; $_$; - --- --- Name: compute_hashes(); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".compute_hashes() RETURNS void - LANGUAGE plpgsql - AS $$ DECLARE r record; BEGIN /* Create JSON object manually as it needs to be in canonical form */ FOR r IN (select id, '{"data":' || "wallets-002".normaliz(data::jsonb) || ',"date":"' || to_char (date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') || '","hash":"","id":' || id || ',"type":"' || type || '"}' as canonical from "wallets-002".log) LOOP UPDATE "wallets-002".log set hash = (select encode(digest( COALESCE((select '{"data":' || "wallets-002".normaliz(data::jsonb) || ',"date":"' || to_char (date at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') || '","hash":"' || hash || '","id":' || id || ',"type":"' || type || '"}' from "wallets-002".log where id = r.id - 1), 'null') || r.canonical, 'sha256' ), 'hex')) WHERE id = r.id; END LOOP; END $$; - --- --- Name: compute_volumes(); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".compute_volumes() RETURNS trigger - LANGUAGE plpgsql - AS $$ DECLARE p record; BEGIN FOR p IN ( SELECT t.postings->>'source' as source, t.postings->>'asset' as asset, sum ((t.postings->>'amount')::bigint) as amount FROM ( SELECT jsonb_array_elements(((newtable.data::jsonb)->>'postings')::jsonb) as postings FROM newtable WHERE newtable.type = 'NEW_TRANSACTION' ) t GROUP BY source, asset ) LOOP INSERT INTO "wallets-002".accounts (address, metadata) VALUES (p.source, '{}') ON CONFLICT DO NOTHING; INSERT INTO "wallets-002".volumes (account, asset, input, output) VALUES (p.source, p.asset, 0, p.amount::bigint) ON CONFLICT (account, asset) DO UPDATE SET output = p.amount::bigint + ( SELECT output FROM "wallets-002".volumes WHERE account = p.source AND asset = p.asset ); END LOOP; FOR p IN ( SELECT t.postings->>'destination' as destination, t.postings->>'asset' as asset, sum ((t.postings->>'amount')::bigint) as amount FROM ( SELECT jsonb_array_elements(((newtable.data::jsonb)->>'postings')::jsonb) as postings FROM newtable WHERE newtable.type = 'NEW_TRANSACTION' ) t GROUP BY destination, asset ) LOOP INSERT INTO "wallets-002".accounts (address, metadata) VALUES (p.destination, '{}') ON CONFLICT DO NOTHING; INSERT INTO "wallets-002".volumes (account, asset, input, output) VALUES (p.destination, p.asset, p.amount::bigint, 0) ON CONFLICT (account, asset) DO UPDATE SET input = p.amount::bigint + ( SELECT input FROM "wallets-002".volumes WHERE account = p.destination AND asset = p.asset ); END LOOP; RETURN NULL; END $$; - --- --- Name: handle_log_entry(); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".handle_log_entry() RETURNS trigger - LANGUAGE plpgsql - AS $$ BEGIN if NEW.type = 'NEW_TRANSACTION' THEN INSERT INTO "wallets-002".transactions(id, timestamp, reference, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES ( (NEW.data ->> 'txid')::bigint, (NEW.data ->> 'timestamp')::varchar, CASE WHEN (NEW.data ->> 'reference')::varchar = '' THEN NULL ELSE (NEW.data ->> 'reference')::varchar END, (NEW.data ->> 'postings')::jsonb, CASE WHEN (NEW.data ->> 'metadata')::jsonb IS NULL THEN '{}' ELSE (NEW.data ->> 'metadata')::jsonb END, (NEW.data ->> 'preCommitVolumes')::jsonb, (NEW.data ->> 'postCommitVolumes')::jsonb ); END IF; if NEW.type = 'SET_METADATA' THEN if NEW.data ->> 'targetType' = 'TRANSACTION' THEN UPDATE "wallets-002".transactions SET metadata = metadata || (NEW.data ->> 'metadata')::jsonb WHERE id = (NEW.data ->> 'targetId')::bigint; END IF; if NEW.data ->> 'targetType' = 'ACCOUNT' THEN INSERT INTO "wallets-002".accounts (address, metadata) VALUES ((NEW.data ->> 'targetId')::varchar, (NEW.data ->> 'metadata')::jsonb) ON CONFLICT (address) DO UPDATE SET metadata = accounts.metadata || (NEW.data ->> 'metadata')::jsonb; END IF; END IF; RETURN NEW; END; $$; - --- --- Name: is_valid_json(text); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".is_valid_json(p_json text) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN RETURN (p_json::jsonb IS NOT NULL); EXCEPTION WHEN others THEN RETURN false; END; $$; - --- --- Name: meta_compare(jsonb, boolean, text[]); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".meta_compare(metadata jsonb, value boolean, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path(metadata, variadic path)::bool = value::bool; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: meta_compare(jsonb, numeric, text[]); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".meta_compare(metadata jsonb, value numeric, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path(metadata, variadic path)::numeric = value::numeric; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: meta_compare(jsonb, character varying, text[]); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".meta_compare(metadata jsonb, value character varying, VARIADIC path text[]) RETURNS boolean - LANGUAGE plpgsql IMMUTABLE - AS $$ BEGIN return jsonb_extract_path_text(metadata, variadic path)::varchar = value::varchar; EXCEPTION WHEN others THEN RAISE INFO 'Error Name: %', SQLERRM; RAISE INFO 'Error State: %', SQLSTATE; RETURN false; END $$; - --- --- Name: normaliz(jsonb); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".normaliz(v jsonb) RETURNS text - LANGUAGE plpgsql - AS $$ DECLARE r record; t jsonb; BEGIN if jsonb_typeof(v) = 'object' then return ( SELECT COALESCE('{' || string_agg(keyValue, ',') || '}', '{}') FROM ( SELECT '"' || key || '":' || value as keyValue FROM ( SELECT key, (CASE WHEN "wallets-002".is_valid_json((select v ->> key)) THEN (select "wallets-002".normaliz((select v ->> key)::jsonb)) ELSE '"' || (select v ->> key) || '"' END) as value FROM ( SELECT jsonb_object_keys(v) as key ) t order by key ) t ) t ); end if; if jsonb_typeof(v) = 'array' then return ( select COALESCE('[' || string_agg(items, ',') || ']', '[]') from ( select "wallets-002".normaliz(item) as items from jsonb_array_elements(v) item ) t ); end if; if jsonb_typeof(v) = 'string' then return v::text; end if; if jsonb_typeof(v) = 'number' then return v::bigint; end if; if jsonb_typeof(v) = 'boolean' then return v::boolean; end if; return ''; END $$; - --- --- Name: use_account(jsonb, character varying); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".use_account(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $$ SELECT bool_or(v.value) from ( SELECT "wallets-002".use_account_as_source(postings, account) AS value UNION SELECT "wallets-002".use_account_as_destination(postings, account) AS value ) v $$; - --- --- Name: use_account_as_destination(jsonb, character varying); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".use_account_as_destination(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $_$ select bool_or(v.value::bool) from ( select jsonb_extract_path_text(jsonb_array_elements(postings), 'destination') ~ ('^' || account || '$') as value) as v; $_$; - --- --- Name: use_account_as_source(jsonb, character varying); Type: FUNCTION; Schema: wallets-002 --- - -CREATE FUNCTION "wallets-002".use_account_as_source(postings jsonb, account character varying) RETURNS boolean - LANGUAGE sql - AS $_$ select bool_or(v.value::bool) from ( select jsonb_extract_path_text(jsonb_array_elements(postings), 'source') ~ ('^' || account || '$') as value) as v; $_$; - -SET default_tablespace = ''; - -SET default_table_access_method = heap; - --- --- Name: configuration; Type: TABLE; Schema: _system --- - -CREATE TABLE _system.configuration ( - key character varying(255) NOT NULL, - value text, - addedat timestamp without time zone -); - --- --- Name: ledgers; Type: TABLE; Schema: _system --- - -CREATE TABLE _system.ledgers ( - ledger character varying(255) NOT NULL, - addedat timestamp without time zone -); - --- --- Name: accounts; Type: TABLE; Schema: default --- - -CREATE TABLE "default".accounts ( - address character varying NOT NULL, - metadata jsonb DEFAULT '{}'::jsonb, - address_json jsonb -); - --- --- Name: idempotency; Type: TABLE; Schema: default --- - -CREATE TABLE "default".idempotency ( - key character varying NOT NULL, - date character varying, - status_code integer, - headers character varying, - body character varying, - request_hash character varying -); - --- --- Name: log; Type: TABLE; Schema: default --- - -CREATE TABLE "default".log ( - id bigint, - type character varying, - hash character varying, - date timestamp with time zone, - data jsonb -); - --- --- Name: log_seq; Type: SEQUENCE; Schema: default --- - -CREATE SEQUENCE "default".log_seq - START WITH 0 - INCREMENT BY 1 - MINVALUE 0 - NO MAXVALUE - CACHE 1; - --- --- Name: mapping; Type: TABLE; Schema: default --- - -CREATE TABLE "default".mapping ( - mapping_id character varying, - mapping character varying -); - --- --- Name: migrations; Type: TABLE; Schema: default --- - -CREATE TABLE "default".migrations ( - version character varying, - date character varying -); - --- --- Name: postings; Type: TABLE; Schema: default --- - -CREATE TABLE "default".postings ( - txid bigint, - posting_index integer, - source jsonb, - destination jsonb -); - --- --- Name: transactions; Type: TABLE; Schema: default --- - -CREATE TABLE "default".transactions ( - id bigint, - "timestamp" timestamp with time zone, - reference character varying, - hash character varying, - postings jsonb, - metadata jsonb DEFAULT '{}'::jsonb, - pre_commit_volumes jsonb, - post_commit_volumes jsonb -); - --- --- Name: volumes; Type: TABLE; Schema: default --- - -CREATE TABLE "default".volumes ( - account character varying, - asset character varying, - input numeric, - output numeric, - account_json jsonb -); - --- --- Name: accounts; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".accounts ( - address character varying NOT NULL, - metadata jsonb DEFAULT '{}'::jsonb, - address_json jsonb -); - --- --- Name: idempotency; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".idempotency ( - key character varying NOT NULL, - date character varying, - status_code integer, - headers character varying, - body character varying, - request_hash character varying -); - --- --- Name: log; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".log ( - id bigint, - type character varying, - hash character varying, - date timestamp with time zone, - data jsonb -); - --- --- Name: log_seq; Type: SEQUENCE; Schema: wallets-002 --- - -CREATE SEQUENCE "wallets-002".log_seq - START WITH 0 - INCREMENT BY 1 - MINVALUE 0 - NO MAXVALUE - CACHE 1; - --- --- Name: mapping; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".mapping ( - mapping_id character varying, - mapping character varying -); - --- --- Name: migrations; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".migrations ( - version character varying, - date character varying -); - --- --- Name: postings; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".postings ( - txid bigint, - posting_index integer, - source jsonb, - destination jsonb -); - --- --- Name: transactions; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".transactions ( - id bigint, - "timestamp" timestamp with time zone, - reference character varying, - hash character varying, - postings jsonb, - metadata jsonb DEFAULT '{}'::jsonb, - pre_commit_volumes jsonb, - post_commit_volumes jsonb -); - --- --- Name: volumes; Type: TABLE; Schema: wallets-002 --- - -CREATE TABLE "wallets-002".volumes ( - account character varying, - asset character varying, - input numeric, - output numeric, - account_json jsonb -); - --- --- Data for Name: configuration; Type: TABLE DATA; Schema: _system --- - -INSERT INTO _system.configuration (key, value, addedat) VALUES ('appId', '7f50ba54-cdb1-4e79-a2f7-3e704ce08d08', '2023-12-13 18:16:31'); - - --- --- Data for Name: ledgers; Type: TABLE DATA; Schema: _system --- - -INSERT INTO _system.ledgers (ledger, addedat) VALUES ('wallets-002', '2023-12-13 18:16:35.943038'); -INSERT INTO _system.ledgers (ledger, addedat) VALUES ('default', '2023-12-13 18:21:05.044237'); - - --- --- Data for Name: accounts; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".accounts (address, metadata, address_json) VALUES ('world', '{}', '["world"]'); -INSERT INTO "default".accounts (address, metadata, address_json) VALUES ('bank', '{}', '["bank"]'); -INSERT INTO "default".accounts (address, metadata, address_json) VALUES ('bob', '{}', '["bob"]'); -INSERT INTO "default".accounts (address, metadata, address_json) VALUES ('alice', '{"foo": "bar"}', '["alice"]'); - - --- --- Data for Name: idempotency; Type: TABLE DATA; Schema: default --- - - - --- --- Data for Name: log; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".log (id, type, hash, date, data) VALUES (0, 'NEW_TRANSACTION', '79fc36b46f2668ee1f682a109765af8e849d11715d078bd361e7b4eb61fadc70', '2023-12-13 18:21:05+00', '{"txid": 0, "metadata": {}, "postings": [{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "bank"}], "reference": "", "timestamp": "2023-12-13T18:21:05Z"}'); -INSERT INTO "default".log (id, type, hash, date, data) VALUES (1, 'NEW_TRANSACTION', 'e493bab4fcce0c281193414ea43a7d34b73c89ac1bb103755e9fb1064d00c0e8', '2023-12-13 18:21:40+00', '{"txid": 1, "metadata": {}, "postings": [{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "bob"}], "reference": "", "timestamp": "2023-12-13T18:21:40Z"}'); -INSERT INTO "default".log (id, type, hash, date, data) VALUES (2, 'NEW_TRANSACTION', '19ac0ffff69a271615ba09c6564f3851ab0fe32e7aabe3ab9083b63501f29332', '2023-12-13 18:21:46+00', '{"txid": 2, "metadata": {}, "postings": [{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "alice"}], "reference": "", "timestamp": "2023-12-13T18:21:46Z"}'); -INSERT INTO "default".log (id, type, hash, date, data) VALUES (3, 'SET_METADATA', '839800b3bf685903b37240e8a59e1872d29c2ed9715a79c56b86edb5b5b0976f', '2023-12-14 09:30:31+00', '{"metadata": {"foo": "bar"}, "targetId": "alice", "targetType": "ACCOUNT"}'); - - --- --- Data for Name: mapping; Type: TABLE DATA; Schema: default --- - - - --- --- Data for Name: migrations; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".migrations (version, date) VALUES ('0', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('1', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('2', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('3', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('4', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('5', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('6', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('7', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('8', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('9', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('10', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('11', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('12', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('13', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('14', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('15', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('16', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('17', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('18', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('19', '2023-12-13T18:21:05Z'); -INSERT INTO "default".migrations (version, date) VALUES ('20', '2023-12-13T18:21:05Z'); - - --- --- Data for Name: postings; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".postings (txid, posting_index, source, destination) VALUES (0, 0, '["world"]', '["bank"]'); -INSERT INTO "default".postings (txid, posting_index, source, destination) VALUES (1, 0, '["world"]', '["bob"]'); -INSERT INTO "default".postings (txid, posting_index, source, destination) VALUES (2, 0, '["world"]', '["alice"]'); - - --- --- Data for Name: transactions; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".transactions (id, "timestamp", reference, hash, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES (0, '2023-12-13 18:21:05+00', NULL, NULL, '[{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "bank"}]', '{}', '{"bank": {"USD/2": {"input": 0, "output": 0, "balance": 0}}, "world": {"USD/2": {"input": 0, "output": 0, "balance": 0}}}', '{"bank": {"USD/2": {"input": 10000, "output": 0, "balance": 10000}}, "world": {"USD/2": {"input": 0, "output": 10000, "balance": -10000}}}'); -INSERT INTO "default".transactions (id, "timestamp", reference, hash, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES (1, '2023-12-13 18:21:40+00', NULL, NULL, '[{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "bob"}]', '{}', '{"bob": {"USD/2": {"input": 0, "output": 0, "balance": 0}}, "world": {"USD/2": {"input": 0, "output": 10000, "balance": -10000}}}', '{"bob": {"USD/2": {"input": 10000, "output": 0, "balance": 10000}}, "world": {"USD/2": {"input": 0, "output": 20000, "balance": -20000}}}'); -INSERT INTO "default".transactions (id, "timestamp", reference, hash, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES (2, '2023-12-13 18:21:46+00', NULL, NULL, '[{"asset": "USD/2", "amount": 10000, "source": "world", "destination": "alice"}]', '{}', '{"alice": {"USD/2": {"input": 0, "output": 0, "balance": 0}}, "world": {"USD/2": {"input": 0, "output": 20000, "balance": -20000}}}', '{"alice": {"USD/2": {"input": 10000, "output": 0, "balance": 10000}}, "world": {"USD/2": {"input": 0, "output": 30000, "balance": -30000}}}'); - - --- --- Data for Name: volumes; Type: TABLE DATA; Schema: default --- - -INSERT INTO "default".volumes (account, asset, input, output, account_json) VALUES ('bank', 'USD/2', 10000, 0, '["bank"]'); -INSERT INTO "default".volumes (account, asset, input, output, account_json) VALUES ('bob', 'USD/2', 10000, 0, '["bob"]'); -INSERT INTO "default".volumes (account, asset, input, output, account_json) VALUES ('alice', 'USD/2', 10000, 0, '["alice"]'); -INSERT INTO "default".volumes (account, asset, input, output, account_json) VALUES ('world', 'USD/2', 0, 30000, '["world"]'); - - --- --- Data for Name: accounts; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".accounts (address, metadata, address_json) VALUES ('wallets:15b7a366c6e9473f96276803ef585ae9:main', '{"wallets/id": "15b7a366-c6e9-473f-9627-6803ef585ae9", "wallets/name": "wallet1", "wallets/balances": "true", "wallets/createdAt": "2023-12-14T09:30:48.01540488Z", "wallets/spec/type": "wallets.primary", "wallets/custom_data": {}, "wallets/balances/name": "main"}', '["wallets", "15b7a366c6e9473f96276803ef585ae9", "main"]'); -INSERT INTO "wallets-002".accounts (address, metadata, address_json) VALUES ('world', '{}', '["world"]'); -INSERT INTO "wallets-002".accounts (address, metadata, address_json) VALUES ('wallets:71e6788ad1954139bec5c3e35ee4a2dc:main', '{"wallets/id": "71e6788a-d195-4139-bec5-c3e35ee4a2dc", "wallets/name": "wallet2", "wallets/balances": "true", "wallets/createdAt": "2023-12-14T09:32:38.001913219Z", "wallets/spec/type": "wallets.primary", "wallets/custom_data": {"catgory": "gold"}, "wallets/balances/name": "main"}', '["wallets", "71e6788ad1954139bec5c3e35ee4a2dc", "main"]'); - - --- --- Data for Name: idempotency; Type: TABLE DATA; Schema: wallets-002 --- - - - --- --- Data for Name: log; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".log (id, type, hash, date, data) VALUES (0, 'SET_METADATA', 'c3d4b844838f4feaf0d35f1f37f8eae496b66328a69fc3d73e46a7cd53b231b6', '2023-12-14 09:30:48+00', '{"metadata": {"wallets/id": "15b7a366-c6e9-473f-9627-6803ef585ae9", "wallets/name": "wallet1", "wallets/balances": "true", "wallets/createdAt": "2023-12-14T09:30:48.01540488Z", "wallets/spec/type": "wallets.primary", "wallets/custom_data": {}, "wallets/balances/name": "main"}, "targetId": "wallets:15b7a366c6e9473f96276803ef585ae9:main", "targetType": "ACCOUNT"}'); -INSERT INTO "wallets-002".log (id, type, hash, date, data) VALUES (1, 'NEW_TRANSACTION', '1f2d8e75e937cee1c91e0a2696f5fbe59947d77ad568cf45c58a01430acb5f0b', '2023-12-14 09:32:04+00', '{"txid": 0, "metadata": {"wallets/custom_data": {}, "wallets/transaction": "true"}, "postings": [{"asset": "USD/2", "amount": 100, "source": "world", "destination": "wallets:15b7a366c6e9473f96276803ef585ae9:main"}], "reference": "", "timestamp": "2023-12-14T09:32:04Z"}'); -INSERT INTO "wallets-002".log (id, type, hash, date, data) VALUES (2, 'SET_METADATA', '3665750bbbe64e79c4631927e9399a8c7f817b55d572ef41cfd9714bd679db7d', '2023-12-14 09:32:38+00', '{"metadata": {"wallets/id": "71e6788a-d195-4139-bec5-c3e35ee4a2dc", "wallets/name": "wallet2", "wallets/balances": "true", "wallets/createdAt": "2023-12-14T09:32:38.001913219Z", "wallets/spec/type": "wallets.primary", "wallets/custom_data": {"catgory": "gold"}, "wallets/balances/name": "main"}, "targetId": "wallets:71e6788ad1954139bec5c3e35ee4a2dc:main", "targetType": "ACCOUNT"}'); - - --- --- Data for Name: mapping; Type: TABLE DATA; Schema: wallets-002 --- - - - --- --- Data for Name: migrations; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".migrations (version, date) VALUES ('0', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('1', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('2', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('3', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('4', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('5', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('6', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('7', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('8', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('9', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('10', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('11', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('12', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('13', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('14', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('15', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('16', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('17', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('18', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('19', '2023-12-13T18:16:36Z'); -INSERT INTO "wallets-002".migrations (version, date) VALUES ('20', '2023-12-13T18:16:36Z'); - - --- --- Data for Name: postings; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".postings (txid, posting_index, source, destination) VALUES (0, 0, '["world"]', '["wallets", "15b7a366c6e9473f96276803ef585ae9", "main"]'); - - --- --- Data for Name: transactions; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".transactions (id, "timestamp", reference, hash, postings, metadata, pre_commit_volumes, post_commit_volumes) VALUES (0, '2023-12-14 09:32:04+00', NULL, NULL, '[{"asset": "USD/2", "amount": 100, "source": "world", "destination": "wallets:15b7a366c6e9473f96276803ef585ae9:main"}]', '{"wallets/custom_data": {}, "wallets/transaction": "true"}', '{"world": {"USD/2": {"input": 0, "output": 0, "balance": 0}}, "wallets:15b7a366c6e9473f96276803ef585ae9:main": {"USD/2": {"input": 0, "output": 0, "balance": 0}}}', '{"world": {"USD/2": {"input": 0, "output": 100, "balance": -100}}, "wallets:15b7a366c6e9473f96276803ef585ae9:main": {"USD/2": {"input": 100, "output": 0, "balance": 100}}}'); - - --- --- Data for Name: volumes; Type: TABLE DATA; Schema: wallets-002 --- - -INSERT INTO "wallets-002".volumes (account, asset, input, output, account_json) VALUES ('world', 'USD/2', 0, 100, '["world"]'); -INSERT INTO "wallets-002".volumes (account, asset, input, output, account_json) VALUES ('wallets:15b7a366c6e9473f96276803ef585ae9:main', 'USD/2', 100, 0, '["wallets", "15b7a366c6e9473f96276803ef585ae9", "main"]'); - - --- --- Name: log_seq; Type: SEQUENCE SET; Schema: default --- - -SELECT pg_catalog.setval('"default".log_seq', 0, false); - - --- --- Name: log_seq; Type: SEQUENCE SET; Schema: wallets-002 --- - -SELECT pg_catalog.setval('"wallets-002".log_seq', 0, false); - - --- --- Name: configuration configuration_pkey; Type: CONSTRAINT; Schema: _system --- - -ALTER TABLE ONLY _system.configuration - ADD CONSTRAINT configuration_pkey PRIMARY KEY (key); - - --- --- Name: ledgers ledgers_pkey; Type: CONSTRAINT; Schema: _system --- - -ALTER TABLE ONLY _system.ledgers - ADD CONSTRAINT ledgers_pkey PRIMARY KEY (ledger); - - --- --- Name: accounts accounts_address_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".accounts - ADD CONSTRAINT accounts_address_key UNIQUE (address); - - --- --- Name: idempotency idempotency_pkey; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".idempotency - ADD CONSTRAINT idempotency_pkey PRIMARY KEY (key); - - --- --- Name: log log_id_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".log - ADD CONSTRAINT log_id_key UNIQUE (id); - - --- --- Name: mapping mapping_mapping_id_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".mapping - ADD CONSTRAINT mapping_mapping_id_key UNIQUE (mapping_id); - - --- --- Name: migrations migrations_version_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".migrations - ADD CONSTRAINT migrations_version_key UNIQUE (version); - - --- --- Name: transactions transactions_id_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".transactions - ADD CONSTRAINT transactions_id_key UNIQUE (id); - - --- --- Name: transactions transactions_reference_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".transactions - ADD CONSTRAINT transactions_reference_key UNIQUE (reference); - - --- --- Name: volumes volumes_account_asset_key; Type: CONSTRAINT; Schema: default --- - -ALTER TABLE ONLY "default".volumes - ADD CONSTRAINT volumes_account_asset_key UNIQUE (account, asset); - - --- --- Name: accounts accounts_address_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".accounts - ADD CONSTRAINT accounts_address_key UNIQUE (address); - - --- --- Name: idempotency idempotency_pkey; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".idempotency - ADD CONSTRAINT idempotency_pkey PRIMARY KEY (key); - - --- --- Name: log log_id_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".log - ADD CONSTRAINT log_id_key UNIQUE (id); - - --- --- Name: mapping mapping_mapping_id_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".mapping - ADD CONSTRAINT mapping_mapping_id_key UNIQUE (mapping_id); - - --- --- Name: migrations migrations_version_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".migrations - ADD CONSTRAINT migrations_version_key UNIQUE (version); - - --- --- Name: transactions transactions_id_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".transactions - ADD CONSTRAINT transactions_id_key UNIQUE (id); - - --- --- Name: transactions transactions_reference_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".transactions - ADD CONSTRAINT transactions_reference_key UNIQUE (reference); - - --- --- Name: volumes volumes_account_asset_key; Type: CONSTRAINT; Schema: wallets-002 --- - -ALTER TABLE ONLY "wallets-002".volumes - ADD CONSTRAINT volumes_account_asset_key UNIQUE (account, asset); - - --- --- Name: accounts_address_json; Type: INDEX; Schema: default --- - -CREATE INDEX accounts_address_json ON "default".accounts USING gin (address_json); - - --- --- Name: accounts_array_length; Type: INDEX; Schema: default --- - -CREATE INDEX accounts_array_length ON "default".accounts USING btree (jsonb_array_length(address_json)); - - --- --- Name: postings_addresses; Type: INDEX; Schema: default --- - -CREATE INDEX postings_addresses ON "default".transactions USING gin (postings); - - --- --- Name: postings_array_length_dst; Type: INDEX; Schema: default --- - -CREATE INDEX postings_array_length_dst ON "default".postings USING btree (jsonb_array_length(destination)); - - --- --- Name: postings_array_length_src; Type: INDEX; Schema: default --- - -CREATE INDEX postings_array_length_src ON "default".postings USING btree (jsonb_array_length(source)); - - --- --- Name: postings_dest; Type: INDEX; Schema: default --- - -CREATE INDEX postings_dest ON "default".postings USING gin (destination); - - --- --- Name: postings_src; Type: INDEX; Schema: default --- - -CREATE INDEX postings_src ON "default".postings USING gin (source); - - --- --- Name: postings_txid; Type: INDEX; Schema: default --- - -CREATE INDEX postings_txid ON "default".postings USING btree (txid); - - --- --- Name: volumes_account_json; Type: INDEX; Schema: default --- - -CREATE INDEX volumes_account_json ON "default".volumes USING gin (account_json); - - --- --- Name: volumes_array_length; Type: INDEX; Schema: default --- - -CREATE INDEX volumes_array_length ON "default".volumes USING btree (jsonb_array_length(account_json)); - - --- --- Name: accounts_address_json; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX accounts_address_json ON "wallets-002".accounts USING gin (address_json); - - --- --- Name: accounts_array_length; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX accounts_array_length ON "wallets-002".accounts USING btree (jsonb_array_length(address_json)); - - --- --- Name: postings_addresses; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_addresses ON "wallets-002".transactions USING gin (postings); - - --- --- Name: postings_array_length_dst; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_array_length_dst ON "wallets-002".postings USING btree (jsonb_array_length(destination)); - - --- --- Name: postings_array_length_src; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_array_length_src ON "wallets-002".postings USING btree (jsonb_array_length(source)); - - --- --- Name: postings_dest; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_dest ON "wallets-002".postings USING gin (destination); - - --- --- Name: postings_src; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_src ON "wallets-002".postings USING gin (source); - - --- --- Name: postings_txid; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX postings_txid ON "wallets-002".postings USING btree (txid); - - --- --- Name: volumes_account_json; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX volumes_account_json ON "wallets-002".volumes USING gin (account_json); - - --- --- Name: volumes_array_length; Type: INDEX; Schema: wallets-002 --- - -CREATE INDEX volumes_array_length ON "wallets-002".volumes USING btree (jsonb_array_length(account_json)); - - --- --- PostgreSQL database dump complete --- - diff --git a/components/ledger/internal/testing/compare.go b/components/ledger/internal/testing/compare.go deleted file mode 100644 index 0e978c8c2e..0000000000 --- a/components/ledger/internal/testing/compare.go +++ /dev/null @@ -1,20 +0,0 @@ -package testing - -import ( - "math/big" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/require" -) - -func bigIntComparer(v1 *big.Int, v2 *big.Int) bool { - return v1.String() == v2.String() -} - -func RequireEqual(t *testing.T, expected, actual any) { - t.Helper() - if diff := cmp.Diff(expected, actual, cmp.Comparer(bigIntComparer)); diff != "" { - require.Failf(t, "Content not matching", diff) - } -} diff --git a/components/ledger/internal/transaction.go b/components/ledger/internal/transaction.go deleted file mode 100644 index 597cb45656..0000000000 --- a/components/ledger/internal/transaction.go +++ /dev/null @@ -1,155 +0,0 @@ -package ledger - -import ( - "math/big" - - "github.com/formancehq/go-libs/time" - - "github.com/formancehq/go-libs/pointer" - - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/metadata" -) - -var ( - ErrNoPostings = errors.New("invalid payload: should contain either postings or script") -) - -type Transactions struct { - Transactions []TransactionData `json:"transactions"` -} - -type TransactionData struct { - Postings Postings `json:"postings"` - Metadata metadata.Metadata `json:"metadata"` - Timestamp time.Time `json:"timestamp"` - Reference string `json:"reference,omitempty"` -} - -func (d TransactionData) WithPostings(postings ...Posting) TransactionData { - d.Postings = append(d.Postings, postings...) - return d -} - -func NewTransactionData() TransactionData { - return TransactionData{ - Metadata: metadata.Metadata{}, - } -} - -func (t *TransactionData) Reverse() TransactionData { - postings := make(Postings, len(t.Postings)) - copy(postings, t.Postings) - postings.Reverse() - - return TransactionData{ - Postings: postings, - } -} - -func (d TransactionData) WithDate(now time.Time) TransactionData { - d.Timestamp = now - - return d -} - -type Transaction struct { - TransactionData - ID *big.Int `json:"id"` - Reverted bool `json:"reverted"` -} - -func (t *Transaction) WithPostings(postings ...Posting) *Transaction { - t.TransactionData = t.TransactionData.WithPostings(postings...) - return t -} - -func (t *Transaction) WithReference(ref string) *Transaction { - t.Reference = ref - return t -} - -func (t *Transaction) WithDate(ts time.Time) *Transaction { - t.Timestamp = ts - return t -} - -func (t *Transaction) WithIDUint64(id uint64) *Transaction { - t.ID = big.NewInt(int64(id)) - return t -} - -func (t *Transaction) WithID(id *big.Int) *Transaction { - t.ID = id - return t -} - -func (t *Transaction) WithMetadata(m metadata.Metadata) *Transaction { - t.Metadata = m - return t -} - -func NewTransaction() *Transaction { - return &Transaction{ - ID: big.NewInt(0), - TransactionData: NewTransactionData(). - WithDate(time.Now()), - } -} - -type ExpandedTransaction struct { - Transaction - PreCommitVolumes AccountsAssetsVolumes `json:"preCommitVolumes,omitempty"` - PostCommitVolumes AccountsAssetsVolumes `json:"postCommitVolumes,omitempty"` - PreCommitEffectiveVolumes AccountsAssetsVolumes `json:"preCommitEffectiveVolumes,omitempty"` - PostCommitEffectiveVolumes AccountsAssetsVolumes `json:"postCommitEffectiveVolumes,omitempty"` -} - -func (t *ExpandedTransaction) AppendPosting(p Posting) { - t.Postings = append(t.Postings, p) -} - -func ExpandTransaction(tx *Transaction, preCommitVolumes AccountsAssetsVolumes) ExpandedTransaction { - postCommitVolumes := preCommitVolumes.Copy() - for _, posting := range tx.Postings { - preCommitVolumes.AddInput(posting.Destination, posting.Asset, Zero) - preCommitVolumes.AddOutput(posting.Source, posting.Asset, Zero) - postCommitVolumes.AddOutput(posting.Source, posting.Asset, posting.Amount) - postCommitVolumes.AddInput(posting.Destination, posting.Asset, posting.Amount) - } - return ExpandedTransaction{ - Transaction: *tx, - PreCommitVolumes: preCommitVolumes, - PostCommitVolumes: postCommitVolumes, - } -} - -type TransactionRequest struct { - Postings Postings `json:"postings"` - Script ScriptV1 `json:"script"` - Timestamp time.Time `json:"timestamp"` - Reference string `json:"reference"` - Metadata metadata.Metadata `json:"metadata" swaggertype:"object"` -} - -func (req *TransactionRequest) ToRunScript() *RunScript { - - if len(req.Postings) > 0 { - txData := TransactionData{ - Postings: req.Postings, - Timestamp: req.Timestamp, - Reference: req.Reference, - Metadata: req.Metadata, - } - - return pointer.For(TxToScriptData(txData, false)) - } - - return &RunScript{ - Script: req.Script.ToCore(), - Timestamp: req.Timestamp, - Reference: req.Reference, - Metadata: req.Metadata, - } -} diff --git a/components/ledger/internal/transaction_test.go b/components/ledger/internal/transaction_test.go deleted file mode 100644 index 7a98cb0224..0000000000 --- a/components/ledger/internal/transaction_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package ledger - -import ( - "math/big" - "testing" - - "github.com/formancehq/go-libs/metadata" - "github.com/stretchr/testify/require" -) - -func TestReverseTransaction(t *testing.T) { - t.Run("1 posting", func(t *testing.T) { - tx := &ExpandedTransaction{ - Transaction: Transaction{ - TransactionData: TransactionData{ - Postings: Postings{ - { - Source: "world", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - Reference: "foo", - }, - }, - } - - expected := TransactionData{ - Postings: Postings{ - { - Source: "users:001", - Destination: "world", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - } - require.Equal(t, expected, tx.Reverse()) - }) - - t.Run("2 postings", func(t *testing.T) { - tx := &ExpandedTransaction{ - Transaction: Transaction{ - TransactionData: TransactionData{ - Postings: Postings{ - { - Source: "world", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "payments:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - Reference: "foo", - }, - }, - } - - expected := TransactionData{ - Postings: Postings{ - { - Source: "payments:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "world", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - } - require.Equal(t, expected, tx.Reverse()) - }) - - t.Run("3 postings", func(t *testing.T) { - tx := &ExpandedTransaction{ - Transaction: Transaction{ - TransactionData: TransactionData{ - Postings: Postings{ - { - Source: "world", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "payments:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "payments:001", - Destination: "alice", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - Reference: "foo", - }, - }, - } - - expected := TransactionData{ - Postings: Postings{ - { - Source: "alice", - Destination: "payments:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "payments:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "COIN", - }, - { - Source: "users:001", - Destination: "world", - Amount: big.NewInt(100), - Asset: "COIN", - }, - }, - } - require.Equal(t, expected, tx.Reverse()) - }) -} - -func BenchmarkHash(b *testing.B) { - logs := make([]ChainedLog, b.N) - var previous *ChainedLog - for i := 0; i < b.N; i++ { - newLog := NewTransactionLog(NewTransaction().WithPostings( - NewPosting("world", "bank", "USD", big.NewInt(100)), - ), map[string]metadata.Metadata{}).ChainLog(previous) - previous = newLog - logs = append(logs, *newLog) - } - - b.ResetTimer() - for i := 1; i < b.N; i++ { - logs[i].ComputeHash(&logs[i-1]) - } -} diff --git a/components/ledger/internal/volumes.go b/components/ledger/internal/volumes.go deleted file mode 100644 index c26de1546b..0000000000 --- a/components/ledger/internal/volumes.go +++ /dev/null @@ -1,257 +0,0 @@ -package ledger - -import ( - "database/sql/driver" - "encoding/json" - "math/big" -) - -type Volumes struct { - Input *big.Int `json:"input"` - Output *big.Int `json:"output"` -} - -func (v Volumes) CopyWithZerosIfNeeded() *Volumes { - var input *big.Int - if v.Input == nil { - input = &big.Int{} - } else { - input = new(big.Int).Set(v.Input) - } - var output *big.Int - if v.Output == nil { - output = &big.Int{} - } else { - output = new(big.Int).Set(v.Output) - } - return &Volumes{ - Input: input, - Output: output, - } -} - -func (v Volumes) WithInput(input *big.Int) *Volumes { - v.Input = input - return &v -} - -func (v Volumes) WithInputInt64(value int64) *Volumes { - v.Input = big.NewInt(value) - return &v -} - -func (v Volumes) WithOutput(output *big.Int) *Volumes { - v.Output = output - return &v -} - -func (v Volumes) WithOutputInt64(value int64) *Volumes { - v.Output = big.NewInt(value) - return &v -} - -func NewEmptyVolumes() *Volumes { - return &Volumes{ - Input: new(big.Int), - Output: new(big.Int), - } -} - -func NewVolumesInt64(input, output int64) *Volumes { - return &Volumes{ - Input: big.NewInt(input), - Output: big.NewInt(output), - } -} - -type VolumesWithBalanceByAssetByAccount struct { - Account string `json:"account" bun:"account"` - Asset string `json:"asset" bun:"asset"` - VolumesWithBalance -} - -type VolumesWithBalance struct { - Input *big.Int `json:"input" bun:"input"` - Output *big.Int `json:"output" bun:"output"` - Balance *big.Int `json:"balance" bun:"balance"` -} - -type VolumesWithBalanceByAssets map[string]*VolumesWithBalance - -func (v Volumes) MarshalJSON() ([]byte, error) { - return json.Marshal(VolumesWithBalance{ - Input: v.Input, - Output: v.Output, - Balance: v.Balance(), - }) -} - -func (v Volumes) Balance() *big.Int { - input := v.Input - if input == nil { - input = Zero - } - output := v.Output - if output == nil { - output = Zero - } - return new(big.Int).Sub(input, output) -} - -func (v Volumes) copy() *Volumes { - return &Volumes{ - Input: new(big.Int).Set(v.Input), - Output: new(big.Int).Set(v.Output), - } -} - -type BalancesByAssets map[string]*big.Int - -type VolumesByAssets map[string]*Volumes - -type BalancesByAssetsByAccounts map[string]BalancesByAssets - -func (v VolumesByAssets) Balances() BalancesByAssets { - balances := BalancesByAssets{} - for asset, vv := range v { - balances[asset] = new(big.Int).Sub(vv.Input, vv.Output) - } - return balances -} - -func (v VolumesByAssets) copy() VolumesByAssets { - ret := VolumesByAssets{} - for key, volumes := range v { - ret[key] = volumes.copy() - } - return ret -} - -type AccountsAssetsVolumes map[string]VolumesByAssets - -func (a AccountsAssetsVolumes) GetVolumes(account, asset string) *Volumes { - if a == nil { - return &Volumes{ - Input: &big.Int{}, - Output: &big.Int{}, - } - } - if assetsVolumes, ok := a[account]; !ok { - return &Volumes{ - Input: &big.Int{}, - Output: &big.Int{}, - } - } else { - return &Volumes{ - Input: assetsVolumes[asset].Input, - Output: assetsVolumes[asset].Output, - } - } -} - -func (a *AccountsAssetsVolumes) SetVolumes(account, asset string, volumes *Volumes) { - if *a == nil { - *a = AccountsAssetsVolumes{} - } - if assetsVolumes, ok := (*a)[account]; !ok { - (*a)[account] = map[string]*Volumes{ - asset: volumes.CopyWithZerosIfNeeded(), - } - } else { - assetsVolumes[asset] = volumes.CopyWithZerosIfNeeded() - } -} - -func (a *AccountsAssetsVolumes) AddInput(account, asset string, input *big.Int) { - if *a == nil { - *a = AccountsAssetsVolumes{} - } - if assetsVolumes, ok := (*a)[account]; !ok { - (*a)[account] = map[string]*Volumes{ - asset: { - Input: input, - Output: &big.Int{}, - }, - } - } else { - volumes := assetsVolumes[asset].CopyWithZerosIfNeeded() - volumes.Input.Add(volumes.Input, input) - assetsVolumes[asset] = volumes - } -} - -func (a *AccountsAssetsVolumes) AddOutput(account, asset string, output *big.Int) { - if *a == nil { - *a = AccountsAssetsVolumes{} - } - if assetsVolumes, ok := (*a)[account]; !ok { - (*a)[account] = map[string]*Volumes{ - asset: { - Output: output, - Input: &big.Int{}, - }, - } - } else { - volumes := assetsVolumes[asset].CopyWithZerosIfNeeded() - volumes.Output.Add(volumes.Output, output) - assetsVolumes[asset] = volumes - } -} - -func (a AccountsAssetsVolumes) HasAccount(account string) bool { - if a == nil { - return false - } - _, ok := a[account] - return ok -} - -func (a AccountsAssetsVolumes) HasAccountAndAsset(account, asset string) bool { - if a == nil { - return false - } - volumesByAsset, ok := a[account] - if !ok { - return false - } - _, ok = volumesByAsset[asset] - return ok -} - -// Scan - Implement the database/sql scanner interface -func (a *AccountsAssetsVolumes) Scan(value interface{}) error { - if value == nil { - return nil - } - - val, err := driver.String.ConvertValue(value) - if err != nil { - return err - } - - *a = AccountsAssetsVolumes{} - switch val := val.(type) { - case []uint8: - return json.Unmarshal(val, a) - case string: - return json.Unmarshal([]byte(val), a) - default: - panic("not handled type") - } -} - -func (a AccountsAssetsVolumes) Copy() AccountsAssetsVolumes { - ret := AccountsAssetsVolumes{} - for key, volumes := range a { - ret[key] = volumes.copy() - } - return ret -} - -func (a AccountsAssetsVolumes) Balances() BalancesByAssetsByAccounts { - ret := BalancesByAssetsByAccounts{} - for account, volumesByAssets := range a { - ret[account] = volumesByAssets.Balances() - } - return ret -} diff --git a/components/ledger/main.go b/components/ledger/main.go deleted file mode 100644 index 2b15716610..0000000000 --- a/components/ledger/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/formancehq/ledger/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/components/ledger/openapi.yaml b/components/ledger/openapi.yaml deleted file mode 100644 index fe21b00485..0000000000 --- a/components/ledger/openapi.yaml +++ /dev/null @@ -1,3716 +0,0 @@ -openapi: 3.0.3 -info: - title: Ledger API - contact: {} - version: LEDGER_VERSION -servers: - - url: http://localhost:8080/ -paths: - /_info: - get: - tags: - - ledger.v1 - summary: Show server information - operationId: getInfo - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/_info: - get: - summary: Get information about a ledger - operationId: getLedgerInfo - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/LedgerInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts: - head: - summary: Count the accounts from a ledger - operationId: countAccounts - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: Filter accounts by address pattern (regular expression placed between ^ and $). - schema: - type: string - example: users:.+ - - name: metadata - in: query - description: Filter accounts by metadata key value pairs. The filter can be used like this metadata[key]=value1&metadata[a.nested.key]=value2 - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - example: metadata[key]=value1&metadata[a.nested.key]=value2 - responses: - "200": - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - get: - summary: List accounts from a ledger - description: List accounts from a ledger, sorted by address in descending order. - operationId: listAccounts - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: Pagination cursor, will return accounts after given address, in descending order. - schema: - type: string - example: users:003 - - name: address - in: query - description: Filter accounts by address pattern (regular expression placed between ^ and $). - schema: - type: string - example: users:.+ - - name: metadata - in: query - description: Filter accounts by metadata key value pairs. Nested objects can be used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - example: metadata[key]=value1&metadata[a.nested.key]=value2 - - name: balance - in: query - description: Filter accounts by their balance (default operator is gte) - schema: - type: integer - format: int64 - example: 2400 - - name: balanceOperator - x-speakeasy-ignore: true - in: query - description: | - Operator used for the filtering of balances can be greater than/equal, less than/equal, greater than, less than, equal or not. - schema: - type: string - enum: - - gte - - lte - - gt - - lt - - e - - ne - example: gte - - name: balance_operator - x-speakeasy-ignore: true - in: query - description: | - Operator used for the filtering of balances can be greater than/equal, less than/equal, greater than, less than, equal or not. - Deprecated, please use `balanceOperator` instead. - schema: - type: string - enum: - - gte - - lte - - gt - - lt - - e - - ne - example: gte - deprecated: true - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountsCursorResponse' - "404": - description: Not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts/{address}: - get: - summary: Get account by its address - operationId: getAccount - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: | - Exact address of the account. It must match the following regular expressions pattern: - ``` - ^\w+(:\w+)*$ - ``` - required: true - schema: - type: string - example: users:001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts/{address}/metadata: - post: - summary: Add metadata to an account - operationId: addMetadataToAccount - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: | - Exact address of the account. It must match the following regular expressions pattern: - ``` - ^\w+(:\w+)*$ - ``` - required: true - schema: - type: string - example: users:001 - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/Metadata' - required: true - responses: - "204": - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/mapping: - get: - tags: - - ledger.v1 - operationId: getMapping - summary: Get the mapping of a ledger - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/MappingResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - put: - tags: - - ledger.v1 - operationId: updateMapping - summary: Update the mapping of a ledger - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Mapping' - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/MappingResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/script: - post: - deprecated: true - tags: - - ledger.v1 - operationId: runScript - summary: Execute a Numscript - description: | - This route is deprecated, and has been merged into `POST /{ledger}/transactions`. - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: preview - in: query - description: Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. - schema: - type: boolean - example: true - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Script' - responses: - "200": - description: | - On success, it will return a 200 status code, and the resulting transaction under the `transaction` field. - - On failure, it will also return a 200 status code, and the following fields: - - `details`: contains a URL. When there is an error parsing Numscript, the result can be difficult to read—the provided URL will render the error in an easy-to-read format. - - `errorCode` and `error_code` (deprecated): contains the string code of the error - - `errorMessage` and `error_message` (deprecated): contains a human-readable indication of what went wrong, for example that an account had insufficient funds, or that there was an error in the provided Numscript. - content: - application/json: - schema: - $ref: '#/components/schemas/ScriptResponse' - security: - - Authorization: - - ledger:write - /{ledger}/stats: - get: - tags: - - ledger.v1 - operationId: readStats - summary: Get statistics from a ledger - description: | - Get statistics from a ledger. (aggregate metrics on accounts and transactions) - parameters: - - name: ledger - in: path - description: name of the ledger - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/StatsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/transactions: - head: - tags: - - ledger.v1 - summary: Count the transactions from a ledger - operationId: countTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: reference - in: query - description: Filter transactions by reference field. - schema: - type: string - example: ref:001 - - name: account - in: query - description: Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: source - in: query - description: Filter transactions with postings involving given account at source (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: destination - in: query - description: Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: startTime - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: metadata - in: query - description: Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - properties: {} - example: metadata[key]=value1&metadata[a.nested.key]=value2 - responses: - "200": - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - get: - tags: - - ledger.v1 - summary: List transactions from a ledger - description: List transactions from a ledger, sorted by txid in descending order. - operationId: listTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: Pagination cursor, will return transactions after given txid (in descending order). - schema: - type: string - example: 1234 - - name: reference - in: query - description: Find transactions by reference field. - schema: - type: string - example: ref:001 - - name: account - in: query - description: Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: source - in: query - description: Filter transactions with postings involving given account at source (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: destination - in: query - description: Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: startTime - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - - name: metadata - in: query - description: Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - post: - tags: - - ledger.v1 - summary: Create a new transaction to a ledger - operationId: createTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: preview - in: query - description: Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. - schema: - type: boolean - example: true - requestBody: - required: true - description: | - The request body must contain at least one of the following objects: - - `postings`: suitable for simple transactions - - `script`: enabling more complex transactions with Numscript - content: - application/json: - schema: - $ref: '#/components/schemas/PostTransaction' - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/{txid}: - get: - tags: - - ledger.v1 - summary: Get transaction from a ledger by its ID - operationId: getTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/transactions/{txid}/metadata: - post: - tags: - - ledger.v1 - summary: Set the metadata of a transaction by its ID - operationId: addMetadataOnTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/Metadata' - responses: - "204": - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/{txid}/revert: - post: - tags: - - ledger.v1 - operationId: revertTransaction - summary: Revert a ledger transaction by its ID - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: disableChecks - in: query - description: Allow to disable balances checks - required: false - schema: - type: boolean - responses: - "201": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/batch: - post: - tags: - - ledger.v1 - summary: Create a new batch of transactions to a ledger - operationId: CreateTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Transactions' - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/balances: - get: - tags: - - ledger.v1 - summary: Get the balances from a ledger's account - operationId: getBalances - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: Filter balances involving given account, either as source or destination. - schema: - type: string - example: users:001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: after - in: query - description: Pagination cursor, will return accounts after given address, in descending order. - schema: - type: string - example: users:003 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: |- - Parameter used in pagination requests. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/BalancesCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/aggregate/balances: - get: - tags: - - ledger.v1 - summary: Get the aggregated balances from selected accounts - operationId: getBalancesAggregated - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: Filter balances involving given account, either as source or destination. - schema: - type: string - example: users:001 - - name: useInsertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AggregateBalancesResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/logs: - get: - tags: - - ledger.v1 - summary: List the logs from a ledger - description: List the logs from a ledger, sorted by ID in descending order. - operationId: listLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: Pagination cursor, will return the logs after a given ID. (in descending order). - schema: - type: string - example: 1234 - - name: startTime - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred after this timestamp. - The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: | - Filter transactions that occurred before this timestamp. - The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 1000. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/LogsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/_info: - get: - tags: - - ledger.v2 - summary: Show server information - operationId: v2GetInfo - x-speakeasy-name-override: GetInfo - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2ConfigInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - 5XX: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2: - get: - summary: List ledgers - operationId: v2ListLedgers - x-speakeasy-name-override: ListLedgers - tags: - - ledger.v2 - parameters: - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LedgerListResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - get: - summary: Get a ledger - operationId: v2GetLedger - x-speakeasy-name-override: GetLedger - tags: - - ledger.v2 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2GetLedgerResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - post: - summary: Create a ledger - operationId: v2CreateLedger - x-speakeasy-name-override: CreateLedger - tags: - - ledger.v2 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2CreateLedgerRequest' - responses: - "204": - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/metadata: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - put: - summary: Update ledger metadata - operationId: v2UpdateLedgerMetadata - x-speakeasy-name-override: UpdateLedgerMetadata - tags: - - ledger.v2 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2UpdateLedgerMetadataRequest' - responses: - "204": - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - 5XX: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/metadata/{key}: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: key - in: path - description: Key to remove. - required: true - schema: - type: string - example: foo - delete: - summary: Delete ledger metadata by key - operationId: v2DeleteLedgerMetadata - x-speakeasy-name-override: DeleteLedgerMetadata - tags: - - ledger.v2 - responses: - "204": - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/_info: - get: - summary: Get information about a ledger - operationId: v2GetLedgerInfo - x-speakeasy-name-override: GetLedgerInfo - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LedgerInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/_bulk: - post: - summary: Bulk request - operationId: v2CreateBulk - x-speakeasy-name-override: CreateBulk - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2Bulk' - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2BulkResponse' - "400": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2BulkResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/accounts: - head: - summary: Count the accounts from a ledger - operationId: v2CountAccounts - x-speakeasy-name-override: CountAccounts - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "204": - description: OK - headers: - Count: - schema: - type: integer - format: bigint - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - get: - summary: List accounts from a ledger - description: List accounts from a ledger, sorted by address in descending order. - operationId: v2ListAccounts - x-speakeasy-name-override: ListAccounts - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AccountsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/accounts/{address}: - get: - summary: Get account by its address - operationId: v2GetAccount - x-speakeasy-name-override: GetAccount - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: | - Exact address of the account. It must match the following regular expressions pattern: - ``` - ^\w+(:\w+)*$ - ``` - required: true - schema: - type: string - example: users:001 - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AccountResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/accounts/{address}/metadata: - post: - summary: Add metadata to an account - operationId: v2AddMetadataToAccount - x-speakeasy-name-override: AddMetadataToAccount - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: | - Exact address of the account. It must match the following regular expressions pattern: - ``` - ^\w+(:\w+)*$ - ``` - required: true - schema: - type: string - example: users:001 - - name: dryRun - in: query - description: Set the dry run mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/V2Metadata' - required: true - responses: - "204": - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/accounts/{address}/metadata/{key}: - delete: - description: Delete metadata by key - operationId: v2DeleteAccountMetadata - x-speakeasy-name-override: DeleteAccountMetadata - tags: - - ledger.v2 - summary: Delete metadata by key - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: Account address - required: true - schema: - type: string - - name: key - in: path - description: The key to remove. - required: true - schema: - type: string - example: foo - responses: - 2XX: - description: Key deleted - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/stats: - get: - tags: - - ledger.v2 - operationId: v2ReadStats - x-speakeasy-name-override: ReadStats - summary: Get statistics from a ledger - description: | - Get statistics from a ledger. (aggregate metrics on accounts and transactions) - parameters: - - name: ledger - in: path - description: name of the ledger - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2StatsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/transactions: - head: - tags: - - ledger.v2 - summary: Count the transactions from a ledger - operationId: v2CountTransactions - x-speakeasy-name-override: CountTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "204": - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - get: - tags: - - ledger.v2 - summary: List transactions from a ledger - description: List transactions from a ledger, sorted by id in descending order. - operationId: v2ListTransactions - x-speakeasy-name-override: ListTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - - name: order - in: query - required: false - schema: - type: string - enum: - - effective - - name: reverse - in: query - required: false - schema: - type: boolean - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2TransactionsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - post: - tags: - - ledger.v2 - summary: Create a new transaction to a ledger - operationId: v2CreateTransaction - x-speakeasy-name-override: CreateTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: dryRun - in: query - description: Set the dryRun mode. dry run mode doesn't add the logs to the database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - required: true - description: | - The request body must contain at least one of the following objects: - - `postings`: suitable for simple transactions - - `script`: enabling more complex transactions with Numscript - content: - application/json: - schema: - $ref: '#/components/schemas/V2PostTransaction' - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2CreateTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}: - get: - tags: - - ledger.v2 - summary: Get transaction from a ledger by its ID - operationId: v2GetTransaction - x-speakeasy-name-override: GetTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2GetTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/transactions/{id}/metadata: - post: - tags: - - ledger.v2 - summary: Set the metadata of a transaction by its ID - operationId: v2AddMetadataOnTransaction - x-speakeasy-name-override: AddMetadataOnTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: dryRun - in: query - description: Set the dryRun mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/V2Metadata' - responses: - "204": - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}/metadata/{key}: - delete: - description: Delete metadata by key - operationId: v2DeleteTransactionMetadata - x-speakeasy-name-override: DeleteTransactionMetadata - summary: Delete metadata by key - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: key - in: path - required: true - description: The key to remove. - schema: - type: string - example: foo - responses: - 2XX: - description: Key deleted - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}/revert: - post: - tags: - - ledger.v2 - operationId: v2RevertTransaction - x-speakeasy-name-override: RevertTransaction - summary: Revert a ledger transaction by its ID - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: force - in: query - description: Force revert - required: false - schema: - type: boolean - - name: atEffectiveDate - in: query - description: Revert transaction at effective date of the original tx - required: false - schema: - type: boolean - responses: - "201": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2RevertTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/aggregate/balances: - get: - tags: - - ledger.v2 - summary: Get the aggregated balances from selected accounts - operationId: v2GetBalancesAggregated - x-speakeasy-name-override: GetBalancesAggregated - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - - name: useInsertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AggregateBalancesResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/volumes: - get: - tags: - - ledger.v2 - summary: Get list of volumes with balances for (account/asset) - operationId: v2GetVolumesWithBalances - x-speakeasy-name-override: GetVolumesWithBalances - parameters: - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: endTime - in: query - required: false - schema: - type: string - format: date-time - - name: startTime - in: query - required: false - schema: - type: string - format: date-time - - name: insertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - - name: groupBy - in: query - description: Group volumes and balance by the level of the segment of the address - example: 3 - schema: - type: integer - format: int64 - minimum: 0 - maximum: 1000 - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2VolumesWithBalanceCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/logs: - get: - tags: - - ledger.v2 - summary: List the logs from a ledger - description: List the logs from a ledger, sorted by ID in descending order. - operationId: v2ListLogs - x-speakeasy-name-override: ListLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LogsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/logs/import: - post: - tags: - - ledger.v2 - operationId: v2ImportLogs - x-speakeasy-name-override: ImportLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/octet-stream: - schema: - type: string - responses: - "204": - description: Import OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/logs/export: - post: - summary: Export logs - operationId: v2ExportLogs - x-speakeasy-name-override: ExportLogs - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - "200": - description: Import OK - default: - description: Error - content: - application/octet-stream: - schema: - title: bytes - type: string - format: binary - security: - - Authorization: - - ledger:write -components: - schemas: - AccountsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/Account' - BalancesCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/AccountsBalances' - TransactionsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/Transaction' - LogsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/Log' - AccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/AccountWithVolumesAndBalances' - AggregateBalancesResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/AssetsBalances' - Config: - type: object - properties: - storage: - $ref: '#/components/schemas/LedgerStorage' - required: - - storage - LedgerStorage: - type: object - properties: - driver: - type: string - ledgers: - type: array - items: - type: string - required: - - driver - - ledgers - Metadata: - type: object - nullable: true - additionalProperties: {} - ConfigInfo: - type: object - properties: - config: - $ref: '#/components/schemas/Config' - server: - type: string - version: - type: string - required: - - config - - server - - version - ScriptResponse: - type: object - properties: - errorCode: - $ref: '#/components/schemas/ErrorsEnum' - errorMessage: - type: string - example: account had insufficient funds - details: - type: string - example: https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - transaction: - $ref: '#/components/schemas/Transaction' - Account: - type: object - required: - - address - properties: - address: - type: string - example: users:001 - type: - type: string - example: virtual - metadata: - type: object - additionalProperties: true - example: - admin: true - a: - nested: - key: value - AccountWithVolumesAndBalances: - type: object - required: - - address - properties: - address: - type: string - example: users:001 - type: - type: string - example: virtual - metadata: - type: object - additionalProperties: true - example: - admin: true - a: - nested: - key: value - volumes: - $ref: '#/components/schemas/Volumes' - balances: - type: object - additionalProperties: - type: integer - format: bigint - example: - COIN: 100 - AccountsBalances: - type: object - additionalProperties: - $ref: '#/components/schemas/AssetsBalances' - example: - account1: - USD: 100 - EUR: 23 - account2: - CAD: 20 - JPY: 21 - AssetsBalances: - type: object - additionalProperties: - type: integer - format: int64 - example: - USD: 100 - EUR: 12 - Contract: - type: object - properties: - account: - type: string - example: users:001 - expr: - type: object - required: - - accounts - - expr - Mapping: - type: object - nullable: true - required: - - contracts - properties: - contracts: - type: array - items: - $ref: '#/components/schemas/Contract' - Posting: - type: object - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: COIN - destination: - type: string - example: users:002 - source: - type: string - example: users:001 - required: - - amount - - asset - - destination - - source - Script: - type: object - properties: - plain: - type: string - example: | - vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - vars: - type: object - additionalProperties: true - example: - user: users:042 - reference: - type: string - example: order_1234 - description: Reference to attach to the generated transaction - metadata: - $ref: '#/components/schemas/Metadata' - required: - - plain - Transaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - txid: - type: integer - format: bigint - minimum: 0 - preCommitVolumes: - $ref: '#/components/schemas/AggregatedVolumes' - postCommitVolumes: - $ref: '#/components/schemas/AggregatedVolumes' - required: - - postings - - timestamp - - txid - TransactionData: - type: object - required: - - postings - properties: - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - timestamp: - type: string - format: date-time - Transactions: - required: - - transactions - type: object - properties: - transactions: - type: array - items: - $ref: '#/components/schemas/TransactionData' - PostTransaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - script: - type: object - properties: - plain: - type: string - example: | - vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - vars: - type: object - additionalProperties: true - example: - user: users:042 - required: - - plain - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - Stats: - type: object - properties: - accounts: - type: integer - format: int64 - minimum: 0 - transactions: - type: integer - format: int64 - minimum: 0 - required: - - accounts - - transactions - Log: - type: object - properties: - id: - type: integer - format: int64 - minimum: 0 - example: 1234 - type: - type: string - enum: - - NEW_TRANSACTION - - SET_METADATA - data: - type: object - additionalProperties: true - hash: - type: string - example: 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e - date: - type: string - format: date-time - required: - - id - - type - - data - - hash - - date - TransactionsResponse: - type: object - properties: - data: - items: - $ref: '#/components/schemas/Transaction' - type: array - required: - - data - TransactionResponse: - properties: - data: - $ref: '#/components/schemas/Transaction' - type: object - required: - - data - StatsResponse: - properties: - data: - $ref: '#/components/schemas/Stats' - type: object - required: - - data - MappingResponse: - properties: - data: - $ref: '#/components/schemas/Mapping' - type: object - ConfigInfoResponse: - properties: - data: - $ref: '#/components/schemas/ConfigInfo' - type: object - required: - - data - Volume: - type: object - properties: - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - input - - output - example: - input: 100 - output: 20 - balance: 80 - Volumes: - type: object - additionalProperties: - $ref: '#/components/schemas/Volume' - example: - USD: - input: 100 - output: 10 - balance: 90 - EUR: - input: 100 - output: 10 - balance: 90 - AggregatedVolumes: - type: object - additionalProperties: - $ref: '#/components/schemas/Volumes' - example: - orders:1: - USD: - input: 100 - output: 10 - balance: 90 - orders:2: - USD: - input: 100 - output: 10 - balance: 90 - ErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/ErrorsEnum' - errorMessage: - type: string - example: '[INSUFFICIENT_FUND] account had insufficient funds' - details: - type: string - example: https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - ErrorsEnum: - type: string - enum: - - INTERNAL - - INSUFFICIENT_FUND - - VALIDATION - - CONFLICT - - NO_SCRIPT - - COMPILATION_FAILED - - METADATA_OVERRIDE - - NOT_FOUND - example: INSUFFICIENT_FUND - LedgerInfoResponse: - properties: - data: - $ref: '#/components/schemas/LedgerInfo' - LedgerInfo: - type: object - properties: - name: - type: string - example: ledger001 - storage: - type: object - properties: - migrations: - type: array - items: - $ref: '#/components/schemas/MigrationInfo' - MigrationInfo: - type: object - properties: - version: - type: integer - format: int64 - minimum: 0 - example: 11 - name: - type: string - example: migrations:001 - date: - type: string - format: date-time - state: - type: string - enum: - - TO DO - - DONE - V2AccountsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/V2Account' - V2TransactionsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/V2ExpandedTransaction' - V2LogsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/V2Log' - V2AccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2Account' - V2AggregateBalancesResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2AssetsBalances' - V2VolumesWithBalanceCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/V2VolumesWithBalance' - V2VolumesWithBalance: - type: object - properties: - account: - type: string - asset: - type: string - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - account - - asset - - input - - output - - balance - V2Metadata: - type: object - additionalProperties: - type: string - example: - admin: "true" - V2ConfigInfo: - type: object - properties: - server: - type: string - version: - type: string - required: - - config - - server - - version - V2Account: - type: object - required: - - address - - metadata - properties: - address: - type: string - example: users:001 - metadata: - type: object - properties: {} - additionalProperties: - type: string - example: - admin: "true" - volumes: - $ref: '#/components/schemas/V2Volumes' - effectiveVolumes: - $ref: '#/components/schemas/V2Volumes' - V2AssetsBalances: - type: object - additionalProperties: - type: integer - format: bigint - example: - USD: 100 - EUR: 12 - V2Posting: - type: object - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: COIN - destination: - type: string - example: users:002 - source: - type: string - example: users:001 - required: - - amount - - asset - - destination - - source - V2Transaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/V2Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/V2Metadata' - id: - type: integer - format: bigint - minimum: 0 - reverted: - type: boolean - required: - - postings - - timestamp - - id - - metadata - - reverted - V2ExpandedTransaction: - allOf: - - $ref: '#/components/schemas/V2Transaction' - - type: object - properties: - preCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' - postCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' - V2PostTransaction: - type: object - required: - - metadata - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/V2Posting' - script: - type: object - properties: - plain: - type: string - example: | - vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - vars: - type: object - properties: {} - additionalProperties: true - example: - user: users:042 - required: - - plain - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/V2Metadata' - V2Stats: - type: object - properties: - accounts: - type: integer - format: int64 - minimum: 0 - transactions: - type: integer - format: bigint - minimum: 0 - required: - - accounts - - transactions - V2Log: - type: object - properties: - id: - type: integer - format: bigint - minimum: 0 - example: 1234 - type: - type: string - enum: - - NEW_TRANSACTION - - SET_METADATA - - REVERTED_TRANSACTION - data: - type: object - properties: {} - additionalProperties: true - hash: - type: string - example: 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e - date: - type: string - format: date-time - required: - - id - - type - - data - - hash - - date - V2CreateTransactionResponse: - properties: - data: - $ref: '#/components/schemas/V2Transaction' - type: object - required: - - data - V2RevertTransactionResponse: - $ref: '#/components/schemas/V2CreateTransactionResponse' - V2GetTransactionResponse: - properties: - data: - $ref: '#/components/schemas/V2ExpandedTransaction' - type: object - required: - - data - V2StatsResponse: - properties: - data: - $ref: '#/components/schemas/V2Stats' - type: object - required: - - data - V2ConfigInfoResponse: - $ref: '#/components/schemas/V2ConfigInfo' - V2Volume: - type: object - properties: - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - input - - output - example: - input: 100 - output: 20 - balance: 80 - V2Volumes: - type: object - additionalProperties: - $ref: '#/components/schemas/V2Volume' - example: - USD: - input: 100 - output: 10 - balance: 90 - EUR: - input: 100 - output: 10 - balance: 90 - V2AggregatedVolumes: - type: object - additionalProperties: - $ref: '#/components/schemas/V2Volumes' - example: - orders:1: - USD: - input: 100 - output: 10 - balance: 90 - orders:2: - USD: - input: 100 - output: 10 - balance: 90 - V2ErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/V2ErrorsEnum' - errorMessage: - type: string - example: '[VALIDATION] invalid ''cursor'' query param' - details: - type: string - example: https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - V2ErrorsEnum: - type: string - enum: - - INTERNAL - - INSUFFICIENT_FUND - - VALIDATION - - CONFLICT - - COMPILATION_FAILED - - METADATA_OVERRIDE - - NOT_FOUND - - REVERT_OCCURRING - - ALREADY_REVERT - - NO_POSTINGS - - LEDGER_NOT_FOUND - - IMPORT - example: VALIDATION - V2LedgerInfoResponse: - type: object - properties: - data: - $ref: '#/components/schemas/V2LedgerInfo' - V2LedgerInfo: - type: object - properties: - name: - type: string - example: ledger001 - storage: - type: object - properties: - migrations: - type: array - items: - $ref: '#/components/schemas/V2MigrationInfo' - V2MigrationInfo: - type: object - properties: - version: - type: integer - format: int64 - minimum: 0 - example: 11 - name: - type: string - example: migrations:001 - date: - type: string - format: date-time - state: - type: string - enum: - - TO DO - - DONE - V2Bulk: - type: array - items: - $ref: '#/components/schemas/V2BulkElement' - V2BaseBulkElement: - type: object - required: - - action - properties: - action: - type: string - ik: - type: string - V2BulkElement: - type: object - oneOf: - - $ref: '#/components/schemas/V2BulkElementCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementAddMetadata' - - $ref: '#/components/schemas/V2BulkElementRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementDeleteMetadata' - discriminator: - propertyName: action - mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementDeleteMetadata' - V2BulkElementCreateTransaction: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - $ref: '#/components/schemas/V2PostTransaction' - V2TargetId: - oneOf: - - type: string - - type: integer - format: bigint - V2TargetType: - type: string - enum: - - TRANSACTION - - ACCOUNT - V2BulkElementAddMetadata: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - targetId: - $ref: '#/components/schemas/V2TargetId' - targetType: - $ref: '#/components/schemas/V2TargetType' - metadata: - type: object - additionalProperties: - type: string - required: - - targetId - - targetType - - metadata - V2BulkElementRevertTransaction: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - id: - type: integer - format: bigint - force: - type: boolean - atEffectiveDate: - type: boolean - required: - - id - V2BulkElementDeleteMetadata: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - targetId: - $ref: '#/components/schemas/V2TargetId' - targetType: - $ref: '#/components/schemas/V2TargetType' - key: - type: string - required: - - targetId - - targetType - - key - V2BulkResponse: - properties: - data: - type: array - items: - $ref: '#/components/schemas/V2BulkElementResult' - type: object - required: - - data - V2BulkElementResult: - type: object - oneOf: - - $ref: '#/components/schemas/V2BulkElementResultCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementResultAddMetadata' - - $ref: '#/components/schemas/V2BulkElementResultRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementResultDeleteMetadata' - - $ref: '#/components/schemas/V2BulkElementResultError' - discriminator: - propertyName: responseType - mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementResultCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementResultAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementResultRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementResultDeleteMetadata' - ERROR: '#/components/schemas/V2BulkElementResultError' - V2BaseBulkElementResult: - type: object - properties: - responseType: - type: string - required: - - responseType - V2BulkElementResultCreateTransaction: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - data: - $ref: '#/components/schemas/V2Transaction' - required: - - data - V2BulkElementResultAddMetadata: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - V2BulkElementResultRevertTransaction: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - data: - $ref: '#/components/schemas/V2Transaction' - required: - - data - V2BulkElementResultDeleteMetadata: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - V2BulkElementResultError: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - errorCode: - type: string - errorDescription: - type: string - errorDetails: - type: string - required: - - errorCode - - errorDescription - V2CreateLedgerRequest: - type: object - properties: - bucket: - type: string - metadata: - $ref: '#/components/schemas/V2Metadata' - V2Ledger: - type: object - properties: - name: - type: string - addedAt: - type: string - format: date-time - bucket: - type: string - metadata: - $ref: '#/components/schemas/V2Metadata' - required: - - name - - addedAt - - bucket - V2LedgerListResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: "" - data: - type: array - items: - $ref: '#/components/schemas/V2Ledger' - V2UpdateLedgerMetadataRequest: - $ref: '#/components/schemas/V2Metadata' - V2GetLedgerResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2Ledger' - securitySchemes: - Authorization: - type: oauth2 - flows: - clientCredentials: - tokenUrl: /api/auth/oauth/token - refreshUrl: /api/auth/oauth/token - scopes: {} diff --git a/components/ledger/openapi/openapi-merge.json b/components/ledger/openapi/openapi-merge.json deleted file mode 100644 index 14511edede..0000000000 --- a/components/ledger/openapi/openapi-merge.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "inputs": [ - { - "inputFile": "./v1.yaml" - }, - { - "inputFile": "./v2.yaml" - } - ], - "output": "./../openapi.json" -} diff --git a/components/ledger/openapi/v1.yaml b/components/ledger/openapi/v1.yaml deleted file mode 100644 index ff9129b4ae..0000000000 --- a/components/ledger/openapi/v1.yaml +++ /dev/null @@ -1,1891 +0,0 @@ -openapi: 3.0.3 -info: - title: Ledger API - contact: {} - version: LEDGER_VERSION -paths: - /_info: - get: - tags: - - ledger.v1 - summary: Show server information - operationId: getInfo - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/_info: - get: - summary: Get information about a ledger - operationId: getLedgerInfo - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/LedgerInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts: - head: - summary: Count the accounts from a ledger - operationId: countAccounts - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: >- - Filter accounts by address pattern (regular expression placed - between ^ and $). - schema: - type: string - example: users:.+ - - name: metadata - in: query - description: >- - Filter accounts by metadata key value pairs. The filter can be used - like this metadata[key]=value1&metadata[a.nested.key]=value2 - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - example: metadata[key]=value1&metadata[a.nested.key]=value2 - responses: - '200': - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - get: - summary: List accounts from a ledger - description: List accounts from a ledger, sorted by address in descending order. - operationId: listAccounts - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: >- - Pagination cursor, will return accounts after given address, in - descending order. - schema: - type: string - example: users:003 - - name: address - in: query - description: >- - Filter accounts by address pattern (regular expression placed - between ^ and $). - schema: - type: string - example: users:.+ - - name: metadata - in: query - description: >- - Filter accounts by metadata key value pairs. Nested objects can be - used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - example: metadata[key]=value1&metadata[a.nested.key]=value2 - - name: balance - in: query - description: Filter accounts by their balance (default operator is gte) - schema: - type: integer - format: int64 - example: 2400 - - name: balanceOperator - x-speakeasy-ignore: true - in: query - description: > - Operator used for the filtering of balances can be greater - than/equal, less than/equal, greater than, less than, equal or not. - schema: - type: string - enum: - - gte - - lte - - gt - - lt - - e - - ne - example: gte - - name: balance_operator - x-speakeasy-ignore: true - in: query - description: > - Operator used for the filtering of balances can be greater - than/equal, less than/equal, greater than, less than, equal or not. - - Deprecated, please use `balanceOperator` instead. - schema: - type: string - enum: - - gte - - lte - - gt - - lt - - e - - ne - example: gte - deprecated: true - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountsCursorResponse' - '404': - description: Not found - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts/{address}: - get: - summary: Get account by its address - operationId: getAccount - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: > - Exact address of the account. It must match the following regular - expressions pattern: - - ``` - - ^\w+(:\w+)*$ - - ``` - required: true - schema: - type: string - example: users:001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/accounts/{address}/metadata: - post: - summary: Add metadata to an account - operationId: addMetadataToAccount - tags: - - ledger.v1 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: > - Exact address of the account. It must match the following regular - expressions pattern: - - ``` - - ^\w+(:\w+)*$ - - ``` - required: true - schema: - type: string - example: users:001 - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/Metadata' - required: true - responses: - '204': - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/mapping: - get: - tags: - - ledger.v1 - operationId: getMapping - summary: Get the mapping of a ledger - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/MappingResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - put: - tags: - - ledger.v1 - operationId: updateMapping - summary: Update the mapping of a ledger - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Mapping' - required: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/MappingResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/script: - post: - deprecated: true - tags: - - ledger.v1 - operationId: runScript - summary: Execute a Numscript - description: > - This route is deprecated, and has been merged into `POST - /{ledger}/transactions`. - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: preview - in: query - description: >- - Set the preview mode. Preview mode doesn't add the logs to the - database or publish a message to the message broker. - schema: - type: boolean - example: true - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Script' - responses: - '200': - description: > - On success, it will return a 200 status code, and the resulting - transaction under the `transaction` field. - - - On failure, it will also return a 200 status code, and the following - fields: - - `details`: contains a URL. When there is an error parsing Numscript, the result can be difficult to read—the provided URL will render the error in an easy-to-read format. - - `errorCode` and `error_code` (deprecated): contains the string code of the error - - `errorMessage` and `error_message` (deprecated): contains a human-readable indication of what went wrong, for example that an account had insufficient funds, or that there was an error in the provided Numscript. - content: - application/json: - schema: - $ref: '#/components/schemas/ScriptResponse' - security: - - Authorization: - - ledger:write - /{ledger}/stats: - get: - tags: - - ledger.v1 - operationId: readStats - summary: Get statistics from a ledger - description: > - Get statistics from a ledger. (aggregate metrics on accounts and - transactions) - parameters: - - name: ledger - in: path - description: name of the ledger - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/StatsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/transactions: - head: - tags: - - ledger.v1 - summary: Count the transactions from a ledger - operationId: countTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: reference - in: query - description: Filter transactions by reference field. - schema: - type: string - example: ref:001 - - name: account - in: query - description: >- - Filter transactions with postings involving given account, either as - source or destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: source - in: query - description: >- - Filter transactions with postings involving given account at source - (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: destination - in: query - description: >- - Filter transactions with postings involving given account at - destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: startTime - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: metadata - in: query - description: >- - Filter transactions by metadata key value pairs. Nested objects can - be used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - properties: {} - example: metadata[key]=value1&metadata[a.nested.key]=value2 - responses: - '200': - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - get: - tags: - - ledger.v1 - summary: List transactions from a ledger - description: List transactions from a ledger, sorted by txid in descending order. - operationId: listTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: >- - Pagination cursor, will return transactions after given txid (in - descending order). - schema: - type: string - example: 1234 - - name: reference - in: query - description: Find transactions by reference field. - schema: - type: string - example: ref:001 - - name: account - in: query - description: >- - Filter transactions with postings involving given account, either as - source or destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: source - in: query - description: >- - Filter transactions with postings involving given account at source - (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: destination - in: query - description: >- - Filter transactions with postings involving given account at - destination (regular expression placed between ^ and $). - schema: - type: string - example: users:001 - - name: startTime - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - - name: metadata - in: query - description: >- - Filter transactions by metadata key value pairs. Nested objects can - be used as seen in the example below. - style: deepObject - explode: true - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - post: - tags: - - ledger.v1 - summary: Create a new transaction to a ledger - operationId: createTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: preview - in: query - description: >- - Set the preview mode. Preview mode doesn't add the logs to the - database or publish a message to the message broker. - schema: - type: boolean - example: true - requestBody: - required: true - description: | - The request body must contain at least one of the following objects: - - `postings`: suitable for simple transactions - - `script`: enabling more complex transactions with Numscript - content: - application/json: - schema: - $ref: '#/components/schemas/PostTransaction' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/{txid}: - get: - tags: - - ledger.v1 - summary: Get transaction from a ledger by its ID - operationId: getTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/transactions/{txid}/metadata: - post: - tags: - - ledger.v1 - summary: Set the metadata of a transaction by its ID - operationId: addMetadataOnTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/Metadata' - responses: - '204': - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/{txid}/revert: - post: - tags: - - ledger.v1 - operationId: revertTransaction - summary: Revert a ledger transaction by its ID - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: txid - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: disableChecks - in: query - description: Allow to disable balances checks - required: false - schema: - type: boolean - responses: - '201': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/transactions/batch: - post: - tags: - - ledger.v1 - summary: Create a new batch of transactions to a ledger - operationId: CreateTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Transactions' - required: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:write - /{ledger}/balances: - get: - tags: - - ledger.v1 - summary: Get the balances from a ledger's account - operationId: getBalances - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: >- - Filter balances involving given account, either as source or - destination. - schema: - type: string - example: users:001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: after - in: query - description: >- - Pagination cursor, will return accounts after given address, in - descending order. - schema: - type: string - example: users:003 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: |- - Parameter used in pagination requests. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/BalancesCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/aggregate/balances: - get: - tags: - - ledger.v1 - summary: Get the aggregated balances from selected accounts - operationId: getBalancesAggregated - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: query - description: >- - Filter balances involving given account, either as source or - destination. - schema: - type: string - example: users:001 - - name: useInsertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AggregateBalancesResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read - /{ledger}/logs: - get: - tags: - - ledger.v1 - summary: List the logs from a ledger - description: List the logs from a ledger, sorted by ID in descending order. - operationId: listLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - - name: page_size - x-speakeasy-ignore: true - in: query - description: | - The maximum number of results to return per page. - Deprecated, please use `pageSize` instead. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - deprecated: true - - name: after - in: query - description: >- - Pagination cursor, will return the logs after a given ID. (in - descending order). - schema: - type: string - example: 1234 - - name: startTime - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - schema: - type: string - format: date-time - - name: start_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred after this timestamp. - - The format is RFC3339 and is inclusive (for example, - "2023-01-02T15:04:01Z" includes the first second of 4th minute). - - Deprecated, please use `startTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: endTime - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - schema: - type: string - format: date-time - - name: end_time - x-speakeasy-ignore: true - in: query - description: > - Filter transactions that occurred before this timestamp. - - The format is RFC3339 and is exclusive (for example, - "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - - Deprecated, please use `endTime` instead. - schema: - type: string - format: date-time - deprecated: true - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pagination_token - x-speakeasy-ignore: true - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 1000. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - - Deprecated, please use `cursor` instead. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - deprecated: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/LogsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - ledger:read -components: - securitySchemes: - Authorization: - type: oauth2 - flows: - clientCredentials: - tokenUrl: '/api/auth/oauth/token' - refreshUrl: '/api/auth/oauth/token' - scopes: { } - - schemas: - AccountsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/Account' - BalancesCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/AccountsBalances' - TransactionsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/Transaction' - LogsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/Log' - AccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/AccountWithVolumesAndBalances' - AggregateBalancesResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/AssetsBalances' - Config: - type: object - properties: - storage: - $ref: '#/components/schemas/LedgerStorage' - required: - - storage - LedgerStorage: - type: object - properties: - driver: - type: string - ledgers: - type: array - items: - type: string - required: - - driver - - ledgers - Metadata: - type: object - nullable: true - additionalProperties: {} - ConfigInfo: - type: object - properties: - config: - $ref: '#/components/schemas/Config' - server: - type: string - version: - type: string - required: - - config - - server - - version - ScriptResponse: - type: object - properties: - errorCode: - $ref: '#/components/schemas/ErrorsEnum' - errorMessage: - type: string - example: account had insufficient funds - details: - type: string - example: >- - https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - transaction: - $ref: '#/components/schemas/Transaction' - Account: - type: object - required: - - address - properties: - address: - type: string - example: users:001 - type: - type: string - example: virtual - metadata: - type: object - additionalProperties: true - example: - admin: true - a: - nested: - key: value - AccountWithVolumesAndBalances: - type: object - required: - - address - properties: - address: - type: string - example: users:001 - type: - type: string - example: virtual - metadata: - type: object - additionalProperties: true - example: - admin: true - a: - nested: - key: value - volumes: - $ref: '#/components/schemas/Volumes' - balances: - type: object - additionalProperties: - type: integer - format: bigint - example: - COIN: 100 - AccountsBalances: - type: object - additionalProperties: - $ref: '#/components/schemas/AssetsBalances' - example: - account1: - USD: 100 - EUR: 23 - account2: - CAD: 20 - JPY: 21 - AssetsBalances: - type: object - additionalProperties: - type: integer - format: int64 - example: - USD: 100 - EUR: 12 - Contract: - type: object - properties: - account: - type: string - example: users:001 - expr: - type: object - required: - - accounts - - expr - Mapping: - type: object - nullable: true - required: - - contracts - properties: - contracts: - type: array - items: - $ref: '#/components/schemas/Contract' - Posting: - type: object - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: COIN - destination: - type: string - example: users:002 - source: - type: string - example: users:001 - required: - - amount - - asset - - destination - - source - Script: - type: object - properties: - plain: - type: string - example: "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n" - vars: - type: object - additionalProperties: true - example: - user: users:042 - reference: - type: string - example: order_1234 - description: Reference to attach to the generated transaction - metadata: - $ref: '#/components/schemas/Metadata' - required: - - plain - Transaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - txid: - type: integer - format: bigint - minimum: 0 - preCommitVolumes: - $ref: '#/components/schemas/AggregatedVolumes' - postCommitVolumes: - $ref: '#/components/schemas/AggregatedVolumes' - required: - - postings - - timestamp - - txid - TransactionData: - type: object - required: - - postings - properties: - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - timestamp: - type: string - format: date-time - Transactions: - required: - - transactions - type: object - properties: - transactions: - type: array - items: - $ref: '#/components/schemas/TransactionData' - PostTransaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/Posting' - script: - type: object - properties: - plain: - type: string - example: "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n" - vars: - type: object - additionalProperties: true - example: - user: users:042 - required: - - plain - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/Metadata' - Stats: - type: object - properties: - accounts: - type: integer - format: int64 - minimum: 0 - transactions: - type: integer - format: int64 - minimum: 0 - required: - - accounts - - transactions - Log: - type: object - properties: - id: - type: integer - format: int64 - minimum: 0 - example: 1234 - type: - type: string - enum: - - NEW_TRANSACTION - - SET_METADATA - data: - type: object - additionalProperties: true - hash: - type: string - example: 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e - date: - type: string - format: date-time - required: - - id - - type - - data - - hash - - date - TransactionsResponse: - type: object - properties: - data: - items: - $ref: '#/components/schemas/Transaction' - type: array - required: - - data - TransactionResponse: - properties: - data: - $ref: '#/components/schemas/Transaction' - type: object - required: - - data - StatsResponse: - properties: - data: - $ref: '#/components/schemas/Stats' - type: object - required: - - data - MappingResponse: - properties: - data: - $ref: '#/components/schemas/Mapping' - type: object - ConfigInfoResponse: - properties: - data: - $ref: '#/components/schemas/ConfigInfo' - type: object - required: - - data - Volume: - type: object - properties: - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - input - - output - example: - input: 100 - output: 20 - balance: 80 - Volumes: - type: object - additionalProperties: - $ref: '#/components/schemas/Volume' - example: - USD: - input: 100 - output: 10 - balance: 90 - EUR: - input: 100 - output: 10 - balance: 90 - AggregatedVolumes: - type: object - additionalProperties: - $ref: '#/components/schemas/Volumes' - example: - orders:1: - USD: - input: 100 - output: 10 - balance: 90 - orders:2: - USD: - input: 100 - output: 10 - balance: 90 - ErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/ErrorsEnum' - errorMessage: - type: string - example: '[INSUFFICIENT_FUND] account had insufficient funds' - details: - type: string - example: >- - https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - ErrorsEnum: - type: string - enum: - - INTERNAL - - INSUFFICIENT_FUND - - VALIDATION - - CONFLICT - - NO_SCRIPT - - COMPILATION_FAILED - - METADATA_OVERRIDE - - NOT_FOUND - example: INSUFFICIENT_FUND - LedgerInfoResponse: - properties: - data: - $ref: '#/components/schemas/LedgerInfo' - LedgerInfo: - type: object - properties: - name: - type: string - example: ledger001 - storage: - type: object - properties: - migrations: - type: array - items: - $ref: '#/components/schemas/MigrationInfo' - MigrationInfo: - type: object - properties: - version: - type: integer - format: int64 - minimum: 0 - example: 11 - name: - type: string - example: migrations:001 - date: - type: string - format: date-time - state: - type: string - enum: - - TO DO - - DONE diff --git a/components/ledger/openapi/v2.yaml b/components/ledger/openapi/v2.yaml deleted file mode 100644 index e0c200b152..0000000000 --- a/components/ledger/openapi/v2.yaml +++ /dev/null @@ -1,1977 +0,0 @@ -openapi: 3.0.3 -info: - title: Ledger API - contact: {} - version: LEDGER_VERSION -servers: - - url: http://localhost:8080/ -paths: - /v2/_info: - get: - tags: - - ledger.v2 - summary: Show server information - operationId: v2GetInfo - x-speakeasy-name-override: GetInfo - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2ConfigInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - 5XX: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2: - get: - summary: List ledgers - operationId: v2ListLedgers - x-speakeasy-name-override: ListLedgers - tags: - - ledger.v2 - parameters: - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 15. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LedgerListResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - get: - summary: Get a ledger - operationId: v2GetLedger - x-speakeasy-name-override: GetLedger - tags: - - ledger.v2 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2GetLedgerResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - post: - summary: Create a ledger - operationId: v2CreateLedger - x-speakeasy-name-override: CreateLedger - tags: - - ledger.v2 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2CreateLedgerRequest' - responses: - '204': - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/metadata: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - put: - summary: Update ledger metadata - operationId: v2UpdateLedgerMetadata - x-speakeasy-name-override: UpdateLedgerMetadata - tags: - - ledger.v2 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2UpdateLedgerMetadataRequest' - responses: - '204': - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - 5XX: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/metadata/{key}: - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: key - in: path - description: Key to remove. - required: true - schema: - type: string - example: foo - delete: - summary: Delete ledger metadata by key - operationId: v2DeleteLedgerMetadata - x-speakeasy-name-override: DeleteLedgerMetadata - tags: - - ledger.v2 - responses: - '204': - description: OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/_info: - get: - summary: Get information about a ledger - operationId: v2GetLedgerInfo - x-speakeasy-name-override: GetLedgerInfo - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LedgerInfoResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/_bulk: - post: - summary: Bulk request - operationId: v2CreateBulk - x-speakeasy-name-override: CreateBulk - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/V2Bulk' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2BulkResponse' - '400': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2BulkResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/accounts: - head: - summary: Count the accounts from a ledger - operationId: v2CountAccounts - x-speakeasy-name-override: CountAccounts - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '204': - description: OK - headers: - Count: - schema: - type: integer - format: bigint - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - get: - summary: List accounts from a ledger - description: List accounts from a ledger, sorted by address in descending order. - operationId: v2ListAccounts - x-speakeasy-name-override: ListAccounts - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 15. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AccountsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/accounts/{address}: - get: - summary: Get account by its address - operationId: v2GetAccount - x-speakeasy-name-override: GetAccount - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: > - Exact address of the account. It must match the following regular - expressions pattern: - - ``` - - ^\w+(:\w+)*$ - - ``` - required: true - schema: - type: string - example: users:001 - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AccountResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/accounts/{address}/metadata: - post: - summary: Add metadata to an account - operationId: v2AddMetadataToAccount - x-speakeasy-name-override: AddMetadataToAccount - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: > - Exact address of the account. It must match the following regular - expressions pattern: - - ``` - - ^\w+(:\w+)*$ - - ``` - required: true - schema: - type: string - example: users:001 - - name: dryRun - in: query - description: >- - Set the dry run mode. Dry run mode doesn't add the logs to the - database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/V2Metadata' - required: true - responses: - '204': - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/accounts/{address}/metadata/{key}: - delete: - description: Delete metadata by key - operationId: v2DeleteAccountMetadata - x-speakeasy-name-override: DeleteAccountMetadata - tags: - - ledger.v2 - summary: Delete metadata by key - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: address - in: path - description: Account address - required: true - schema: - type: string - - name: key - in: path - description: The key to remove. - required: true - schema: - type: string - example: foo - responses: - 2XX: - description: Key deleted - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/stats: - get: - tags: - - ledger.v2 - operationId: v2ReadStats - x-speakeasy-name-override: ReadStats - summary: Get statistics from a ledger - description: > - Get statistics from a ledger. (aggregate metrics on accounts and - transactions) - parameters: - - name: ledger - in: path - description: name of the ledger - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2StatsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/transactions: - head: - tags: - - ledger.v2 - summary: Count the transactions from a ledger - operationId: v2CountTransactions - x-speakeasy-name-override: CountTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '204': - description: OK - headers: - Count: - schema: - type: integer - format: int64 - minimum: 0 - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - get: - tags: - - ledger.v2 - summary: List transactions from a ledger - description: List transactions from a ledger, sorted by id in descending order. - operationId: v2ListTransactions - x-speakeasy-name-override: ListTransactions - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 15. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - - name: order - in: query - required: false - schema: - type: string - enum: - - effective - - name: reverse - in: query - required: false - schema: - type: boolean - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2TransactionsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - post: - tags: - - ledger.v2 - summary: Create a new transaction to a ledger - operationId: v2CreateTransaction - x-speakeasy-name-override: CreateTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: dryRun - in: query - description: >- - Set the dryRun mode. dry run mode doesn't add the logs to the - database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - required: true - description: | - The request body must contain at least one of the following objects: - - `postings`: suitable for simple transactions - - `script`: enabling more complex transactions with Numscript - content: - application/json: - schema: - $ref: '#/components/schemas/V2PostTransaction' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2CreateTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}: - get: - tags: - - ledger.v2 - summary: Get transaction from a ledger by its ID - operationId: v2GetTransaction - x-speakeasy-name-override: GetTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: expand - in: query - schema: - type: string - items: - type: string - - name: pit - in: query - required: false - schema: - type: string - format: date-time - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2GetTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/transactions/{id}/metadata: - post: - tags: - - ledger.v2 - summary: Set the metadata of a transaction by its ID - operationId: v2AddMetadataOnTransaction - x-speakeasy-name-override: AddMetadataOnTransaction - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: dryRun - in: query - description: >- - Set the dryRun mode. Dry run mode doesn't add the logs to the - database or publish a message to the message broker. - schema: - type: boolean - example: true - - name: Idempotency-Key - in: header - description: Use an idempotency key - schema: - type: string - requestBody: - description: metadata - content: - application/json: - schema: - $ref: '#/components/schemas/V2Metadata' - responses: - '204': - description: No Content - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}/metadata/{key}: - delete: - description: Delete metadata by key - operationId: v2DeleteTransactionMetadata - x-speakeasy-name-override: DeleteTransactionMetadata - summary: Delete metadata by key - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: key - in: path - required: true - description: The key to remove. - schema: - type: string - example: foo - responses: - 2XX: - description: Key deleted - content: {} - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/transactions/{id}/revert: - post: - tags: - - ledger.v2 - operationId: v2RevertTransaction - x-speakeasy-name-override: RevertTransaction - summary: Revert a ledger transaction by its ID - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: id - in: path - description: Transaction ID. - required: true - schema: - type: integer - format: bigint - minimum: 0 - example: 1234 - - name: force - in: query - description: Force revert - required: false - schema: - type: boolean - - name: atEffectiveDate - in: query - description: Revert transaction at effective date of the original tx - required: false - schema: - type: boolean - responses: - '201': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2RevertTransactionResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/aggregate/balances: - get: - tags: - - ledger.v2 - summary: Get the aggregated balances from selected accounts - operationId: v2GetBalancesAggregated - x-speakeasy-name-override: GetBalancesAggregated - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pit - in: query - required: false - schema: - type: string - format: date-time - - name: useInsertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2AggregateBalancesResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/volumes: - get: - tags: - - ledger.v2 - summary: Get list of volumes with balances for (account/asset) - operationId: v2GetVolumesWithBalances - x-speakeasy-name-override: GetVolumesWithBalances - parameters: - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 15. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: endTime - in: query - required: false - schema: - type: string - format: date-time - - name: startTime - in: query - required: false - schema: - type: string - format: date-time - - name: insertionDate - in: query - description: Use insertion date instead of effective date - required: false - schema: - type: boolean - - name: groupBy - in: query - description: Group volumes and balance by the level of the segment of the address - example: 3 - schema: - type: integer - format: int64 - minimum: 0 - maximum: 1000 - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2VolumesWithBalanceCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/logs: - get: - tags: - - ledger.v2 - summary: List the logs from a ledger - description: List the logs from a ledger, sorted by ID in descending order. - operationId: v2ListLogs - x-speakeasy-name-override: ListLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - - name: cursor - in: query - description: > - Parameter used in pagination requests. Maximum page size is set to - 15. - - Set to the value of next for the next page of results. - - Set to the value of previous for the previous page of results. - - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - - name: pit - in: query - required: false - schema: - type: string - format: date-time - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/V2LogsCursorResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:read - /v2/{ledger}/logs/import: - post: - tags: - - ledger.v2 - operationId: v2ImportLogs - x-speakeasy-name-override: ImportLogs - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - requestBody: - content: - application/octet-stream: - schema: - type: string - responses: - '204': - description: Import OK - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/V2ErrorResponse' - security: - - Authorization: - - ledger:write - /v2/{ledger}/logs/export: - post: - summary: Export logs - operationId: v2ExportLogs - x-speakeasy-name-override: ExportLogs - tags: - - ledger.v2 - parameters: - - name: ledger - in: path - description: Name of the ledger. - required: true - schema: - type: string - example: ledger001 - responses: - '200': - description: Import OK - default: - description: Error - content: - application/octet-stream: - schema: - title: bytes - type: string - format: binary - security: - - Authorization: - - ledger:write -components: - securitySchemes: - Authorization: - type: oauth2 - flows: - clientCredentials: - tokenUrl: '/api/auth/oauth/token' - refreshUrl: '/api/auth/oauth/token' - scopes: { } - - schemas: - V2AccountsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/V2Account' - V2TransactionsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/V2ExpandedTransaction' - V2LogsCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/V2Log' - V2AccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2Account' - V2AggregateBalancesResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2AssetsBalances' - V2VolumesWithBalanceCursorResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/V2VolumesWithBalance' - V2VolumesWithBalance: - type: object - properties: - account: - type: string - asset: - type: string - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - account - - asset - - input - - output - - balance - V2Metadata: - type: object - additionalProperties: - type: string - example: - admin: 'true' - V2ConfigInfo: - type: object - properties: - server: - type: string - version: - type: string - required: - - config - - server - - version - V2Account: - type: object - required: - - address - - metadata - properties: - address: - type: string - example: users:001 - metadata: - type: object - properties: {} - additionalProperties: - type: string - example: - admin: 'true' - volumes: - $ref: '#/components/schemas/V2Volumes' - effectiveVolumes: - $ref: '#/components/schemas/V2Volumes' - V2AssetsBalances: - type: object - additionalProperties: - type: integer - format: bigint - example: - USD: 100 - EUR: 12 - V2Posting: - type: object - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: COIN - destination: - type: string - example: users:002 - source: - type: string - example: users:001 - required: - - amount - - asset - - destination - - source - V2Transaction: - type: object - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/V2Posting' - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/V2Metadata' - id: - type: integer - format: bigint - minimum: 0 - reverted: - type: boolean - required: - - postings - - timestamp - - id - - metadata - - reverted - V2ExpandedTransaction: - allOf: - - $ref: '#/components/schemas/V2Transaction' - - type: object - properties: - preCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' - postCommitVolumes: - $ref: '#/components/schemas/V2AggregatedVolumes' - V2PostTransaction: - type: object - required: - - metadata - properties: - timestamp: - type: string - format: date-time - postings: - type: array - items: - $ref: '#/components/schemas/V2Posting' - script: - type: object - properties: - plain: - type: string - example: "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n" - vars: - type: object - properties: {} - additionalProperties: true - example: - user: users:042 - required: - - plain - reference: - type: string - example: ref:001 - metadata: - $ref: '#/components/schemas/V2Metadata' - V2Stats: - type: object - properties: - accounts: - type: integer - format: int64 - minimum: 0 - transactions: - type: integer - format: bigint - minimum: 0 - required: - - accounts - - transactions - V2Log: - type: object - properties: - id: - type: integer - format: bigint - minimum: 0 - example: 1234 - type: - type: string - enum: - - NEW_TRANSACTION - - SET_METADATA - - REVERTED_TRANSACTION - data: - type: object - properties: {} - additionalProperties: true - hash: - type: string - example: 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e - date: - type: string - format: date-time - required: - - id - - type - - data - - hash - - date - V2CreateTransactionResponse: - properties: - data: - $ref: '#/components/schemas/V2Transaction' - type: object - required: - - data - V2RevertTransactionResponse: - $ref: '#/components/schemas/V2CreateTransactionResponse' - V2GetTransactionResponse: - properties: - data: - $ref: '#/components/schemas/V2ExpandedTransaction' - type: object - required: - - data - V2StatsResponse: - properties: - data: - $ref: '#/components/schemas/V2Stats' - type: object - required: - - data - V2ConfigInfoResponse: - $ref: '#/components/schemas/V2ConfigInfo' - V2Volume: - type: object - properties: - input: - type: integer - format: bigint - output: - type: integer - format: bigint - balance: - type: integer - format: bigint - required: - - input - - output - example: - input: 100 - output: 20 - balance: 80 - V2Volumes: - type: object - additionalProperties: - $ref: '#/components/schemas/V2Volume' - example: - USD: - input: 100 - output: 10 - balance: 90 - EUR: - input: 100 - output: 10 - balance: 90 - V2AggregatedVolumes: - type: object - additionalProperties: - $ref: '#/components/schemas/V2Volumes' - example: - orders:1: - USD: - input: 100 - output: 10 - balance: 90 - orders:2: - USD: - input: 100 - output: 10 - balance: 90 - V2ErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/V2ErrorsEnum' - errorMessage: - type: string - example: '[VALIDATION] invalid ''cursor'' query param' - details: - type: string - example: >- - https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - V2ErrorsEnum: - type: string - enum: - - INTERNAL - - INSUFFICIENT_FUND - - VALIDATION - - CONFLICT - - COMPILATION_FAILED - - METADATA_OVERRIDE - - NOT_FOUND - - REVERT_OCCURRING - - ALREADY_REVERT - - NO_POSTINGS - - LEDGER_NOT_FOUND - - IMPORT - example: VALIDATION - V2LedgerInfoResponse: - type: object - properties: - data: - $ref: '#/components/schemas/V2LedgerInfo' - V2LedgerInfo: - type: object - properties: - name: - type: string - example: ledger001 - storage: - type: object - properties: - migrations: - type: array - items: - $ref: '#/components/schemas/V2MigrationInfo' - V2MigrationInfo: - type: object - properties: - version: - type: integer - format: int64 - minimum: 0 - example: 11 - name: - type: string - example: migrations:001 - date: - type: string - format: date-time - state: - type: string - enum: - - TO DO - - DONE - V2Bulk: - type: array - items: - $ref: '#/components/schemas/V2BulkElement' - V2BaseBulkElement: - type: object - required: - - action - properties: - action: - type: string - ik: - type: string - V2BulkElement: - type: object - oneOf: - - $ref: '#/components/schemas/V2BulkElementCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementAddMetadata' - - $ref: '#/components/schemas/V2BulkElementRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementDeleteMetadata' - discriminator: - propertyName: action - mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementDeleteMetadata' - V2BulkElementCreateTransaction: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - $ref: '#/components/schemas/V2PostTransaction' - V2TargetId: - oneOf: - - type: string - - type: integer - format: bigint - V2TargetType: - type: string - enum: - - TRANSACTION - - ACCOUNT - V2BulkElementAddMetadata: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - targetId: - $ref: '#/components/schemas/V2TargetId' - targetType: - $ref: '#/components/schemas/V2TargetType' - metadata: - type: object - additionalProperties: - type: string - required: - - targetId - - targetType - - metadata - V2BulkElementRevertTransaction: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - id: - type: integer - format: bigint - force: - type: boolean - atEffectiveDate: - type: boolean - required: - - id - V2BulkElementDeleteMetadata: - type: object - allOf: - - $ref: '#/components/schemas/V2BaseBulkElement' - - type: object - properties: - data: - type: object - properties: - targetId: - $ref: '#/components/schemas/V2TargetId' - targetType: - $ref: '#/components/schemas/V2TargetType' - key: - type: string - required: - - targetId - - targetType - - key - V2BulkResponse: - properties: - data: - type: array - items: - $ref: '#/components/schemas/V2BulkElementResult' - type: object - required: - - data - V2BulkElementResult: - type: object - oneOf: - - $ref: '#/components/schemas/V2BulkElementResultCreateTransaction' - - $ref: '#/components/schemas/V2BulkElementResultAddMetadata' - - $ref: '#/components/schemas/V2BulkElementResultRevertTransaction' - - $ref: '#/components/schemas/V2BulkElementResultDeleteMetadata' - - $ref: '#/components/schemas/V2BulkElementResultError' - discriminator: - propertyName: responseType - mapping: - CREATE_TRANSACTION: '#/components/schemas/V2BulkElementResultCreateTransaction' - ADD_METADATA: '#/components/schemas/V2BulkElementResultAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/V2BulkElementResultRevertTransaction' - DELETE_METADATA: '#/components/schemas/V2BulkElementResultDeleteMetadata' - ERROR: '#/components/schemas/V2BulkElementResultError' - V2BaseBulkElementResult: - type: object - properties: - responseType: - type: string - required: - - responseType - V2BulkElementResultCreateTransaction: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - data: - $ref: '#/components/schemas/V2Transaction' - required: - - data - V2BulkElementResultAddMetadata: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - V2BulkElementResultRevertTransaction: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - data: - $ref: '#/components/schemas/V2Transaction' - required: - - data - V2BulkElementResultDeleteMetadata: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - V2BulkElementResultError: - allOf: - - $ref: '#/components/schemas/V2BaseBulkElementResult' - - type: object - properties: - errorCode: - type: string - errorDescription: - type: string - errorDetails: - type: string - required: - - errorCode - - errorDescription - V2CreateLedgerRequest: - type: object - properties: - bucket: - type: string - metadata: - $ref: '#/components/schemas/V2Metadata' - V2Ledger: - type: object - properties: - name: - type: string - addedAt: - type: string - format: date-time - bucket: - type: string - metadata: - $ref: '#/components/schemas/V2Metadata' - required: - - name - - addedAt - - bucket - V2LedgerListResponse: - type: object - required: - - cursor - properties: - cursor: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - data: - type: array - items: - $ref: '#/components/schemas/V2Ledger' - V2UpdateLedgerMetadataRequest: - $ref: '#/components/schemas/V2Metadata' - V2GetLedgerResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/V2Ledger' diff --git a/components/ledger/pkg/client/.devcontainer/README.md b/components/ledger/pkg/client/.devcontainer/README.md deleted file mode 100644 index 2540e7dfdc..0000000000 --- a/components/ledger/pkg/client/.devcontainer/README.md +++ /dev/null @@ -1,30 +0,0 @@ - -> **Remember to shutdown a GitHub Codespace when it is not in use!** - -# Dev Containers Quick Start - -The default location for usage snippets is the `samples` directory. - -## Running a Usage Sample - -A sample usage example has been provided in a `root.go` file. As you work with the SDK, it's expected that you will modify these samples to fit your needs. To execute this particular snippet, use the command below. - -``` -go run root.go -``` - -## Generating Additional Usage Samples - -The speakeasy CLI allows you to generate more usage snippets. Here's how: - -- To generate a sample for a specific operation by providing an operation ID, use: - -``` -speakeasy generate usage -s openapi/v2.yaml -l go -i {INPUT_OPERATION_ID} -o ./samples -``` - -- To generate samples for an entire namespace (like a tag or group name), use: - -``` -speakeasy generate usage -s openapi/v2.yaml -l go -n {INPUT_TAG_NAME} -o ./samples -``` diff --git a/components/ledger/pkg/client/.devcontainer/devcontainer.json b/components/ledger/pkg/client/.devcontainer/devcontainer.json deleted file mode 100644 index 2afb19f0b0..0000000000 --- a/components/ledger/pkg/client/.devcontainer/devcontainer.json +++ /dev/null @@ -1,59 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/go -{ - "name": "Go", - "image": "mcr.microsoft.com/devcontainers/go:1-1.20-bullseye", - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "sudo chmod +x ./.devcontainer/setup.sh && ./.devcontainer/setup.sh", - "customizations": { - "vscode": { - "extensions": [ - "golang.go", - "github.vscode-pull-request-github" // Github interaction - ], - "settings": { - "files.eol": "\n", - "editor.formatOnSave": true, - "go.buildTags": "", - "go.toolsEnvVars": { - "CGO_ENABLED": "0" - }, - "go.useLanguageServer": true, - "go.testEnvVars": { - "CGO_ENABLED": "1" - }, - "go.testFlags": [ - "-v", - "-race" - ], - "go.testTimeout": "60s", - "go.coverOnSingleTest": true, - "go.coverOnSingleTestFile": true, - "go.coverOnTestPackage": true, - "go.lintTool": "golangci-lint", - "go.lintOnSave": "package", - "[go]": { - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - "gopls": { - "usePlaceholders": false, - "staticcheck": true, - "vulncheck": "Imports" - } - } - }, - "codespaces": { - "openFiles": [ - ".devcontainer/README.md" - ] - } - } - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} \ No newline at end of file diff --git a/components/ledger/pkg/client/.devcontainer/setup.sh b/components/ledger/pkg/client/.devcontainer/setup.sh deleted file mode 100644 index f96631bbb7..0000000000 --- a/components/ledger/pkg/client/.devcontainer/setup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Install the speakeasy CLI -curl -fsSL https://raw.githubusercontent.com/speakeasy-api/speakeasy/main/install.sh | sh - -# Setup samples directory -rmdir samples || true -mkdir samples - -# Go module commands -go mod download -go mod tidy - -# Generate starter usage sample with speakeasy -speakeasy generate usage -s openapi/v2.yaml -l go -o samples/root.go \ No newline at end of file diff --git a/components/ledger/pkg/client/.gitattributes b/components/ledger/pkg/client/.gitattributes deleted file mode 100644 index e6a994416d..0000000000 --- a/components/ledger/pkg/client/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# This allows generated code to be indexed correctly -*.go linguist-generated=false \ No newline at end of file diff --git a/components/ledger/pkg/client/.gitignore b/components/ledger/pkg/client/.gitignore deleted file mode 100644 index d3c2f597fe..0000000000 --- a/components/ledger/pkg/client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -# .gitignore diff --git a/components/ledger/pkg/client/.speakeasy/gen.lock b/components/ledger/pkg/client/.speakeasy/gen.lock deleted file mode 100644 index 46f8407479..0000000000 --- a/components/ledger/pkg/client/.speakeasy/gen.lock +++ /dev/null @@ -1,381 +0,0 @@ -lockVersion: 2.0.0 -id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 -management: - docChecksum: ca116951d61ec04bcf24308e613bf2d7 - docVersion: LEDGER_VERSION - speakeasyVersion: 1.351.0 - generationVersion: 2.384.1 - releaseVersion: 0.3.0 - configChecksum: c8d8f683fb8781c1d3ca28aa7371d52e -features: - go: - additionalDependencies: 0.1.0 - constsAndDefaults: 0.1.4 - core: 3.5.2 - defaultEnabledRetries: 0.2.0 - deprecations: 2.81.1 - devContainers: 2.90.0 - downloadStreams: 0.1.1 - envVarSecurityUsage: 0.2.1 - errors: 2.81.9 - flattening: 2.81.1 - getRequestBodies: 2.81.1 - globalSecurity: 2.82.9 - globalSecurityCallbacks: 0.1.0 - globalServerURLs: 2.82.2 - ignores: 2.81.1 - intellisenseMarkdownSupport: 0.1.0 - nameOverrides: 2.81.2 - nullables: 0.1.0 - oauth2ClientCredentials: 0.1.1 - responseFormat: 0.1.2 - retries: 2.83.0 - sdkHooks: 0.1.0 - unions: 2.85.8 -generatedFiles: - - v1.go - - v2.go - - ledger.go - - formance.go - - go.mod - - models/sdkerrors/sdkerror.go - - retry/config.go - - types/bigint.go - - types/date.go - - types/datetime.go - - types/decimal.go - - types/pointers.go - - internal/utils/contenttype.go - - internal/utils/form.go - - internal/utils/headers.go - - internal/utils/json.go - - internal/utils/pathparams.go - - internal/utils/queryparams.go - - internal/utils/requestbody.go - - internal/utils/retries.go - - internal/utils/security.go - - internal/utils/utils.go - - /models/operations/getinfo.go - - /models/operations/getledgerinfo.go - - /models/operations/countaccounts.go - - /models/operations/listaccounts.go - - /models/operations/getaccount.go - - /models/operations/addmetadatatoaccount.go - - /models/operations/getmapping.go - - /models/operations/updatemapping.go - - /models/operations/runscript.go - - /models/operations/readstats.go - - /models/operations/counttransactions.go - - /models/operations/listtransactions.go - - /models/operations/createtransaction.go - - /models/operations/gettransaction.go - - /models/operations/addmetadataontransaction.go - - /models/operations/reverttransaction.go - - /models/operations/createtransactions.go - - /models/operations/getbalances.go - - /models/operations/getbalancesaggregated.go - - /models/operations/listlogs.go - - /models/operations/v2getinfo.go - - /models/operations/v2listledgers.go - - /models/operations/v2getledger.go - - /models/operations/v2createledger.go - - /models/operations/v2updateledgermetadata.go - - /models/operations/v2deleteledgermetadata.go - - /models/operations/v2getledgerinfo.go - - /models/operations/v2createbulk.go - - /models/operations/v2countaccounts.go - - /models/operations/v2listaccounts.go - - /models/operations/v2getaccount.go - - /models/operations/v2addmetadatatoaccount.go - - /models/operations/v2deleteaccountmetadata.go - - /models/operations/v2readstats.go - - /models/operations/v2counttransactions.go - - /models/operations/v2listtransactions.go - - /models/operations/v2createtransaction.go - - /models/operations/v2gettransaction.go - - /models/operations/v2addmetadataontransaction.go - - /models/operations/v2deletetransactionmetadata.go - - /models/operations/v2reverttransaction.go - - /models/operations/v2getbalancesaggregated.go - - /models/operations/v2getvolumeswithbalances.go - - /models/operations/v2listlogs.go - - /models/operations/v2importlogs.go - - /models/operations/v2exportlogs.go - - /models/components/configinforesponse.go - - /models/components/configinfo.go - - /models/components/config.go - - /models/components/ledgerstorage.go - - /models/components/httpmetadata.go - - /models/components/errorsenum.go - - /models/components/ledgerinforesponse.go - - /models/components/ledgerinfo.go - - /models/components/migrationinfo.go - - /models/components/accountscursorresponse.go - - /models/components/account.go - - /models/components/accountresponse.go - - /models/components/accountwithvolumesandbalances.go - - /models/components/volume.go - - /models/components/mappingresponse.go - - /models/components/mapping.go - - /models/components/contract.go - - /models/components/scriptresponse.go - - /models/components/transaction.go - - /models/components/posting.go - - /models/components/script.go - - /models/components/statsresponse.go - - /models/components/stats.go - - /models/components/transactionscursorresponse.go - - /models/components/transactionsresponse.go - - /models/components/posttransaction.go - - /models/components/transactionresponse.go - - /models/components/transactions.go - - /models/components/transactiondata.go - - /models/components/balancescursorresponse.go - - /models/components/aggregatebalancesresponse.go - - /models/components/logscursorresponse.go - - /models/components/log.go - - /models/components/v2errorsenum.go - - /models/components/v2configinforesponse.go - - /models/components/v2ledgerlistresponse.go - - /models/components/v2ledger.go - - /models/components/v2getledgerresponse.go - - /models/components/v2createledgerrequest.go - - /models/components/v2ledgerinforesponse.go - - /models/components/v2ledgerinfo.go - - /models/components/v2migrationinfo.go - - /models/components/v2bulkresponse.go - - /models/components/v2bulkelementresult.go - - /models/components/v2transaction.go - - /models/components/v2posting.go - - /models/components/v2bulkelement.go - - /models/components/v2bulkelementcreatetransaction.go - - /models/components/v2posttransaction.go - - /models/components/v2bulkelementaddmetadata.go - - /models/components/v2targettype.go - - /models/components/v2targetid.go - - /models/components/v2bulkelementreverttransaction.go - - /models/components/v2bulkelementdeletemetadata.go - - /models/components/v2accountscursorresponse.go - - /models/components/v2account.go - - /models/components/v2volume.go - - /models/components/v2accountresponse.go - - /models/components/v2statsresponse.go - - /models/components/v2stats.go - - /models/components/v2transactionscursorresponse.go - - /models/components/v2expandedtransaction.go - - /models/components/v2createtransactionresponse.go - - /models/components/v2gettransactionresponse.go - - /models/components/v2reverttransactionresponse.go - - /models/components/v2aggregatebalancesresponse.go - - /models/components/v2volumeswithbalancecursorresponse.go - - /models/components/v2volumeswithbalance.go - - /models/components/v2logscursorresponse.go - - /models/components/v2log.go - - /models/components/security.go - - /models/sdkerrors/errorresponse.go - - /models/sdkerrors/v2errorresponse.go - - docs/models/operations/getinforesponse.md - - docs/models/operations/getledgerinforequest.md - - docs/models/operations/getledgerinforesponse.md - - docs/models/operations/countaccountsrequest.md - - docs/models/operations/countaccountsresponse.md - - docs/models/operations/listaccountsrequest.md - - docs/models/operations/listaccountsresponse.md - - docs/models/operations/getaccountrequest.md - - docs/models/operations/getaccountresponse.md - - docs/models/operations/addmetadatatoaccountrequest.md - - docs/models/operations/addmetadatatoaccountresponse.md - - docs/models/operations/getmappingrequest.md - - docs/models/operations/getmappingresponse.md - - docs/models/operations/updatemappingrequest.md - - docs/models/operations/updatemappingresponse.md - - docs/models/operations/runscriptrequest.md - - docs/models/operations/runscriptresponse.md - - docs/models/operations/readstatsrequest.md - - docs/models/operations/readstatsresponse.md - - docs/models/operations/metadata.md - - docs/models/operations/counttransactionsrequest.md - - docs/models/operations/counttransactionsresponse.md - - docs/models/operations/listtransactionsrequest.md - - docs/models/operations/listtransactionsresponse.md - - docs/models/operations/createtransactionrequest.md - - docs/models/operations/createtransactionresponse.md - - docs/models/operations/gettransactionrequest.md - - docs/models/operations/gettransactionresponse.md - - docs/models/operations/addmetadataontransactionrequest.md - - docs/models/operations/addmetadataontransactionresponse.md - - docs/models/operations/reverttransactionrequest.md - - docs/models/operations/reverttransactionresponse.md - - docs/models/operations/createtransactionsrequest.md - - docs/models/operations/createtransactionsresponse.md - - docs/models/operations/getbalancesrequest.md - - docs/models/operations/getbalancesresponse.md - - docs/models/operations/getbalancesaggregatedrequest.md - - docs/models/operations/getbalancesaggregatedresponse.md - - docs/models/operations/listlogsrequest.md - - docs/models/operations/listlogsresponse.md - - docs/models/operations/v2getinforesponse.md - - docs/models/operations/v2listledgersrequest.md - - docs/models/operations/v2listledgersresponse.md - - docs/models/operations/v2getledgerrequest.md - - docs/models/operations/v2getledgerresponse.md - - docs/models/operations/v2createledgerrequest.md - - docs/models/operations/v2createledgerresponse.md - - docs/models/operations/v2updateledgermetadatarequest.md - - docs/models/operations/v2updateledgermetadataresponse.md - - docs/models/operations/v2deleteledgermetadatarequest.md - - docs/models/operations/v2deleteledgermetadataresponse.md - - docs/models/operations/v2getledgerinforequest.md - - docs/models/operations/v2getledgerinforesponse.md - - docs/models/operations/v2createbulkrequest.md - - docs/models/operations/v2createbulkresponse.md - - docs/models/operations/v2countaccountsrequest.md - - docs/models/operations/v2countaccountsresponse.md - - docs/models/operations/v2listaccountsrequest.md - - docs/models/operations/v2listaccountsresponse.md - - docs/models/operations/v2getaccountrequest.md - - docs/models/operations/v2getaccountresponse.md - - docs/models/operations/v2addmetadatatoaccountrequest.md - - docs/models/operations/v2addmetadatatoaccountresponse.md - - docs/models/operations/v2deleteaccountmetadatarequest.md - - docs/models/operations/v2deleteaccountmetadataresponse.md - - docs/models/operations/v2readstatsrequest.md - - docs/models/operations/v2readstatsresponse.md - - docs/models/operations/v2counttransactionsrequest.md - - docs/models/operations/v2counttransactionsresponse.md - - docs/models/operations/order.md - - docs/models/operations/v2listtransactionsrequest.md - - docs/models/operations/v2listtransactionsresponse.md - - docs/models/operations/v2createtransactionrequest.md - - docs/models/operations/v2createtransactionresponse.md - - docs/models/operations/v2gettransactionrequest.md - - docs/models/operations/v2gettransactionresponse.md - - docs/models/operations/v2addmetadataontransactionrequest.md - - docs/models/operations/v2addmetadataontransactionresponse.md - - docs/models/operations/v2deletetransactionmetadatarequest.md - - docs/models/operations/v2deletetransactionmetadataresponse.md - - docs/models/operations/v2reverttransactionrequest.md - - docs/models/operations/v2reverttransactionresponse.md - - docs/models/operations/v2getbalancesaggregatedrequest.md - - docs/models/operations/v2getbalancesaggregatedresponse.md - - docs/models/operations/v2getvolumeswithbalancesrequest.md - - docs/models/operations/v2getvolumeswithbalancesresponse.md - - docs/models/operations/v2listlogsrequest.md - - docs/models/operations/v2listlogsresponse.md - - docs/models/operations/v2importlogsrequest.md - - docs/models/operations/v2importlogsresponse.md - - docs/models/operations/v2exportlogsrequest.md - - docs/models/operations/v2exportlogsresponse.md - - docs/models/components/configinforesponse.md - - docs/models/components/configinfo.md - - docs/models/components/config.md - - docs/models/components/ledgerstorage.md - - docs/models/components/httpmetadata.md - - docs/models/components/errorsenum.md - - docs/models/components/ledgerinforesponse.md - - docs/models/components/storage.md - - docs/models/components/ledgerinfo.md - - docs/models/components/state.md - - docs/models/components/migrationinfo.md - - docs/models/components/cursor.md - - docs/models/components/accountscursorresponse.md - - docs/models/components/account.md - - docs/models/components/accountresponse.md - - docs/models/components/accountwithvolumesandbalances.md - - docs/models/components/volume.md - - docs/models/components/mappingresponse.md - - docs/models/components/mapping.md - - docs/models/components/expr.md - - docs/models/components/contract.md - - docs/models/components/scriptresponse.md - - docs/models/components/transaction.md - - docs/models/components/posting.md - - docs/models/components/script.md - - docs/models/components/statsresponse.md - - docs/models/components/stats.md - - docs/models/components/transactionscursorresponsecursor.md - - docs/models/components/transactionscursorresponse.md - - docs/models/components/transactionsresponse.md - - docs/models/components/posttransactionscript.md - - docs/models/components/posttransaction.md - - docs/models/components/transactionresponse.md - - docs/models/components/transactions.md - - docs/models/components/transactiondata.md - - docs/models/components/balancescursorresponsecursor.md - - docs/models/components/balancescursorresponse.md - - docs/models/components/aggregatebalancesresponse.md - - docs/models/components/logscursorresponsecursor.md - - docs/models/components/logscursorresponse.md - - docs/models/components/type.md - - docs/models/components/log.md - - docs/models/components/v2errorsenum.md - - docs/models/components/v2configinforesponse.md - - docs/models/components/v2ledgerlistresponsecursor.md - - docs/models/components/v2ledgerlistresponse.md - - docs/models/components/v2ledger.md - - docs/models/components/v2getledgerresponse.md - - docs/models/components/v2createledgerrequest.md - - docs/models/components/v2ledgerinforesponse.md - - docs/models/components/v2ledgerinfostorage.md - - docs/models/components/v2ledgerinfo.md - - docs/models/components/v2migrationinfostate.md - - docs/models/components/v2migrationinfo.md - - docs/models/components/v2bulkresponse.md - - docs/models/components/v2bulkelementresulterror.md - - docs/models/components/v2bulkelementresultdeletemetadata.md - - docs/models/components/v2bulkelementresultreverttransaction.md - - docs/models/components/v2bulkelementresultaddmetadata.md - - docs/models/components/v2bulkelementresultcreatetransaction.md - - docs/models/components/v2bulkelementresult.md - - docs/models/components/v2transaction.md - - docs/models/components/v2posting.md - - docs/models/components/v2bulkelement.md - - docs/models/components/v2bulkelementcreatetransaction.md - - docs/models/components/v2posttransactionscript.md - - docs/models/components/v2posttransaction.md - - docs/models/components/data.md - - docs/models/components/v2bulkelementaddmetadata.md - - docs/models/components/v2targettype.md - - docs/models/components/v2targetid.md - - docs/models/components/v2bulkelementreverttransactiondata.md - - docs/models/components/v2bulkelementreverttransaction.md - - docs/models/components/v2bulkelementdeletemetadatadata.md - - docs/models/components/v2bulkelementdeletemetadata.md - - docs/models/components/v2accountscursorresponsecursor.md - - docs/models/components/v2accountscursorresponse.md - - docs/models/components/v2account.md - - docs/models/components/v2volume.md - - docs/models/components/v2accountresponse.md - - docs/models/components/v2statsresponse.md - - docs/models/components/v2stats.md - - docs/models/components/v2transactionscursorresponsecursor.md - - docs/models/components/v2transactionscursorresponse.md - - docs/models/components/v2expandedtransaction.md - - docs/models/components/v2createtransactionresponse.md - - docs/models/components/v2gettransactionresponse.md - - docs/models/components/v2reverttransactionresponse.md - - docs/models/components/v2aggregatebalancesresponse.md - - docs/models/components/v2volumeswithbalancecursorresponsecursor.md - - docs/models/components/v2volumeswithbalancecursorresponse.md - - docs/models/components/v2volumeswithbalance.md - - docs/models/components/v2logscursorresponsecursor.md - - docs/models/components/v2logscursorresponse.md - - docs/models/components/v2logtype.md - - docs/models/components/v2log.md - - docs/models/components/security.md - - docs/models/sdkerrors/errorresponse.md - - docs/models/sdkerrors/v2errorresponse.md - - docs/sdks/formance/README.md - - docs/sdks/ledger/README.md - - docs/models/operations/option.md - - docs/sdks/v1/README.md - - docs/sdks/v2/README.md - - USAGE.md - - models/operations/options.go - - .gitattributes - - .devcontainer/README.md - - .devcontainer/devcontainer.json - - .devcontainer/setup.sh - - internal/hooks/hooks.go - - internal/hooks/clientcredentials.go - - CONTRIBUTING.md diff --git a/components/ledger/pkg/client/.speakeasy/gen.yaml b/components/ledger/pkg/client/.speakeasy/gen.yaml deleted file mode 100644 index f8aaf5189e..0000000000 --- a/components/ledger/pkg/client/.speakeasy/gen.yaml +++ /dev/null @@ -1,37 +0,0 @@ -configVersion: 2.0.0 -generation: - devContainers: - enabled: true - schemaPath: openapi/v2.yaml - sdkClassName: Formance - maintainOpenAPIOrder: true - usageSnippets: - optionalPropertyRendering: withExample - useClassNamesForArrayFields: true - fixes: - nameResolutionDec2023: true - parameterOrderingFeb2024: true - requestResponseComponentNamesFeb2024: true - auth: - oAuth2ClientCredentialsEnabled: true -go: - version: 0.3.0 - additionalDependencies: {} - allowUnknownFieldsInWeakUnions: false - clientServerStatusCodesAsErrors: true - envVarPrefix: FORMANCE - flattenGlobalSecurity: true - imports: - option: openapi - paths: - callbacks: models/callbacks - errors: models/sdkerrors - operations: models/operations - shared: models/components - webhooks: models/webhooks - inputModelSuffix: input - maxMethodParams: 4 - methodArguments: require-security-and-request - outputModelSuffix: output - packageName: github.com/formancehq/stack/ledger/client - responseFormat: envelope-http diff --git a/components/ledger/pkg/client/.speakeasy/workflow.yaml b/components/ledger/pkg/client/.speakeasy/workflow.yaml deleted file mode 100644 index 9b1648d078..0000000000 --- a/components/ledger/pkg/client/.speakeasy/workflow.yaml +++ /dev/null @@ -1,11 +0,0 @@ -workflowVersion: 1.0.0 -sources: - Formance-OAS: - inputs: - - location: ../../openapi/v2.yaml - registry: - location: registry.speakeasyapi.dev/formance/gfyrag/formance-oas -targets: - formance: - target: go - source: Formance-OAS diff --git a/components/ledger/pkg/client/CONTRIBUTING.md b/components/ledger/pkg/client/CONTRIBUTING.md deleted file mode 100644 index d585717fca..0000000000 --- a/components/ledger/pkg/client/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# Contributing to This Repository - -Thank you for your interest in contributing to this repository. Please note that this repository contains generated code. As such, we do not accept direct changes or pull requests. Instead, we encourage you to follow the guidelines below to report issues and suggest improvements. - -## How to Report Issues - -If you encounter any bugs or have suggestions for improvements, please open an issue on GitHub. When reporting an issue, please provide as much detail as possible to help us reproduce the problem. This includes: - -- A clear and descriptive title -- Steps to reproduce the issue -- Expected and actual behavior -- Any relevant logs, screenshots, or error messages -- Information about your environment (e.g., operating system, software versions) - - For example can be collected using the `npx envinfo` command from your terminal if you have Node.js installed - -## Issue Triage and Upstream Fixes - -We will review and triage issues as quickly as possible. Our goal is to address bugs and incorporate improvements in the upstream source code. Fixes will be included in the next generation of the generated code. - -## Contact - -If you have any questions or need further assistance, please feel free to reach out by opening an issue. - -Thank you for your understanding and cooperation! - -The Maintainers diff --git a/components/ledger/pkg/client/README.md b/components/ledger/pkg/client/README.md deleted file mode 100644 index e1fb468a56..0000000000 --- a/components/ledger/pkg/client/README.md +++ /dev/null @@ -1,459 +0,0 @@ -# github.com/formancehq/stack/ledger/client - -Developer-friendly & type-safe Go SDK specifically catered to leverage *github.com/formancehq/stack/ledger/client* API. - -
- - - - -
- - -## 🏗 **Welcome to your new SDK!** 🏗 - -It has been generated successfully based on your OpenAPI spec. However, it is not yet ready for production use. Here are some next steps: -- [ ] 🛠 Make your SDK feel handcrafted by [customizing it](https://www.speakeasy.com/docs/customize-sdks) -- [ ] ♻️ Refine your SDK quickly by iterating locally with the [Speakeasy CLI](https://github.com/speakeasy-api/speakeasy) -- [ ] 🎁 Publish your SDK to package managers by [configuring automatic publishing](https://www.speakeasy.com/docs/advanced-setup/publish-sdks) -- [ ] ✨ When ready to productionize, delete this section from the README - - -## Summary - - - - - -## Table of Contents - -* [SDK Installation](#sdk-installation) -* [SDK Example Usage](#sdk-example-usage) -* [Available Resources and Operations](#available-resources-and-operations) -* [Retries](#retries) -* [Error Handling](#error-handling) -* [Server Selection](#server-selection) -* [Custom HTTP Client](#custom-http-client) -* [Special Types](#special-types) - - - -## SDK Installation - -```bash -go get github.com/formancehq/stack/ledger/client -``` - - - -## SDK Example Usage - -### Example - -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - - - -## Available Resources and Operations - -### [Ledger.V1](docs/sdks/v1/README.md) - -* [GetInfo](docs/sdks/v1/README.md#getinfo) - Show server information -* [GetLedgerInfo](docs/sdks/v1/README.md#getledgerinfo) - Get information about a ledger -* [CountAccounts](docs/sdks/v1/README.md#countaccounts) - Count the accounts from a ledger -* [ListAccounts](docs/sdks/v1/README.md#listaccounts) - List accounts from a ledger -* [GetAccount](docs/sdks/v1/README.md#getaccount) - Get account by its address -* [AddMetadataToAccount](docs/sdks/v1/README.md#addmetadatatoaccount) - Add metadata to an account -* [GetMapping](docs/sdks/v1/README.md#getmapping) - Get the mapping of a ledger -* [UpdateMapping](docs/sdks/v1/README.md#updatemapping) - Update the mapping of a ledger -* [~~RunScript~~](docs/sdks/v1/README.md#runscript) - Execute a Numscript :warning: **Deprecated** -* [ReadStats](docs/sdks/v1/README.md#readstats) - Get statistics from a ledger -* [CountTransactions](docs/sdks/v1/README.md#counttransactions) - Count the transactions from a ledger -* [ListTransactions](docs/sdks/v1/README.md#listtransactions) - List transactions from a ledger -* [CreateTransaction](docs/sdks/v1/README.md#createtransaction) - Create a new transaction to a ledger -* [GetTransaction](docs/sdks/v1/README.md#gettransaction) - Get transaction from a ledger by its ID -* [AddMetadataOnTransaction](docs/sdks/v1/README.md#addmetadataontransaction) - Set the metadata of a transaction by its ID -* [RevertTransaction](docs/sdks/v1/README.md#reverttransaction) - Revert a ledger transaction by its ID -* [CreateTransactions](docs/sdks/v1/README.md#createtransactions) - Create a new batch of transactions to a ledger -* [GetBalances](docs/sdks/v1/README.md#getbalances) - Get the balances from a ledger's account -* [GetBalancesAggregated](docs/sdks/v1/README.md#getbalancesaggregated) - Get the aggregated balances from selected accounts -* [ListLogs](docs/sdks/v1/README.md#listlogs) - List the logs from a ledger - -### [Ledger.V2](docs/sdks/v2/README.md) - -* [GetInfo](docs/sdks/v2/README.md#getinfo) - Show server information -* [ListLedgers](docs/sdks/v2/README.md#listledgers) - List ledgers -* [GetLedger](docs/sdks/v2/README.md#getledger) - Get a ledger -* [CreateLedger](docs/sdks/v2/README.md#createledger) - Create a ledger -* [UpdateLedgerMetadata](docs/sdks/v2/README.md#updateledgermetadata) - Update ledger metadata -* [DeleteLedgerMetadata](docs/sdks/v2/README.md#deleteledgermetadata) - Delete ledger metadata by key -* [GetLedgerInfo](docs/sdks/v2/README.md#getledgerinfo) - Get information about a ledger -* [CreateBulk](docs/sdks/v2/README.md#createbulk) - Bulk request -* [CountAccounts](docs/sdks/v2/README.md#countaccounts) - Count the accounts from a ledger -* [ListAccounts](docs/sdks/v2/README.md#listaccounts) - List accounts from a ledger -* [GetAccount](docs/sdks/v2/README.md#getaccount) - Get account by its address -* [AddMetadataToAccount](docs/sdks/v2/README.md#addmetadatatoaccount) - Add metadata to an account -* [DeleteAccountMetadata](docs/sdks/v2/README.md#deleteaccountmetadata) - Delete metadata by key -* [ReadStats](docs/sdks/v2/README.md#readstats) - Get statistics from a ledger -* [CountTransactions](docs/sdks/v2/README.md#counttransactions) - Count the transactions from a ledger -* [ListTransactions](docs/sdks/v2/README.md#listtransactions) - List transactions from a ledger -* [CreateTransaction](docs/sdks/v2/README.md#createtransaction) - Create a new transaction to a ledger -* [GetTransaction](docs/sdks/v2/README.md#gettransaction) - Get transaction from a ledger by its ID -* [AddMetadataOnTransaction](docs/sdks/v2/README.md#addmetadataontransaction) - Set the metadata of a transaction by its ID -* [DeleteTransactionMetadata](docs/sdks/v2/README.md#deletetransactionmetadata) - Delete metadata by key -* [RevertTransaction](docs/sdks/v2/README.md#reverttransaction) - Revert a ledger transaction by its ID -* [GetBalancesAggregated](docs/sdks/v2/README.md#getbalancesaggregated) - Get the aggregated balances from selected accounts -* [GetVolumesWithBalances](docs/sdks/v2/README.md#getvolumeswithbalances) - Get list of volumes with balances for (account/asset) -* [ListLogs](docs/sdks/v2/README.md#listlogs) - List the logs from a ledger -* [ImportLogs](docs/sdks/v2/README.md#importlogs) -* [ExportLogs](docs/sdks/v2/README.md#exportlogs) - Export logs - - - -## Retries - -Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK. - -To change the default retry strategy for a single API call, simply provide a `retry.Config` object to the call by using the `WithRetries` option: -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/retry" - "log" - "models/operations" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx, operations.WithRetries( - retry.Config{ - Strategy: "backoff", - Backoff: &retry.BackoffStrategy{ - InitialInterval: 1, - MaxInterval: 50, - Exponent: 1.1, - MaxElapsedTime: 100, - }, - RetryConnectionErrors: false, - })) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - -If you'd like to override the default retry strategy for all operations that support retries, you can use the `WithRetryConfig` option at SDK initialization: -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/retry" - "log" -) - -func main() { - s := client.New( - client.WithRetryConfig( - retry.Config{ - Strategy: "backoff", - Backoff: &retry.BackoffStrategy{ - InitialInterval: 1, - MaxInterval: 50, - Exponent: 1.1, - MaxElapsedTime: 100, - }, - RetryConnectionErrors: false, - }), - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - - - -## Error Handling - -Handling errors in this SDK should largely match your expectations. All operations return a response object or an error, they will never return both. When specified by the OpenAPI spec document, the SDK will return the appropriate subclass. - -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -### Example - -```go -package main - -import ( - "context" - "errors" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - - var e *sdkerrors.ErrorResponse - if errors.As(err, &e) { - // handle error - log.Fatal(e.Error()) - } - - var e *sdkerrors.SDKError - if errors.As(err, &e) { - // handle error - log.Fatal(e.Error()) - } - } -} - -``` - - - -## Server Selection - -### Select Server by Index - -You can override the default server globally using the `WithServerIndex` option when initializing the SDK client instance. The selected server will then be used as the default on the operations that use it. This table lists the indexes associated with the available servers: - -| # | Server | Variables | -| - | ------ | --------- | -| 0 | `http://localhost:8080/` | None | - -#### Example - -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "log" -) - -func main() { - s := client.New( - client.WithServerIndex(0), - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - - -### Override Server URL Per-Client - -The default server can also be overridden globally using the `WithServerURL` option when initializing the SDK client instance. For example: -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "log" -) - -func main() { - s := client.New( - client.WithServerURL("http://localhost:8080/"), - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - - - -## Custom HTTP Client - -The Go SDK makes API calls that wrap an internal HTTP client. The requirements for the HTTP client are very simple. It must match this interface: - -```go -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} -``` - -The built-in `net/http` client satisfies this interface and a default client based on the built-in is provided by default. To replace this default with a client of your own, you can implement this interface yourself or provide your own client configured as desired. Here's a simple example, which adds a client with a 30 second timeout. - -```go -import ( - "net/http" - "time" - "github.com/myorg/your-go-sdk" -) - -var ( - httpClient = &http.Client{Timeout: 30 * time.Second} - sdkClient = sdk.New(sdk.WithClient(httpClient)) -) -``` - -This can be a convenient way to configure timeouts, cookies, proxies, custom headers, and other low-level configuration. - - - -## Special Types - - - - - -## Authentication - -### Per-Client Security Schemes - -This SDK supports the following security schemes globally: - -| Name | Type | Scheme | -| -------------- | -------------- | -------------- | -| `ClientID` | oauth2 | OAuth2 token | -| `ClientSecret` | oauth2 | OAuth2 token | - -You can set the security parameters through the `WithSecurity` option when initializing the SDK client instance. The selected scheme will be used by default to authenticate with the API for all operations that support it. For example: -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - - - - -# Development - -## Maturity - -This SDK is in beta, and there may be breaking changes between versions without a major version update. Therefore, we recommend pinning usage -to a specific package version. This way, you can install the same version each time without breaking changes unless you are intentionally -looking for the latest version. - -## Contributions - -While we value open-source contributions to this SDK, this library is generated programmatically. Any manual changes added to internal files will be overwritten on the next generation. -We look forward to hearing your feedback. Feel free to open a PR or an issue with a proof of concept and we'll do our best to include it in a future release. - -### SDK Created by [Speakeasy](https://www.speakeasy.com/?utm_source=github-com/formancehq/stack/ledger/client&utm_campaign=go) diff --git a/components/ledger/pkg/client/USAGE.md b/components/ledger/pkg/client/USAGE.md deleted file mode 100644 index 4dad1a4042..0000000000 --- a/components/ledger/pkg/client/USAGE.md +++ /dev/null @@ -1,31 +0,0 @@ - -```go -package main - -import ( - "context" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/components" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} - -``` - \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/account.md b/components/ledger/pkg/client/docs/models/components/account.md deleted file mode 100644 index 50c7d2543c..0000000000 --- a/components/ledger/pkg/client/docs/models/components/account.md +++ /dev/null @@ -1,10 +0,0 @@ -# Account - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `Address` | *string* | :heavy_check_mark: | N/A | users:001 | -| `Type` | **string* | :heavy_minus_sign: | N/A | virtual | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | {
"admin": true,
"a": {
"nested": {
"key": "value"
}
}
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/accountresponse.md b/components/ledger/pkg/client/docs/models/components/accountresponse.md deleted file mode 100644 index aa0dbb2beb..0000000000 --- a/components/ledger/pkg/client/docs/models/components/accountresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# AccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `Data` | [components.AccountWithVolumesAndBalances](../../models/components/accountwithvolumesandbalances.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/accountscursorresponse.md b/components/ledger/pkg/client/docs/models/components/accountscursorresponse.md deleted file mode 100644 index cb36e52595..0000000000 --- a/components/ledger/pkg/client/docs/models/components/accountscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# AccountsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | -| `Cursor` | [components.Cursor](../../models/components/cursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/accountwithvolumesandbalances.md b/components/ledger/pkg/client/docs/models/components/accountwithvolumesandbalances.md deleted file mode 100644 index a3f04ff466..0000000000 --- a/components/ledger/pkg/client/docs/models/components/accountwithvolumesandbalances.md +++ /dev/null @@ -1,12 +0,0 @@ -# AccountWithVolumesAndBalances - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `Address` | *string* | :heavy_check_mark: | N/A | users:001 | -| `Type` | **string* | :heavy_minus_sign: | N/A | virtual | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | {
"admin": true,
"a": {
"nested": {
"key": "value"
}
}
} | -| `Volumes` | map[string][components.Volume](../../models/components/volume.md) | :heavy_minus_sign: | N/A | {
"USD": {
"input": 100,
"output": 10,
"balance": 90
},
"EUR": {
"input": 100,
"output": 10,
"balance": 90
}
} | -| `Balances` | map[string][*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_minus_sign: | N/A | {
"COIN": 100
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/aggregatebalancesresponse.md b/components/ledger/pkg/client/docs/models/components/aggregatebalancesresponse.md deleted file mode 100644 index 2c1da4164f..0000000000 --- a/components/ledger/pkg/client/docs/models/components/aggregatebalancesresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# AggregateBalancesResponse - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------- | ------------------------- | ------------------------- | ------------------------- | ------------------------- | -| `Data` | map[string]*int64* | :heavy_check_mark: | N/A | {
"USD": 100,
"EUR": 12
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/balancescursorresponse.md b/components/ledger/pkg/client/docs/models/components/balancescursorresponse.md deleted file mode 100644 index 8face69557..0000000000 --- a/components/ledger/pkg/client/docs/models/components/balancescursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# BalancesCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -| `Cursor` | [components.BalancesCursorResponseCursor](../../models/components/balancescursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/balancescursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/balancescursorresponsecursor.md deleted file mode 100644 index 8a35dbb2eb..0000000000 --- a/components/ledger/pkg/client/docs/models/components/balancescursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# BalancesCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | []map[string]map[string]*int64* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/config.md b/components/ledger/pkg/client/docs/models/components/config.md deleted file mode 100644 index d73e3095ad..0000000000 --- a/components/ledger/pkg/client/docs/models/components/config.md +++ /dev/null @@ -1,8 +0,0 @@ -# Config - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `Storage` | [components.LedgerStorage](../../models/components/ledgerstorage.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/configinfo.md b/components/ledger/pkg/client/docs/models/components/configinfo.md deleted file mode 100644 index a6562985a1..0000000000 --- a/components/ledger/pkg/client/docs/models/components/configinfo.md +++ /dev/null @@ -1,10 +0,0 @@ -# ConfigInfo - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | -| `Config` | [components.Config](../../models/components/config.md) | :heavy_check_mark: | N/A | -| `Server` | *string* | :heavy_check_mark: | N/A | -| `Version` | *string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/configinforesponse.md b/components/ledger/pkg/client/docs/models/components/configinforesponse.md deleted file mode 100644 index 40f2595c42..0000000000 --- a/components/ledger/pkg/client/docs/models/components/configinforesponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# ConfigInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -| `Data` | [components.ConfigInfo](../../models/components/configinfo.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/contract.md b/components/ledger/pkg/client/docs/models/components/contract.md deleted file mode 100644 index eef8b41221..0000000000 --- a/components/ledger/pkg/client/docs/models/components/contract.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contract - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -| `Account` | **string* | :heavy_minus_sign: | N/A | users:001 | -| `Expr` | [components.Expr](../../models/components/expr.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/cursor.md b/components/ledger/pkg/client/docs/models/components/cursor.md deleted file mode 100644 index de83140b8b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/cursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# Cursor - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.Account](../../models/components/account.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/data.md b/components/ledger/pkg/client/docs/models/components/data.md deleted file mode 100644 index 6f135b98b1..0000000000 --- a/components/ledger/pkg/client/docs/models/components/data.md +++ /dev/null @@ -1,10 +0,0 @@ -# Data - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `TargetID` | [components.V2TargetID](../../models/components/v2targetid.md) | :heavy_check_mark: | N/A | -| `TargetType` | [components.V2TargetType](../../models/components/v2targettype.md) | :heavy_check_mark: | N/A | -| `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/errorsenum.md b/components/ledger/pkg/client/docs/models/components/errorsenum.md deleted file mode 100644 index f7cdb1cece..0000000000 --- a/components/ledger/pkg/client/docs/models/components/errorsenum.md +++ /dev/null @@ -1,15 +0,0 @@ -# ErrorsEnum - - -## Values - -| Name | Value | -| ----------------------------- | ----------------------------- | -| `ErrorsEnumInternal` | INTERNAL | -| `ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | -| `ErrorsEnumValidation` | VALIDATION | -| `ErrorsEnumConflict` | CONFLICT | -| `ErrorsEnumNoScript` | NO_SCRIPT | -| `ErrorsEnumCompilationFailed` | COMPILATION_FAILED | -| `ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | -| `ErrorsEnumNotFound` | NOT_FOUND | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/expr.md b/components/ledger/pkg/client/docs/models/components/expr.md deleted file mode 100644 index 4cd8556281..0000000000 --- a/components/ledger/pkg/client/docs/models/components/expr.md +++ /dev/null @@ -1,7 +0,0 @@ -# Expr - - -## Fields - -| Field | Type | Required | Description | -| ----------- | ----------- | ----------- | ----------- | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/httpmetadata.md b/components/ledger/pkg/client/docs/models/components/httpmetadata.md deleted file mode 100644 index df1fdd59f3..0000000000 --- a/components/ledger/pkg/client/docs/models/components/httpmetadata.md +++ /dev/null @@ -1,9 +0,0 @@ -# HTTPMetadata - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | -| `Response` | [*http.Response](https://pkg.go.dev/net/http#Response) | :heavy_check_mark: | Raw HTTP response; suitable for custom response parsing | -| `Request` | [*http.Request](https://pkg.go.dev/net/http#Request) | :heavy_check_mark: | Raw HTTP request; suitable for debugging | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/ledgerinfo.md b/components/ledger/pkg/client/docs/models/components/ledgerinfo.md deleted file mode 100644 index f170e7e89b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/ledgerinfo.md +++ /dev/null @@ -1,9 +0,0 @@ -# LedgerInfo - - -## Fields - -| Field | Type | Required | Description | Example | -| --------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | -| `Name` | **string* | :heavy_minus_sign: | N/A | ledger001 | -| `Storage` | [*components.Storage](../../models/components/storage.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/ledgerinforesponse.md b/components/ledger/pkg/client/docs/models/components/ledgerinforesponse.md deleted file mode 100644 index d45e0f2c75..0000000000 --- a/components/ledger/pkg/client/docs/models/components/ledgerinforesponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# LedgerInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | -| `Data` | [*components.LedgerInfo](../../models/components/ledgerinfo.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/ledgerstorage.md b/components/ledger/pkg/client/docs/models/components/ledgerstorage.md deleted file mode 100644 index 159532bd02..0000000000 --- a/components/ledger/pkg/client/docs/models/components/ledgerstorage.md +++ /dev/null @@ -1,9 +0,0 @@ -# LedgerStorage - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `Driver` | *string* | :heavy_check_mark: | N/A | -| `Ledgers` | []*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/log.md b/components/ledger/pkg/client/docs/models/components/log.md deleted file mode 100644 index 4dd66d037a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/log.md +++ /dev/null @@ -1,12 +0,0 @@ -# Log - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | -| `ID` | *int64* | :heavy_check_mark: | N/A | 1234 | -| `Type` | [components.Type](../../models/components/type.md) | :heavy_check_mark: | N/A | | -| `Data` | map[string]*any* | :heavy_check_mark: | N/A | | -| `Hash` | *string* | :heavy_check_mark: | N/A | 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e | -| `Date` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/logscursorresponse.md b/components/ledger/pkg/client/docs/models/components/logscursorresponse.md deleted file mode 100644 index 3503553f48..0000000000 --- a/components/ledger/pkg/client/docs/models/components/logscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# LogsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| `Cursor` | [components.LogsCursorResponseCursor](../../models/components/logscursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/logscursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/logscursorresponsecursor.md deleted file mode 100644 index 781a3a9c81..0000000000 --- a/components/ledger/pkg/client/docs/models/components/logscursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# LogsCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------- | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.Log](../../models/components/log.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/mapping.md b/components/ledger/pkg/client/docs/models/components/mapping.md deleted file mode 100644 index 1cd36db637..0000000000 --- a/components/ledger/pkg/client/docs/models/components/mapping.md +++ /dev/null @@ -1,8 +0,0 @@ -# Mapping - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `Contracts` | [][components.Contract](../../models/components/contract.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/mappingresponse.md b/components/ledger/pkg/client/docs/models/components/mappingresponse.md deleted file mode 100644 index a5f4484632..0000000000 --- a/components/ledger/pkg/client/docs/models/components/mappingresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# MappingResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------- | -| `Data` | [*components.Mapping](../../models/components/mapping.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/migrationinfo.md b/components/ledger/pkg/client/docs/models/components/migrationinfo.md deleted file mode 100644 index aabaecf30b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/migrationinfo.md +++ /dev/null @@ -1,11 +0,0 @@ -# MigrationInfo - - -## Fields - -| Field | Type | Required | Description | Example | -| ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- | -| `Version` | **int64* | :heavy_minus_sign: | N/A | 11 | -| `Name` | **string* | :heavy_minus_sign: | N/A | migrations:001 | -| `Date` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `State` | [*components.State](../../models/components/state.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/posting.md b/components/ledger/pkg/client/docs/models/components/posting.md deleted file mode 100644 index dc8a9ec937..0000000000 --- a/components/ledger/pkg/client/docs/models/components/posting.md +++ /dev/null @@ -1,11 +0,0 @@ -# Posting - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Amount` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | 100 | -| `Asset` | *string* | :heavy_check_mark: | N/A | COIN | -| `Destination` | *string* | :heavy_check_mark: | N/A | users:002 | -| `Source` | *string* | :heavy_check_mark: | N/A | users:001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/posttransaction.md b/components/ledger/pkg/client/docs/models/components/posttransaction.md deleted file mode 100644 index 5307324076..0000000000 --- a/components/ledger/pkg/client/docs/models/components/posttransaction.md +++ /dev/null @@ -1,12 +0,0 @@ -# PostTransaction - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| `Timestamp` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `Postings` | [][components.Posting](../../models/components/posting.md) | :heavy_minus_sign: | N/A | | -| `Script` | [*components.PostTransactionScript](../../models/components/posttransactionscript.md) | :heavy_minus_sign: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/posttransactionscript.md b/components/ledger/pkg/client/docs/models/components/posttransactionscript.md deleted file mode 100644 index c344ba499d..0000000000 --- a/components/ledger/pkg/client/docs/models/components/posttransactionscript.md +++ /dev/null @@ -1,9 +0,0 @@ -# PostTransactionScript - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| -| `Vars` | map[string]*any* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/script.md b/components/ledger/pkg/client/docs/models/components/script.md deleted file mode 100644 index b5b4ce45b2..0000000000 --- a/components/ledger/pkg/client/docs/models/components/script.md +++ /dev/null @@ -1,11 +0,0 @@ -# Script - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| -| `Vars` | map[string]*any* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | -| `Reference` | **string* | :heavy_minus_sign: | Reference to attach to the generated transaction | order_1234 | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/scriptresponse.md b/components/ledger/pkg/client/docs/models/components/scriptresponse.md deleted file mode 100644 index 388b2fe938..0000000000 --- a/components/ledger/pkg/client/docs/models/components/scriptresponse.md +++ /dev/null @@ -1,11 +0,0 @@ -# ScriptResponse - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `ErrorCode` | [*components.ErrorsEnum](../../models/components/errorsenum.md) | :heavy_minus_sign: | N/A | INSUFFICIENT_FUND | -| `ErrorMessage` | **string* | :heavy_minus_sign: | N/A | account had insufficient funds | -| `Details` | **string* | :heavy_minus_sign: | N/A | https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 | -| `Transaction` | [*components.Transaction](../../models/components/transaction.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/security.md b/components/ledger/pkg/client/docs/models/components/security.md deleted file mode 100644 index 051560923a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/security.md +++ /dev/null @@ -1,10 +0,0 @@ -# Security - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | -| `ClientID` | *string* | :heavy_check_mark: | N/A | | -| `ClientSecret` | *string* | :heavy_check_mark: | N/A | | -| `TokenURL` | *string* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/state.md b/components/ledger/pkg/client/docs/models/components/state.md deleted file mode 100644 index fa26cf56b0..0000000000 --- a/components/ledger/pkg/client/docs/models/components/state.md +++ /dev/null @@ -1,9 +0,0 @@ -# State - - -## Values - -| Name | Value | -| ----------- | ----------- | -| `StateToDo` | TO DO | -| `StateDone` | DONE | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/stats.md b/components/ledger/pkg/client/docs/models/components/stats.md deleted file mode 100644 index c87615bc04..0000000000 --- a/components/ledger/pkg/client/docs/models/components/stats.md +++ /dev/null @@ -1,9 +0,0 @@ -# Stats - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `Accounts` | *int64* | :heavy_check_mark: | N/A | -| `Transactions` | *int64* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/statsresponse.md b/components/ledger/pkg/client/docs/models/components/statsresponse.md deleted file mode 100644 index 2b70b37baa..0000000000 --- a/components/ledger/pkg/client/docs/models/components/statsresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# StatsResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | -| `Data` | [components.Stats](../../models/components/stats.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/storage.md b/components/ledger/pkg/client/docs/models/components/storage.md deleted file mode 100644 index 2fb03efeeb..0000000000 --- a/components/ledger/pkg/client/docs/models/components/storage.md +++ /dev/null @@ -1,8 +0,0 @@ -# Storage - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `Migrations` | [][components.MigrationInfo](../../models/components/migrationinfo.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transaction.md b/components/ledger/pkg/client/docs/models/components/transaction.md deleted file mode 100644 index 757c27e69e..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transaction.md +++ /dev/null @@ -1,14 +0,0 @@ -# Transaction - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `Timestamp` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | -| `Postings` | [][components.Posting](../../models/components/posting.md) | :heavy_check_mark: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | | -| `Txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | | -| `PreCommitVolumes` | map[string]map[string][components.Volume](../../models/components/volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | -| `PostCommitVolumes` | map[string]map[string][components.Volume](../../models/components/volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactiondata.md b/components/ledger/pkg/client/docs/models/components/transactiondata.md deleted file mode 100644 index 97c958d3c7..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactiondata.md +++ /dev/null @@ -1,11 +0,0 @@ -# TransactionData - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | -| `Postings` | [][components.Posting](../../models/components/posting.md) | :heavy_check_mark: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | N/A | | -| `Timestamp` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactionresponse.md b/components/ledger/pkg/client/docs/models/components/transactionresponse.md deleted file mode 100644 index 345e2c5363..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# TransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | -| `Data` | [components.Transaction](../../models/components/transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactions.md b/components/ledger/pkg/client/docs/models/components/transactions.md deleted file mode 100644 index 6ca34cb05d..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactions.md +++ /dev/null @@ -1,8 +0,0 @@ -# Transactions - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| `Transactions` | [][components.TransactionData](../../models/components/transactiondata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactionscursorresponse.md b/components/ledger/pkg/client/docs/models/components/transactionscursorresponse.md deleted file mode 100644 index 3fd945384c..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactionscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# TransactionsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `Cursor` | [components.TransactionsCursorResponseCursor](../../models/components/transactionscursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactionscursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/transactionscursorresponsecursor.md deleted file mode 100644 index d3ed51be33..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactionscursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# TransactionsCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.Transaction](../../models/components/transaction.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/transactionsresponse.md b/components/ledger/pkg/client/docs/models/components/transactionsresponse.md deleted file mode 100644 index d1e0e5c884..0000000000 --- a/components/ledger/pkg/client/docs/models/components/transactionsresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# TransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `Data` | [][components.Transaction](../../models/components/transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/type.md b/components/ledger/pkg/client/docs/models/components/type.md deleted file mode 100644 index 6630c645ed..0000000000 --- a/components/ledger/pkg/client/docs/models/components/type.md +++ /dev/null @@ -1,9 +0,0 @@ -# Type - - -## Values - -| Name | Value | -| -------------------- | -------------------- | -| `TypeNewTransaction` | NEW_TRANSACTION | -| `TypeSetMetadata` | SET_METADATA | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2account.md b/components/ledger/pkg/client/docs/models/components/v2account.md deleted file mode 100644 index 91b9e84361..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2account.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2Account - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `Address` | *string* | :heavy_check_mark: | N/A | users:001 | -| `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | {
"admin": "true"
} | -| `Volumes` | map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"USD": {
"input": 100,
"output": 10,
"balance": 90
},
"EUR": {
"input": 100,
"output": 10,
"balance": 90
}
} | -| `EffectiveVolumes` | map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"USD": {
"input": 100,
"output": 10,
"balance": 90
},
"EUR": {
"input": 100,
"output": 10,
"balance": 90
}
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2accountresponse.md b/components/ledger/pkg/client/docs/models/components/v2accountresponse.md deleted file mode 100644 index 44ce771c4e..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2accountresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2AccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `Data` | [components.V2Account](../../models/components/v2account.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2accountscursorresponse.md b/components/ledger/pkg/client/docs/models/components/v2accountscursorresponse.md deleted file mode 100644 index feed6ea0c4..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2accountscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2AccountsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | -| `Cursor` | [components.V2AccountsCursorResponseCursor](../../models/components/v2accountscursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2accountscursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/v2accountscursorresponsecursor.md deleted file mode 100644 index f863929984..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2accountscursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2AccountsCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.V2Account](../../models/components/v2account.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2aggregatebalancesresponse.md b/components/ledger/pkg/client/docs/models/components/v2aggregatebalancesresponse.md deleted file mode 100644 index cc5cd6e777..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2aggregatebalancesresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2AggregateBalancesResponse - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | -| `Data` | map[string][*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | {
"USD": 100,
"EUR": 12
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelement.md b/components/ledger/pkg/client/docs/models/components/v2bulkelement.md deleted file mode 100644 index b13faa8881..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelement.md +++ /dev/null @@ -1,29 +0,0 @@ -# V2BulkElement - - -## Supported Types - -### V2BulkElementCreateTransaction - -```go -v2BulkElement := components.CreateV2BulkElementCreateTransaction(components.V2BulkElementCreateTransaction{/* values here */}) -``` - -### V2BulkElementAddMetadata - -```go -v2BulkElement := components.CreateV2BulkElementAddMetadata(components.V2BulkElementAddMetadata{/* values here */}) -``` - -### V2BulkElementRevertTransaction - -```go -v2BulkElement := components.CreateV2BulkElementRevertTransaction(components.V2BulkElementRevertTransaction{/* values here */}) -``` - -### V2BulkElementDeleteMetadata - -```go -v2BulkElement := components.CreateV2BulkElementDeleteMetadata(components.V2BulkElementDeleteMetadata{/* values here */}) -``` - diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementaddmetadata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementaddmetadata.md deleted file mode 100644 index ac1e01fbaa..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementaddmetadata.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementAddMetadata - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------- | -| `Action` | *string* | :heavy_check_mark: | N/A | -| `Ik` | **string* | :heavy_minus_sign: | N/A | -| `Data` | [*components.Data](../../models/components/data.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementcreatetransaction.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementcreatetransaction.md deleted file mode 100644 index 37065f1a73..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementcreatetransaction.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementCreateTransaction - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| `Action` | *string* | :heavy_check_mark: | N/A | -| `Ik` | **string* | :heavy_minus_sign: | N/A | -| `Data` | [*components.V2PostTransaction](../../models/components/v2posttransaction.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadata.md deleted file mode 100644 index 4119dbcfb3..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadata.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementDeleteMetadata - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `Action` | *string* | :heavy_check_mark: | N/A | -| `Ik` | **string* | :heavy_minus_sign: | N/A | -| `Data` | [*components.V2BulkElementDeleteMetadataData](../../models/components/v2bulkelementdeletemetadatadata.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadatadata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadatadata.md deleted file mode 100644 index eecc527e54..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementdeletemetadatadata.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementDeleteMetadataData - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `TargetID` | [components.V2TargetID](../../models/components/v2targetid.md) | :heavy_check_mark: | N/A | -| `TargetType` | [components.V2TargetType](../../models/components/v2targettype.md) | :heavy_check_mark: | N/A | -| `Key` | *string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresult.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresult.md deleted file mode 100644 index 73f93d35a0..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresult.md +++ /dev/null @@ -1,35 +0,0 @@ -# V2BulkElementResult - - -## Supported Types - -### V2BulkElementResultCreateTransaction - -```go -v2BulkElementResult := components.CreateV2BulkElementResultCreateTransaction(components.V2BulkElementResultCreateTransaction{/* values here */}) -``` - -### V2BulkElementResultAddMetadata - -```go -v2BulkElementResult := components.CreateV2BulkElementResultAddMetadata(components.V2BulkElementResultAddMetadata{/* values here */}) -``` - -### V2BulkElementResultRevertTransaction - -```go -v2BulkElementResult := components.CreateV2BulkElementResultRevertTransaction(components.V2BulkElementResultRevertTransaction{/* values here */}) -``` - -### V2BulkElementResultDeleteMetadata - -```go -v2BulkElementResult := components.CreateV2BulkElementResultDeleteMetadata(components.V2BulkElementResultDeleteMetadata{/* values here */}) -``` - -### V2BulkElementResultError - -```go -v2BulkElementResult := components.CreateV2BulkElementResultError(components.V2BulkElementResultError{/* values here */}) -``` - diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultaddmetadata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresultaddmetadata.md deleted file mode 100644 index 5f8d1fefb2..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultaddmetadata.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2BulkElementResultAddMetadata - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `ResponseType` | *string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultcreatetransaction.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresultcreatetransaction.md deleted file mode 100644 index eac3e3d86a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultcreatetransaction.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2BulkElementResultCreateTransaction - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `ResponseType` | *string* | :heavy_check_mark: | N/A | -| `Data` | [components.V2Transaction](../../models/components/v2transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultdeletemetadata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresultdeletemetadata.md deleted file mode 100644 index 40a8844207..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultdeletemetadata.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2BulkElementResultDeleteMetadata - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `ResponseType` | *string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresulterror.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresulterror.md deleted file mode 100644 index 1fdefd1253..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresulterror.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2BulkElementResultError - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `ResponseType` | *string* | :heavy_check_mark: | N/A | -| `ErrorCode` | *string* | :heavy_check_mark: | N/A | -| `ErrorDescription` | *string* | :heavy_check_mark: | N/A | -| `ErrorDetails` | **string* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultreverttransaction.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementresultreverttransaction.md deleted file mode 100644 index b85016b600..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementresultreverttransaction.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2BulkElementResultRevertTransaction - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `ResponseType` | *string* | :heavy_check_mark: | N/A | -| `Data` | [components.V2Transaction](../../models/components/v2transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransaction.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransaction.md deleted file mode 100644 index 189d5711ef..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransaction.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementRevertTransaction - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `Action` | *string* | :heavy_check_mark: | N/A | -| `Ik` | **string* | :heavy_minus_sign: | N/A | -| `Data` | [*components.V2BulkElementRevertTransactionData](../../models/components/v2bulkelementreverttransactiondata.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransactiondata.md b/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransactiondata.md deleted file mode 100644 index b15a498c55..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkelementreverttransactiondata.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2BulkElementRevertTransactionData - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Force` | **bool* | :heavy_minus_sign: | N/A | -| `AtEffectiveDate` | **bool* | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2bulkresponse.md b/components/ledger/pkg/client/docs/models/components/v2bulkresponse.md deleted file mode 100644 index 64c4956603..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2bulkresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2BulkResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| `Data` | [][components.V2BulkElementResult](../../models/components/v2bulkelementresult.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2configinforesponse.md b/components/ledger/pkg/client/docs/models/components/v2configinforesponse.md deleted file mode 100644 index 146140e08a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2configinforesponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ConfigInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `Server` | *string* | :heavy_check_mark: | N/A | -| `Version` | *string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2createledgerrequest.md b/components/ledger/pkg/client/docs/models/components/v2createledgerrequest.md deleted file mode 100644 index af6e8fd58c..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2createledgerrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CreateLedgerRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Bucket` | **string* | :heavy_minus_sign: | N/A | | -| `Metadata` | map[string]*string* | :heavy_minus_sign: | N/A | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2createtransactionresponse.md b/components/ledger/pkg/client/docs/models/components/v2createtransactionresponse.md deleted file mode 100644 index d5a3954e4f..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2createtransactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2CreateTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `Data` | [components.V2Transaction](../../models/components/v2transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2errorsenum.md b/components/ledger/pkg/client/docs/models/components/v2errorsenum.md deleted file mode 100644 index 088a86b2a0..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2errorsenum.md +++ /dev/null @@ -1,19 +0,0 @@ -# V2ErrorsEnum - - -## Values - -| Name | Value | -| ------------------------------- | ------------------------------- | -| `V2ErrorsEnumInternal` | INTERNAL | -| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | -| `V2ErrorsEnumValidation` | VALIDATION | -| `V2ErrorsEnumConflict` | CONFLICT | -| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED | -| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | -| `V2ErrorsEnumNotFound` | NOT_FOUND | -| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING | -| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT | -| `V2ErrorsEnumNoPostings` | NO_POSTINGS | -| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND | -| `V2ErrorsEnumImport` | IMPORT | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2expandedtransaction.md b/components/ledger/pkg/client/docs/models/components/v2expandedtransaction.md deleted file mode 100644 index 6df0fa3a42..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2expandedtransaction.md +++ /dev/null @@ -1,15 +0,0 @@ -# V2ExpandedTransaction - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `Timestamp` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | -| `Postings` | [][components.V2Posting](../../models/components/v2posting.md) | :heavy_check_mark: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | {
"admin": "true"
} | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | | -| `Reverted` | *bool* | :heavy_check_mark: | N/A | | -| `PreCommitVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | -| `PostCommitVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2getledgerresponse.md b/components/ledger/pkg/client/docs/models/components/v2getledgerresponse.md deleted file mode 100644 index bb0eae9315..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2getledgerresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2GetLedgerResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- | -| `Data` | [components.V2Ledger](../../models/components/v2ledger.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2gettransactionresponse.md b/components/ledger/pkg/client/docs/models/components/v2gettransactionresponse.md deleted file mode 100644 index 08f98f748a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2gettransactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2GetTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | -| `Data` | [components.V2ExpandedTransaction](../../models/components/v2expandedtransaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledger.md b/components/ledger/pkg/client/docs/models/components/v2ledger.md deleted file mode 100644 index 149a5a56b1..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledger.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2Ledger - - -## Fields - -| Field | Type | Required | Description | Example | -| ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | -| `Name` | *string* | :heavy_check_mark: | N/A | | -| `AddedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | -| `Bucket` | *string* | :heavy_check_mark: | N/A | | -| `Metadata` | map[string]*string* | :heavy_minus_sign: | N/A | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledgerinfo.md b/components/ledger/pkg/client/docs/models/components/v2ledgerinfo.md deleted file mode 100644 index a83c6dcfe6..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledgerinfo.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2LedgerInfo - - -## Fields - -| Field | Type | Required | Description | Example | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `Name` | **string* | :heavy_minus_sign: | N/A | ledger001 | -| `Storage` | [*components.V2LedgerInfoStorage](../../models/components/v2ledgerinfostorage.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledgerinforesponse.md b/components/ledger/pkg/client/docs/models/components/v2ledgerinforesponse.md deleted file mode 100644 index bc3ded5267..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledgerinforesponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2LedgerInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------- | -| `Data` | [*components.V2LedgerInfo](../../models/components/v2ledgerinfo.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledgerinfostorage.md b/components/ledger/pkg/client/docs/models/components/v2ledgerinfostorage.md deleted file mode 100644 index 1eb38ab77b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledgerinfostorage.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2LedgerInfoStorage - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| `Migrations` | [][components.V2MigrationInfo](../../models/components/v2migrationinfo.md) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponse.md b/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponse.md deleted file mode 100644 index 4e52f4b84a..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2LedgerListResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| `Cursor` | [components.V2LedgerListResponseCursor](../../models/components/v2ledgerlistresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponsecursor.md b/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponsecursor.md deleted file mode 100644 index 12e998338f..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2ledgerlistresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2LedgerListResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.V2Ledger](../../models/components/v2ledger.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2log.md b/components/ledger/pkg/client/docs/models/components/v2log.md deleted file mode 100644 index ae3fd7b22b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2log.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2Log - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | 1234 | -| `Type` | [components.V2LogType](../../models/components/v2logtype.md) | :heavy_check_mark: | N/A | | -| `Data` | map[string]*any* | :heavy_check_mark: | N/A | | -| `Hash` | *string* | :heavy_check_mark: | N/A | 9ee060170400f556b7e1575cb13f9db004f150a08355c7431c62bc639166431e | -| `Date` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2logscursorresponse.md b/components/ledger/pkg/client/docs/models/components/v2logscursorresponse.md deleted file mode 100644 index 8d564f7535..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2logscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2LogsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| `Cursor` | [components.V2LogsCursorResponseCursor](../../models/components/v2logscursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2logscursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/v2logscursorresponsecursor.md deleted file mode 100644 index 2134beb488..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2logscursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2LogsCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.V2Log](../../models/components/v2log.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2logtype.md b/components/ledger/pkg/client/docs/models/components/v2logtype.md deleted file mode 100644 index 28b0b930c6..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2logtype.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2LogType - - -## Values - -| Name | Value | -| ------------------------------ | ------------------------------ | -| `V2LogTypeNewTransaction` | NEW_TRANSACTION | -| `V2LogTypeSetMetadata` | SET_METADATA | -| `V2LogTypeRevertedTransaction` | REVERTED_TRANSACTION | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2migrationinfo.md b/components/ledger/pkg/client/docs/models/components/v2migrationinfo.md deleted file mode 100644 index 1990948833..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2migrationinfo.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2MigrationInfo - - -## Fields - -| Field | Type | Required | Description | Example | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `Version` | **int64* | :heavy_minus_sign: | N/A | 11 | -| `Name` | **string* | :heavy_minus_sign: | N/A | migrations:001 | -| `Date` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `State` | [*components.V2MigrationInfoState](../../models/components/v2migrationinfostate.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2migrationinfostate.md b/components/ledger/pkg/client/docs/models/components/v2migrationinfostate.md deleted file mode 100644 index 1250def69b..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2migrationinfostate.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2MigrationInfoState - - -## Values - -| Name | Value | -| -------------------------- | -------------------------- | -| `V2MigrationInfoStateToDo` | TO DO | -| `V2MigrationInfoStateDone` | DONE | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2posting.md b/components/ledger/pkg/client/docs/models/components/v2posting.md deleted file mode 100644 index 9fa0277b40..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2posting.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2Posting - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Amount` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | 100 | -| `Asset` | *string* | :heavy_check_mark: | N/A | COIN | -| `Destination` | *string* | :heavy_check_mark: | N/A | users:002 | -| `Source` | *string* | :heavy_check_mark: | N/A | users:001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2posttransaction.md b/components/ledger/pkg/client/docs/models/components/v2posttransaction.md deleted file mode 100644 index c156fcaba7..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2posttransaction.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2PostTransaction - - -## Fields - -| Field | Type | Required | Description | Example | -| ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `Timestamp` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `Postings` | [][components.V2Posting](../../models/components/v2posting.md) | :heavy_minus_sign: | N/A | | -| `Script` | [*components.V2PostTransactionScript](../../models/components/v2posttransactionscript.md) | :heavy_minus_sign: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2posttransactionscript.md b/components/ledger/pkg/client/docs/models/components/v2posttransactionscript.md deleted file mode 100644 index 657c7dd0ea..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2posttransactionscript.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2PostTransactionScript - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| -| `Vars` | map[string]*any* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2reverttransactionresponse.md b/components/ledger/pkg/client/docs/models/components/v2reverttransactionresponse.md deleted file mode 100644 index db65ff1f7c..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2reverttransactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2RevertTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `Data` | [components.V2Transaction](../../models/components/v2transaction.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2stats.md b/components/ledger/pkg/client/docs/models/components/v2stats.md deleted file mode 100644 index 15e788cd0f..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2stats.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2Stats - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Accounts` | *int64* | :heavy_check_mark: | N/A | -| `Transactions` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2statsresponse.md b/components/ledger/pkg/client/docs/models/components/v2statsresponse.md deleted file mode 100644 index ebb1716406..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2statsresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2StatsResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `Data` | [components.V2Stats](../../models/components/v2stats.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2targetid.md b/components/ledger/pkg/client/docs/models/components/v2targetid.md deleted file mode 100644 index d10de04d2d..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2targetid.md +++ /dev/null @@ -1,17 +0,0 @@ -# V2TargetID - - -## Supported Types - -### - -```go -v2TargetID := components.CreateV2TargetIDStr(string{/* values here */}) -``` - -### - -```go -v2TargetID := components.CreateV2TargetIDBigint(*big.Int{/* values here */}) -``` - diff --git a/components/ledger/pkg/client/docs/models/components/v2targettype.md b/components/ledger/pkg/client/docs/models/components/v2targettype.md deleted file mode 100644 index e50c571508..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2targettype.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2TargetType - - -## Values - -| Name | Value | -| ------------------------- | ------------------------- | -| `V2TargetTypeTransaction` | TRANSACTION | -| `V2TargetTypeAccount` | ACCOUNT | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2transaction.md b/components/ledger/pkg/client/docs/models/components/v2transaction.md deleted file mode 100644 index 87c7060d13..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2transaction.md +++ /dev/null @@ -1,13 +0,0 @@ -# V2Transaction - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | -| `Timestamp` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | N/A | | -| `Postings` | [][components.V2Posting](../../models/components/v2posting.md) | :heavy_check_mark: | N/A | | -| `Reference` | **string* | :heavy_minus_sign: | N/A | ref:001 | -| `Metadata` | map[string]*string* | :heavy_check_mark: | N/A | {
"admin": "true"
} | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | | -| `Reverted` | *bool* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponse.md b/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponse.md deleted file mode 100644 index 7f2b797fd3..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2TransactionsCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `Cursor` | [components.V2TransactionsCursorResponseCursor](../../models/components/v2transactionscursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponsecursor.md deleted file mode 100644 index bb2df69952..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2transactionscursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2TransactionsCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.V2ExpandedTransaction](../../models/components/v2expandedtransaction.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2volume.md b/components/ledger/pkg/client/docs/models/components/v2volume.md deleted file mode 100644 index 1b6c643e56..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2volume.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2Volume - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Input` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Output` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Balance` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalance.md b/components/ledger/pkg/client/docs/models/components/v2volumeswithbalance.md deleted file mode 100644 index a76f609f06..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalance.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2VolumesWithBalance - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Account` | *string* | :heavy_check_mark: | N/A | -| `Asset` | *string* | :heavy_check_mark: | N/A | -| `Input` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Output` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Balance` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponse.md b/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponse.md deleted file mode 100644 index 29fc052934..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2VolumesWithBalanceCursorResponse - - -## Fields - -| Field | Type | Required | Description | -| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `Cursor` | [components.V2VolumesWithBalanceCursorResponseCursor](../../models/components/v2volumeswithbalancecursorresponsecursor.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponsecursor.md b/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponsecursor.md deleted file mode 100644 index 4145611d31..0000000000 --- a/components/ledger/pkg/client/docs/models/components/v2volumeswithbalancecursorresponsecursor.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2VolumesWithBalanceCursorResponseCursor - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | -| `PageSize` | *int64* | :heavy_check_mark: | N/A | 15 | -| `HasMore` | *bool* | :heavy_check_mark: | N/A | false | -| `Previous` | **string* | :heavy_minus_sign: | N/A | YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= | -| `Next` | **string* | :heavy_minus_sign: | N/A | | -| `Data` | [][components.V2VolumesWithBalance](../../models/components/v2volumeswithbalance.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/components/volume.md b/components/ledger/pkg/client/docs/models/components/volume.md deleted file mode 100644 index 5e2ea51fbd..0000000000 --- a/components/ledger/pkg/client/docs/models/components/volume.md +++ /dev/null @@ -1,10 +0,0 @@ -# Volume - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Input` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Output` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | N/A | -| `Balance` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_minus_sign: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionrequest.md deleted file mode 100644 index 1bf49cc467..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# AddMetadataOnTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | metadata | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionresponse.md deleted file mode 100644 index f213b79b16..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/addmetadataontransactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# AddMetadataOnTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountrequest.md b/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountrequest.md deleted file mode 100644 index d2eabe2a86..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# AddMetadataToAccountRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `RequestBody` | map[string]*any* | :heavy_check_mark: | metadata | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountresponse.md b/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountresponse.md deleted file mode 100644 index 44d783abe4..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/addmetadatatoaccountresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# AddMetadataToAccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/countaccountsrequest.md b/components/ledger/pkg/client/docs/models/operations/countaccountsrequest.md deleted file mode 100644 index e280363ac4..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/countaccountsrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# CountAccountsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | **string* | :heavy_minus_sign: | Filter accounts by address pattern (regular expression placed between ^ and $). | users:.+ | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | Filter accounts by metadata key value pairs. The filter can be used like this metadata[key]=value1&metadata[a.nested.key]=value2 | metadata[key]=value1&metadata[a.nested.key]=value2 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/countaccountsresponse.md b/components/ledger/pkg/client/docs/models/operations/countaccountsresponse.md deleted file mode 100644 index 50644f1438..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/countaccountsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# CountAccountsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/counttransactionsrequest.md b/components/ledger/pkg/client/docs/models/operations/counttransactionsrequest.md deleted file mode 100644 index 400c002438..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/counttransactionsrequest.md +++ /dev/null @@ -1,15 +0,0 @@ -# CountTransactionsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Reference` | **string* | :heavy_minus_sign: | Filter transactions by reference field. | ref:001 | -| `Account` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). | users:001 | -| `Source` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account at source (regular expression placed between ^ and $). | users:001 | -| `Destination` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). | users:001 | -| `StartTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred after this timestamp.
The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute).
| | -| `EndTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred before this timestamp.
The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute).
| | -| `Metadata` | [*operations.Metadata](../../models/operations/metadata.md) | :heavy_minus_sign: | Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. | metadata[key]=value1&metadata[a.nested.key]=value2 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/counttransactionsresponse.md b/components/ledger/pkg/client/docs/models/operations/counttransactionsresponse.md deleted file mode 100644 index 4c265d393a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/counttransactionsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# CountTransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/createtransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/createtransactionrequest.md deleted file mode 100644 index 8419ddf60c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/createtransactionrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# CreateTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Preview` | **bool* | :heavy_minus_sign: | Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `PostTransaction` | [components.PostTransaction](../../models/components/posttransaction.md) | :heavy_check_mark: | The request body must contain at least one of the following objects:
- `postings`: suitable for simple transactions
- `script`: enabling more complex transactions with Numscript
| | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/createtransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/createtransactionresponse.md deleted file mode 100644 index 4c63523b69..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/createtransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# CreateTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `TransactionsResponse` | [*components.TransactionsResponse](../../models/components/transactionsresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/createtransactionsrequest.md b/components/ledger/pkg/client/docs/models/operations/createtransactionsrequest.md deleted file mode 100644 index 319ff03d6d..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/createtransactionsrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# CreateTransactionsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Transactions` | [components.Transactions](../../models/components/transactions.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/createtransactionsresponse.md b/components/ledger/pkg/client/docs/models/operations/createtransactionsresponse.md deleted file mode 100644 index c8e70b3d8c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/createtransactionsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# CreateTransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `TransactionsResponse` | [*components.TransactionsResponse](../../models/components/transactionsresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getaccountrequest.md b/components/ledger/pkg/client/docs/models/operations/getaccountrequest.md deleted file mode 100644 index ef3de9390d..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getaccountrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetAccountRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getaccountresponse.md b/components/ledger/pkg/client/docs/models/operations/getaccountresponse.md deleted file mode 100644 index 6391542c3d..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getaccountresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetAccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `AccountResponse` | [*components.AccountResponse](../../models/components/accountresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedrequest.md b/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedrequest.md deleted file mode 100644 index 316ff70bc8..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# GetBalancesAggregatedRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | **string* | :heavy_minus_sign: | Filter balances involving given account, either as source or destination. | users:001 | -| `UseInsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedresponse.md b/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedresponse.md deleted file mode 100644 index 84e8d37968..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getbalancesaggregatedresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetBalancesAggregatedResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `AggregateBalancesResponse` | [*components.AggregateBalancesResponse](../../models/components/aggregatebalancesresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getbalancesrequest.md b/components/ledger/pkg/client/docs/models/operations/getbalancesrequest.md deleted file mode 100644 index e1e57da71a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getbalancesrequest.md +++ /dev/null @@ -1,12 +0,0 @@ -# GetBalancesRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | **string* | :heavy_minus_sign: | Filter balances involving given account, either as source or destination. | users:001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| | -| `After` | **string* | :heavy_minus_sign: | Pagination cursor, will return accounts after given address, in descending order. | users:003 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 1000.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getbalancesresponse.md b/components/ledger/pkg/client/docs/models/operations/getbalancesresponse.md deleted file mode 100644 index b605c68b08..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getbalancesresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetBalancesResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `BalancesCursorResponse` | [*components.BalancesCursorResponse](../../models/components/balancescursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getinforesponse.md b/components/ledger/pkg/client/docs/models/operations/getinforesponse.md deleted file mode 100644 index 1f1abf2bd1..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getinforesponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `ConfigInfoResponse` | [*components.ConfigInfoResponse](../../models/components/configinforesponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getledgerinforequest.md b/components/ledger/pkg/client/docs/models/operations/getledgerinforequest.md deleted file mode 100644 index f45b795ddd..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getledgerinforequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# GetLedgerInfoRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getledgerinforesponse.md b/components/ledger/pkg/client/docs/models/operations/getledgerinforesponse.md deleted file mode 100644 index 94e5c04359..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getledgerinforesponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetLedgerInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `LedgerInfoResponse` | [*components.LedgerInfoResponse](../../models/components/ledgerinforesponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getmappingrequest.md b/components/ledger/pkg/client/docs/models/operations/getmappingrequest.md deleted file mode 100644 index 1399e12ecc..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getmappingrequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# GetMappingRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/getmappingresponse.md b/components/ledger/pkg/client/docs/models/operations/getmappingresponse.md deleted file mode 100644 index 548ff90db0..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/getmappingresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetMappingResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `MappingResponse` | [*components.MappingResponse](../../models/components/mappingresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/gettransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/gettransactionrequest.md deleted file mode 100644 index 9b7bcbcb66..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/gettransactionrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/gettransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/gettransactionresponse.md deleted file mode 100644 index 8940578609..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/gettransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# GetTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `TransactionResponse` | [*components.TransactionResponse](../../models/components/transactionresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listaccountsrequest.md b/components/ledger/pkg/client/docs/models/operations/listaccountsrequest.md deleted file mode 100644 index af41b328eb..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listaccountsrequest.md +++ /dev/null @@ -1,15 +0,0 @@ -# ListAccountsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `After` | **string* | :heavy_minus_sign: | Pagination cursor, will return accounts after given address, in descending order. | users:003 | -| `Address` | **string* | :heavy_minus_sign: | Filter accounts by address pattern (regular expression placed between ^ and $). | users:.+ | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | Filter accounts by metadata key value pairs. Nested objects can be used as seen in the example below. | metadata[key]=value1&metadata[a.nested.key]=value2 | -| `Balance` | **int64* | :heavy_minus_sign: | Filter accounts by their balance (default operator is gte) | 2400 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 1000.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| ~~`PaginationToken`~~ | **string* | :heavy_minus_sign: | : warning: ** DEPRECATED **: This will be removed in a future release, please migrate away from it as soon as possible.

Parameter used in pagination requests. Maximum page size is set to 1000.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
Deprecated, please use `cursor` instead.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listaccountsresponse.md b/components/ledger/pkg/client/docs/models/operations/listaccountsresponse.md deleted file mode 100644 index 62f1327e19..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listaccountsresponse.md +++ /dev/null @@ -1,10 +0,0 @@ -# ListAccountsResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `AccountsCursorResponse` | [*components.AccountsCursorResponse](../../models/components/accountscursorresponse.md) | :heavy_minus_sign: | OK | -| `ErrorResponse` | **sdkerrors.ErrorResponse* | :heavy_minus_sign: | Not found | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listlogsrequest.md b/components/ledger/pkg/client/docs/models/operations/listlogsrequest.md deleted file mode 100644 index dc551c20de..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listlogsrequest.md +++ /dev/null @@ -1,13 +0,0 @@ -# ListLogsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `After` | **string* | :heavy_minus_sign: | Pagination cursor, will return the logs after a given ID. (in descending order). | 1234 | -| `StartTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred after this timestamp.
The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute).
| | -| `EndTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred before this timestamp.
The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute).
| | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 1000.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listlogsresponse.md b/components/ledger/pkg/client/docs/models/operations/listlogsresponse.md deleted file mode 100644 index b6a7f89826..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listlogsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# ListLogsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `LogsCursorResponse` | [*components.LogsCursorResponse](../../models/components/logscursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listtransactionsrequest.md b/components/ledger/pkg/client/docs/models/operations/listtransactionsrequest.md deleted file mode 100644 index 5744daa73a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listtransactionsrequest.md +++ /dev/null @@ -1,18 +0,0 @@ -# ListTransactionsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `After` | **string* | :heavy_minus_sign: | Pagination cursor, will return transactions after given txid (in descending order). | 1234 | -| `Reference` | **string* | :heavy_minus_sign: | Find transactions by reference field. | ref:001 | -| `Account` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). | users:001 | -| `Source` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account at source (regular expression placed between ^ and $). | users:001 | -| `Destination` | **string* | :heavy_minus_sign: | Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). | users:001 | -| `StartTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred after this timestamp.
The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute).
| | -| `EndTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | Filter transactions that occurred before this timestamp.
The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute).
| | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 1000.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `Metadata` | map[string]*any* | :heavy_minus_sign: | Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/listtransactionsresponse.md b/components/ledger/pkg/client/docs/models/operations/listtransactionsresponse.md deleted file mode 100644 index 028f53ff50..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/listtransactionsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# ListTransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `TransactionsCursorResponse` | [*components.TransactionsCursorResponse](../../models/components/transactionscursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/metadata.md b/components/ledger/pkg/client/docs/models/operations/metadata.md deleted file mode 100644 index 0f347d94ec..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/metadata.md +++ /dev/null @@ -1,9 +0,0 @@ -# Metadata - -Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. - - -## Fields - -| Field | Type | Required | Description | -| ----------- | ----------- | ----------- | ----------- | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/option.md b/components/ledger/pkg/client/docs/models/operations/option.md deleted file mode 100644 index 35d8a1e2ad..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/option.md +++ /dev/null @@ -1,37 +0,0 @@ -## Options - -### WithServerURL - -WithServerURL allows providing an alternative server URL. - -```go -operations.WithServerURL("http://api.example.com") -``` - -## WithTemplatedServerURL - -WithTemplatedServerURL allows providing an alternative server URL with templated parameters. - -```go -operations.WithTemplatedServerURL("http://{host}:{port}", map[string]string{ - "host": "api.example.com", - "port": "8080", -}) -``` - -### WithRetries - -WithRetries allows customizing the default retry configuration. Only usable with methods that mention they support retries. - -```go -operations.WithRetries(retry.Config{ - Strategy: "backoff", - Backoff: retry.BackoffStrategy{ - InitialInterval: 500 * time.Millisecond, - MaxInterval: 60 * time.Second, - Exponent: 1.5, - MaxElapsedTime: 5 * time.Minute, - }, - RetryConnectionErrors: true, -}) -``` \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/order.md b/components/ledger/pkg/client/docs/models/operations/order.md deleted file mode 100644 index b29bc2b6b5..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/order.md +++ /dev/null @@ -1,8 +0,0 @@ -# Order - - -## Values - -| Name | Value | -| ---------------- | ---------------- | -| `OrderEffective` | effective | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/readstatsrequest.md b/components/ledger/pkg/client/docs/models/operations/readstatsrequest.md deleted file mode 100644 index 1be5ec7475..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/readstatsrequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# ReadStatsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | -| `Ledger` | *string* | :heavy_check_mark: | name of the ledger | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/readstatsresponse.md b/components/ledger/pkg/client/docs/models/operations/readstatsresponse.md deleted file mode 100644 index bf5012b591..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/readstatsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# ReadStatsResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `StatsResponse` | [*components.StatsResponse](../../models/components/statsresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/reverttransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/reverttransactionrequest.md deleted file mode 100644 index f45867da17..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/reverttransactionrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# RevertTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `DisableChecks` | **bool* | :heavy_minus_sign: | Allow to disable balances checks | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/reverttransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/reverttransactionresponse.md deleted file mode 100644 index 879646dc28..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/reverttransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# RevertTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `TransactionResponse` | [*components.TransactionResponse](../../models/components/transactionresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/runscriptrequest.md b/components/ledger/pkg/client/docs/models/operations/runscriptrequest.md deleted file mode 100644 index 4457efea13..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/runscriptrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# RunScriptRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Preview` | **bool* | :heavy_minus_sign: | Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `Script` | [components.Script](../../models/components/script.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/runscriptresponse.md b/components/ledger/pkg/client/docs/models/operations/runscriptresponse.md deleted file mode 100644 index bbc1646aa7..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/runscriptresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# RunScriptResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `ScriptResponse` | [*components.ScriptResponse](../../models/components/scriptresponse.md) | :heavy_minus_sign: | On success, it will return a 200 status code, and the resulting transaction under the `transaction` field.

On failure, it will also return a 200 status code, and the following fields:
- `details`: contains a URL. When there is an error parsing Numscript, the result can be difficult to read—the provided URL will render the error in an easy-to-read format.
- `errorCode` and `error_code` (deprecated): contains the string code of the error
- `errorMessage` and `error_message` (deprecated): contains a human-readable indication of what went wrong, for example that an account had insufficient funds, or that there was an error in the provided Numscript.
| \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/updatemappingrequest.md b/components/ledger/pkg/client/docs/models/operations/updatemappingrequest.md deleted file mode 100644 index 188c707e48..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/updatemappingrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# UpdateMappingRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Mapping` | [components.Mapping](../../models/components/mapping.md) | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/updatemappingresponse.md b/components/ledger/pkg/client/docs/models/operations/updatemappingresponse.md deleted file mode 100644 index 3d228dd016..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/updatemappingresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# UpdateMappingResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `MappingResponse` | [*components.MappingResponse](../../models/components/mappingresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md deleted file mode 100644 index ac45eb811a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2AddMetadataOnTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `DryRun` | **bool* | :heavy_minus_sign: | Set the dryRun mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | | -| `RequestBody` | map[string]*string* | :heavy_minus_sign: | metadata | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionresponse.md deleted file mode 100644 index e2857cc7fe..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2addmetadataontransactionresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2AddMetadataOnTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md b/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md deleted file mode 100644 index 2d0315b546..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2AddMetadataToAccountRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `DryRun` | **bool* | :heavy_minus_sign: | Set the dry run mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | | -| `RequestBody` | map[string]*string* | :heavy_check_mark: | metadata | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountresponse.md b/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountresponse.md deleted file mode 100644 index 6ddfee1565..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2addmetadatatoaccountresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2AddMetadataToAccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2countaccountsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2countaccountsrequest.md deleted file mode 100644 index fdc614bbe5..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2countaccountsrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2CountAccountsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2countaccountsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2countaccountsresponse.md deleted file mode 100644 index 67f47e3002..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2countaccountsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CountAccountsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2counttransactionsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2counttransactionsrequest.md deleted file mode 100644 index 9e4c5cd880..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2counttransactionsrequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2CountTransactionsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | ------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2counttransactionsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2counttransactionsresponse.md deleted file mode 100644 index bb5a46aed4..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2counttransactionsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CountTransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createbulkrequest.md b/components/ledger/pkg/client/docs/models/operations/v2createbulkrequest.md deleted file mode 100644 index a3531bc224..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createbulkrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CreateBulkRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `RequestBody` | [][components.V2BulkElement](../../models/components/v2bulkelement.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createbulkresponse.md b/components/ledger/pkg/client/docs/models/operations/v2createbulkresponse.md deleted file mode 100644 index 692a7dabbe..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createbulkresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CreateBulkResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2BulkResponse` | [*components.V2BulkResponse](../../models/components/v2bulkresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createledgerrequest.md b/components/ledger/pkg/client/docs/models/operations/v2createledgerrequest.md deleted file mode 100644 index 65276e228c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createledgerrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CreateLedgerRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `V2CreateLedgerRequest` | [*components.V2CreateLedgerRequest](../../models/components/v2createledgerrequest.md) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createledgerresponse.md b/components/ledger/pkg/client/docs/models/operations/v2createledgerresponse.md deleted file mode 100644 index 32651a2d6f..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createledgerresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2CreateLedgerResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createtransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/v2createtransactionrequest.md deleted file mode 100644 index 73fbead129..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createtransactionrequest.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2CreateTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `DryRun` | **bool* | :heavy_minus_sign: | Set the dryRun mode. dry run mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | | -| `V2PostTransaction` | [components.V2PostTransaction](../../models/components/v2posttransaction.md) | :heavy_check_mark: | The request body must contain at least one of the following objects:
- `postings`: suitable for simple transactions
- `script`: enabling more complex transactions with Numscript
| | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2createtransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/v2createtransactionresponse.md deleted file mode 100644 index b895b6b041..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2createtransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2CreateTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2CreateTransactionResponse` | [*components.V2CreateTransactionResponse](../../models/components/v2createtransactionresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadatarequest.md b/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadatarequest.md deleted file mode 100644 index 1a9c58e7fc..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadatarequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2DeleteAccountMetadataRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | *string* | :heavy_check_mark: | Account address | | -| `Key` | *string* | :heavy_check_mark: | The key to remove. | foo | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadataresponse.md b/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadataresponse.md deleted file mode 100644 index 2988236ef3..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deleteaccountmetadataresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2DeleteAccountMetadataResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadatarequest.md b/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadatarequest.md deleted file mode 100644 index be1e38cd1a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadatarequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2DeleteLedgerMetadataRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Key` | *string* | :heavy_check_mark: | Key to remove. | foo | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadataresponse.md b/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadataresponse.md deleted file mode 100644 index b85b317a34..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deleteledgermetadataresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2DeleteLedgerMetadataResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadatarequest.md b/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadatarequest.md deleted file mode 100644 index 4d2ba6adf4..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadatarequest.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2DeleteTransactionMetadataRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `Key` | *string* | :heavy_check_mark: | The key to remove. | foo | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadataresponse.md b/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadataresponse.md deleted file mode 100644 index 37b2b98130..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2deletetransactionmetadataresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2DeleteTransactionMetadataResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2exportlogsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2exportlogsrequest.md deleted file mode 100644 index 5c1c15baab..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2exportlogsrequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2ExportLogsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2exportlogsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2exportlogsresponse.md deleted file mode 100644 index 488d5b56e6..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2exportlogsresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2ExportLogsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getaccountrequest.md b/components/ledger/pkg/client/docs/models/operations/v2getaccountrequest.md deleted file mode 100644 index 1d25646573..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getaccountrequest.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2GetAccountRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `Expand` | **string* | :heavy_minus_sign: | N/A | | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getaccountresponse.md b/components/ledger/pkg/client/docs/models/operations/v2getaccountresponse.md deleted file mode 100644 index 9a9d7e5a3a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getaccountresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetAccountResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2AccountResponse` | [*components.V2AccountResponse](../../models/components/v2accountresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedrequest.md b/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedrequest.md deleted file mode 100644 index 1a6c42684b..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedrequest.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2GetBalancesAggregatedRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `UseInsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedresponse.md b/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedresponse.md deleted file mode 100644 index da586c4565..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getbalancesaggregatedresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetBalancesAggregatedResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2AggregateBalancesResponse` | [*components.V2AggregateBalancesResponse](../../models/components/v2aggregatebalancesresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getinforesponse.md b/components/ledger/pkg/client/docs/models/operations/v2getinforesponse.md deleted file mode 100644 index e7589f89ac..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getinforesponse.md +++ /dev/null @@ -1,10 +0,0 @@ -# V2GetInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2ConfigInfoResponse` | [*components.V2ConfigInfoResponse](../../models/components/v2configinforesponse.md) | :heavy_minus_sign: | OK | -| `V2ErrorResponse` | **sdkerrors.V2ErrorResponse* | :heavy_minus_sign: | Error | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getledgerinforequest.md b/components/ledger/pkg/client/docs/models/operations/v2getledgerinforequest.md deleted file mode 100644 index 8d240702ee..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getledgerinforequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2GetLedgerInfoRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getledgerinforesponse.md b/components/ledger/pkg/client/docs/models/operations/v2getledgerinforesponse.md deleted file mode 100644 index ef1f04482a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getledgerinforesponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetLedgerInfoResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2LedgerInfoResponse` | [*components.V2LedgerInfoResponse](../../models/components/v2ledgerinforesponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getledgerrequest.md b/components/ledger/pkg/client/docs/models/operations/v2getledgerrequest.md deleted file mode 100644 index 4114f4395e..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getledgerrequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2GetLedgerRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getledgerresponse.md b/components/ledger/pkg/client/docs/models/operations/v2getledgerresponse.md deleted file mode 100644 index 3592b788da..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getledgerresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetLedgerResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2GetLedgerResponse` | [*components.V2GetLedgerResponse](../../models/components/v2getledgerresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2gettransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/v2gettransactionrequest.md deleted file mode 100644 index 92b7f458a8..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2gettransactionrequest.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2GetTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | ------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `Expand` | **string* | :heavy_minus_sign: | N/A | | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2gettransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/v2gettransactionresponse.md deleted file mode 100644 index 3d99fcecb0..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2gettransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2GetTransactionResponse` | [*components.V2GetTransactionResponse](../../models/components/v2gettransactionresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md b/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md deleted file mode 100644 index 1d7fa21ded..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesrequest.md +++ /dev/null @@ -1,15 +0,0 @@ -# V2GetVolumesWithBalancesRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `EndTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `StartTime` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `InsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | -| `GroupBy` | **int64* | :heavy_minus_sign: | Group volumes and balance by the level of the segment of the address | 3 | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesresponse.md b/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesresponse.md deleted file mode 100644 index 2bbf1c9bb8..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2getvolumeswithbalancesresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2GetVolumesWithBalancesResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2VolumesWithBalanceCursorResponse` | [*components.V2VolumesWithBalanceCursorResponse](../../models/components/v2volumeswithbalancecursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2importlogsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2importlogsrequest.md deleted file mode 100644 index 605864e81b..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2importlogsrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ImportLogsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `RequestBody` | **string* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2importlogsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2importlogsresponse.md deleted file mode 100644 index 2e24461ea2..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2importlogsresponse.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2ImportLogsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listaccountsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2listaccountsrequest.md deleted file mode 100644 index a55a6bf000..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listaccountsrequest.md +++ /dev/null @@ -1,13 +0,0 @@ -# V2ListAccountsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `Expand` | **string* | :heavy_minus_sign: | N/A | | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listaccountsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2listaccountsresponse.md deleted file mode 100644 index f0c5d3e24c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listaccountsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ListAccountsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2AccountsCursorResponse` | [*components.V2AccountsCursorResponse](../../models/components/v2accountscursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listledgersrequest.md b/components/ledger/pkg/client/docs/models/operations/v2listledgersrequest.md deleted file mode 100644 index 9263467fde..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listledgersrequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ListLedgersRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listledgersresponse.md b/components/ledger/pkg/client/docs/models/operations/v2listledgersresponse.md deleted file mode 100644 index d5c67c727a..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listledgersresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ListLedgersResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2LedgerListResponse` | [*components.V2LedgerListResponse](../../models/components/v2ledgerlistresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listlogsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2listlogsrequest.md deleted file mode 100644 index 19fdb9650d..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listlogsrequest.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2ListLogsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listlogsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2listlogsresponse.md deleted file mode 100644 index 429514bb3c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listlogsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ListLogsResponse - - -## Fields - -| Field | Type | Required | Description | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2LogsCursorResponse` | [*components.V2LogsCursorResponse](../../models/components/v2logscursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listtransactionsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2listtransactionsrequest.md deleted file mode 100644 index eb6f8c5ea3..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listtransactionsrequest.md +++ /dev/null @@ -1,15 +0,0 @@ -# V2ListTransactionsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `PageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `Cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `Expand` | **string* | :heavy_minus_sign: | N/A | | -| `Pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `Order` | [*operations.Order](../../models/operations/order.md) | :heavy_minus_sign: | N/A | | -| `Reverse` | **bool* | :heavy_minus_sign: | N/A | | -| `RequestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2listtransactionsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2listtransactionsresponse.md deleted file mode 100644 index d123903284..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2listtransactionsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ListTransactionsResponse - - -## Fields - -| Field | Type | Required | Description | -| --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2TransactionsCursorResponse` | [*components.V2TransactionsCursorResponse](../../models/components/v2transactionscursorresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2readstatsrequest.md b/components/ledger/pkg/client/docs/models/operations/v2readstatsrequest.md deleted file mode 100644 index 890f0883c5..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2readstatsrequest.md +++ /dev/null @@ -1,8 +0,0 @@ -# V2ReadStatsRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------ | ------------------ | ------------------ | ------------------ | ------------------ | -| `Ledger` | *string* | :heavy_check_mark: | name of the ledger | ledger001 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2readstatsresponse.md b/components/ledger/pkg/client/docs/models/operations/v2readstatsresponse.md deleted file mode 100644 index 26fbd3a291..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2readstatsresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2ReadStatsResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2StatsResponse` | [*components.V2StatsResponse](../../models/components/v2statsresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2reverttransactionrequest.md b/components/ledger/pkg/client/docs/models/operations/v2reverttransactionrequest.md deleted file mode 100644 index 58973420d5..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2reverttransactionrequest.md +++ /dev/null @@ -1,11 +0,0 @@ -# V2RevertTransactionRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `ID` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `Force` | **bool* | :heavy_minus_sign: | Force revert | | -| `AtEffectiveDate` | **bool* | :heavy_minus_sign: | Revert transaction at effective date of the original tx | | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2reverttransactionresponse.md b/components/ledger/pkg/client/docs/models/operations/v2reverttransactionresponse.md deleted file mode 100644 index 9f1a7374cb..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2reverttransactionresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2RevertTransactionResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2RevertTransactionResponse` | [*components.V2RevertTransactionResponse](../../models/components/v2reverttransactionresponse.md) | :heavy_minus_sign: | OK | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadatarequest.md b/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadatarequest.md deleted file mode 100644 index 79b7f1eb5c..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadatarequest.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2UpdateLedgerMetadataRequest - - -## Fields - -| Field | Type | Required | Description | Example | -| ------------------- | ------------------- | ------------------- | ------------------- | ------------------- | -| `Ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `RequestBody` | map[string]*string* | :heavy_minus_sign: | N/A | {
"admin": "true"
} | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadataresponse.md b/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadataresponse.md deleted file mode 100644 index c2e3ced729..0000000000 --- a/components/ledger/pkg/client/docs/models/operations/v2updateledgermetadataresponse.md +++ /dev/null @@ -1,9 +0,0 @@ -# V2UpdateLedgerMetadataResponse - - -## Fields - -| Field | Type | Required | Description | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A | -| `V2ErrorResponse` | **sdkerrors.V2ErrorResponse* | :heavy_minus_sign: | Error | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/sdkerrors/errorresponse.md b/components/ledger/pkg/client/docs/models/sdkerrors/errorresponse.md deleted file mode 100644 index 601e62804b..0000000000 --- a/components/ledger/pkg/client/docs/models/sdkerrors/errorresponse.md +++ /dev/null @@ -1,12 +0,0 @@ -# ErrorResponse - -Error - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `ErrorCode` | [components.ErrorsEnum](../../models/components/errorsenum.md) | :heavy_check_mark: | N/A | INSUFFICIENT_FUND | -| `ErrorMessage` | *string* | :heavy_check_mark: | N/A | [INSUFFICIENT_FUND] account had insufficient funds | -| `Details` | **string* | :heavy_minus_sign: | N/A | https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/models/sdkerrors/v2errorresponse.md b/components/ledger/pkg/client/docs/models/sdkerrors/v2errorresponse.md deleted file mode 100644 index a7810cedd0..0000000000 --- a/components/ledger/pkg/client/docs/models/sdkerrors/v2errorresponse.md +++ /dev/null @@ -1,12 +0,0 @@ -# V2ErrorResponse - -Error - - -## Fields - -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `ErrorCode` | [components.V2ErrorsEnum](../../models/components/v2errorsenum.md) | :heavy_check_mark: | N/A | VALIDATION | -| `ErrorMessage` | *string* | :heavy_check_mark: | N/A | [VALIDATION] invalid 'cursor' query param | -| `Details` | **string* | :heavy_minus_sign: | N/A | https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 | \ No newline at end of file diff --git a/components/ledger/pkg/client/docs/sdks/formance/README.md b/components/ledger/pkg/client/docs/sdks/formance/README.md deleted file mode 100644 index 158bf7083e..0000000000 --- a/components/ledger/pkg/client/docs/sdks/formance/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Formance SDK - - -## Overview - -### Available Operations - diff --git a/components/ledger/pkg/client/docs/sdks/ledger/README.md b/components/ledger/pkg/client/docs/sdks/ledger/README.md deleted file mode 100644 index 9609c10c1a..0000000000 --- a/components/ledger/pkg/client/docs/sdks/ledger/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ledger -(*Ledger*) - -### Available Operations - diff --git a/components/ledger/pkg/client/docs/sdks/v1/README.md b/components/ledger/pkg/client/docs/sdks/v1/README.md deleted file mode 100644 index 974b444032..0000000000 --- a/components/ledger/pkg/client/docs/sdks/v1/README.md +++ /dev/null @@ -1,1320 +0,0 @@ -# V1 -(*Ledger.V1*) - -### Available Operations - -* [GetInfo](#getinfo) - Show server information -* [GetLedgerInfo](#getledgerinfo) - Get information about a ledger -* [CountAccounts](#countaccounts) - Count the accounts from a ledger -* [ListAccounts](#listaccounts) - List accounts from a ledger -* [GetAccount](#getaccount) - Get account by its address -* [AddMetadataToAccount](#addmetadatatoaccount) - Add metadata to an account -* [GetMapping](#getmapping) - Get the mapping of a ledger -* [UpdateMapping](#updatemapping) - Update the mapping of a ledger -* [~~RunScript~~](#runscript) - Execute a Numscript :warning: **Deprecated** -* [ReadStats](#readstats) - Get statistics from a ledger -* [CountTransactions](#counttransactions) - Count the transactions from a ledger -* [ListTransactions](#listtransactions) - List transactions from a ledger -* [CreateTransaction](#createtransaction) - Create a new transaction to a ledger -* [GetTransaction](#gettransaction) - Get transaction from a ledger by its ID -* [AddMetadataOnTransaction](#addmetadataontransaction) - Set the metadata of a transaction by its ID -* [RevertTransaction](#reverttransaction) - Revert a ledger transaction by its ID -* [CreateTransactions](#createtransactions) - Create a new batch of transactions to a ledger -* [GetBalances](#getbalances) - Get the balances from a ledger's account -* [GetBalancesAggregated](#getbalancesaggregated) - Get the aggregated balances from selected accounts -* [ListLogs](#listlogs) - List the logs from a ledger - -## GetInfo - -Show server information - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V1.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.ConfigInfoResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.GetInfoResponse](../../models/operations/getinforesponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetLedgerInfo - -Get information about a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V1.GetLedgerInfo(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.LedgerInfoResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.GetLedgerInfoResponse](../../models/operations/getledgerinforesponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CountAccounts - -Count the accounts from a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address *string = client.String("users:.+") - - var metadata map[string]any = map[string]any{ - "0": "m", - "1": "e", - "2": "t", - "3": "a", - "4": "d", - "5": "a", - "6": "t", - "7": "a", - "8": "[", - "9": "k", - "10": "e", - "11": "y", - "12": "]", - "13": "=", - "14": "v", - "15": "a", - "16": "l", - "17": "u", - "18": "e", - "19": "1", - "20": "&", - "21": "m", - "22": "e", - "23": "t", - "24": "a", - "25": "d", - "26": "a", - "27": "t", - "28": "a", - "29": "[", - "30": "a", - "31": ".", - "32": "n", - "33": "e", - "34": "s", - "35": "t", - "36": "e", - "37": "d", - "38": ".", - "39": "k", - "40": "e", - "41": "y", - "42": "]", - "43": "=", - "44": "v", - "45": "a", - "46": "l", - "47": "u", - "48": "e", - "49": "2", - } - ctx := context.Background() - res, err := s.Ledger.V1.CountAccounts(ctx, ledger, address, metadata) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | **string* | :heavy_minus_sign: | Filter accounts by address pattern (regular expression placed between ^ and $). | users:.+ | -| `metadata` | map[string]*any* | :heavy_minus_sign: | Filter accounts by metadata key value pairs. The filter can be used like this metadata[key]=value1&metadata[a.nested.key]=value2 | metadata[key]=value1&metadata[a.nested.key]=value2 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.CountAccountsResponse](../../models/operations/countaccountsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListAccounts - -List accounts from a ledger, sorted by address in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.ListAccountsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - After: client.String("users:003"), - Address: client.String("users:.+"), - Metadata: map[string]any{ - "0": "m", - "1": "e", - "2": "t", - "3": "a", - "4": "d", - "5": "a", - "6": "t", - "7": "a", - "8": "[", - "9": "k", - "10": "e", - "11": "y", - "12": "]", - "13": "=", - "14": "v", - "15": "a", - "16": "l", - "17": "u", - "18": "e", - "19": "1", - "20": "&", - "21": "m", - "22": "e", - "23": "t", - "24": "a", - "25": "d", - "26": "a", - "27": "t", - "28": "a", - "29": "[", - "30": "a", - "31": ".", - "32": "n", - "33": "e", - "34": "s", - "35": "t", - "36": "e", - "37": "d", - "38": ".", - "39": "k", - "40": "e", - "41": "y", - "42": "]", - "43": "=", - "44": "v", - "45": "a", - "46": "l", - "47": "u", - "48": "e", - "49": "2", - }, - Balance: client.Int64(2400), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V1.ListAccounts(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.AccountsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.ListAccountsRequest](../../models/operations/listaccountsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.ListAccountsResponse](../../models/operations/listaccountsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetAccount - -Get account by its address - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address string = "users:001" - ctx := context.Background() - res, err := s.Ledger.V1.GetAccount(ctx, ledger, address) - if err != nil { - log.Fatal(err) - } - if res.AccountResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.GetAccountResponse](../../models/operations/getaccountresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## AddMetadataToAccount - -Add metadata to an account - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address string = "users:001" - - var requestBody map[string]any = map[string]any{ - "key": "", - } - ctx := context.Background() - res, err := s.Ledger.V1.AddMetadataToAccount(ctx, ledger, address, requestBody) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `requestBody` | map[string]*any* | :heavy_check_mark: | metadata | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.AddMetadataToAccountResponse](../../models/operations/addmetadatatoaccountresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetMapping - -Get the mapping of a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V1.GetMapping(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.MappingResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.GetMappingResponse](../../models/operations/getmappingresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## UpdateMapping - -Update the mapping of a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var mapping *components.Mapping = &components.Mapping{ - Contracts: []components.Contract{ - components.Contract{ - Account: client.String("users:001"), - Expr: components.Expr{}, - }, - }, - } - ctx := context.Background() - res, err := s.Ledger.V1.UpdateMapping(ctx, ledger, mapping) - if err != nil { - log.Fatal(err) - } - if res.MappingResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `mapping` | [components.Mapping](../../models/components/mapping.md) | :heavy_check_mark: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.UpdateMappingResponse](../../models/operations/updatemappingresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ~~RunScript~~ - -This route is deprecated, and has been merged into `POST /{ledger}/transactions`. - - -> :warning: **DEPRECATED**: This will be removed in a future release, please migrate away from it as soon as possible. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - script := components.Script{ - Plain: "vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - ", - Vars: map[string]any{ - "user": "users:042", - }, - Reference: client.String("order_1234"), - } - - var preview *bool = client.Bool(true) - ctx := context.Background() - res, err := s.Ledger.V1.RunScript(ctx, ledger, script, preview) - if err != nil { - log.Fatal(err) - } - if res.ScriptResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `script` | [components.Script](../../models/components/script.md) | :heavy_check_mark: | N/A | | -| `preview` | **bool* | :heavy_minus_sign: | Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.RunScriptResponse](../../models/operations/runscriptresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------ | ------------------ | ------------------ | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ReadStats - -Get statistics from a ledger. (aggregate metrics on accounts and transactions) - - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V1.ReadStats(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.StatsResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | name of the ledger | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.ReadStatsResponse](../../models/operations/readstatsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CountTransactions - -Count the transactions from a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.CountTransactionsRequest{ - Ledger: "ledger001", - Reference: client.String("ref:001"), - Account: client.String("users:001"), - Source: client.String("users:001"), - Destination: client.String("users:001"), - Metadata: &operations.Metadata{}, - } - ctx := context.Background() - res, err := s.Ledger.V1.CountTransactions(ctx, request) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.CountTransactionsRequest](../../models/operations/counttransactionsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.CountTransactionsResponse](../../models/operations/counttransactionsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListTransactions - -List transactions from a ledger, sorted by txid in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.ListTransactionsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - After: client.String("1234"), - Reference: client.String("ref:001"), - Account: client.String("users:001"), - Source: client.String("users:001"), - Destination: client.String("users:001"), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V1.ListTransactions(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.TransactionsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.ListTransactionsRequest](../../models/operations/listtransactionsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.ListTransactionsResponse](../../models/operations/listtransactionsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CreateTransaction - -Create a new transaction to a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - postTransaction := components.PostTransaction{ - Postings: []components.Posting{ - components.Posting{ - Amount: big.NewInt(100), - Asset: "COIN", - Destination: "users:002", - Source: "users:001", - }, - }, - Script: &components.PostTransactionScript{ - Plain: "vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - ", - Vars: map[string]any{ - "user": "users:042", - }, - }, - Reference: client.String("ref:001"), - } - - var preview *bool = client.Bool(true) - ctx := context.Background() - res, err := s.Ledger.V1.CreateTransaction(ctx, ledger, postTransaction, preview) - if err != nil { - log.Fatal(err) - } - if res.TransactionsResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `postTransaction` | [components.PostTransaction](../../models/components/posttransaction.md) | :heavy_check_mark: | The request body must contain at least one of the following objects:
- `postings`: suitable for simple transactions
- `script`: enabling more complex transactions with Numscript
| | -| `preview` | **bool* | :heavy_minus_sign: | Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.CreateTransactionResponse](../../models/operations/createtransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetTransaction - -Get transaction from a ledger by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var txid *big.Int = big.NewInt(1234) - ctx := context.Background() - res, err := s.Ledger.V1.GetTransaction(ctx, ledger, txid) - if err != nil { - log.Fatal(err) - } - if res.TransactionResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.GetTransactionResponse](../../models/operations/gettransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## AddMetadataOnTransaction - -Set the metadata of a transaction by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var txid *big.Int = big.NewInt(1234) - ctx := context.Background() - res, err := s.Ledger.V1.AddMetadataOnTransaction(ctx, ledger, txid, nil) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `requestBody` | map[string]*any* | :heavy_minus_sign: | metadata | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.AddMetadataOnTransactionResponse](../../models/operations/addmetadataontransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## RevertTransaction - -Revert a ledger transaction by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var txid *big.Int = big.NewInt(1234) - ctx := context.Background() - res, err := s.Ledger.V1.RevertTransaction(ctx, ledger, txid, nil) - if err != nil { - log.Fatal(err) - } - if res.TransactionResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `txid` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `disableChecks` | **bool* | :heavy_minus_sign: | Allow to disable balances checks | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.RevertTransactionResponse](../../models/operations/reverttransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CreateTransactions - -Create a new batch of transactions to a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - transactions := components.Transactions{ - Transactions: []components.TransactionData{ - components.TransactionData{ - Postings: []components.Posting{ - components.Posting{ - Amount: big.NewInt(100), - Asset: "COIN", - Destination: "users:002", - Source: "users:001", - }, - }, - Reference: client.String("ref:001"), - }, - }, - } - ctx := context.Background() - res, err := s.Ledger.V1.CreateTransactions(ctx, ledger, transactions) - if err != nil { - log.Fatal(err) - } - if res.TransactionsResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `transactions` | [components.Transactions](../../models/components/transactions.md) | :heavy_check_mark: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.CreateTransactionsResponse](../../models/operations/createtransactionsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetBalances - -Get the balances from a ledger's account - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.GetBalancesRequest{ - Ledger: "ledger001", - Address: client.String("users:001"), - After: client.String("users:003"), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V1.GetBalances(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.BalancesCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.GetBalancesRequest](../../models/operations/getbalancesrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.GetBalancesResponse](../../models/operations/getbalancesresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetBalancesAggregated - -Get the aggregated balances from selected accounts - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address *string = client.String("users:001") - ctx := context.Background() - res, err := s.Ledger.V1.GetBalancesAggregated(ctx, ledger, address, nil) - if err != nil { - log.Fatal(err) - } - if res.AggregateBalancesResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | **string* | :heavy_minus_sign: | Filter balances involving given account, either as source or destination. | users:001 | -| `useInsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.GetBalancesAggregatedResponse](../../models/operations/getbalancesaggregatedresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListLogs - -List the logs from a ledger, sorted by ID in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.ListLogsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - After: client.String("1234"), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V1.ListLogs(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.LogsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.ListLogsRequest](../../models/operations/listlogsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.ListLogsResponse](../../models/operations/listlogsresponse.md), error** -| Error Object | Status Code | Content Type | -| ----------------------- | ----------------------- | ----------------------- | -| sdkerrors.ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | diff --git a/components/ledger/pkg/client/docs/sdks/v2/README.md b/components/ledger/pkg/client/docs/sdks/v2/README.md deleted file mode 100644 index e8cf9c8aa3..0000000000 --- a/components/ledger/pkg/client/docs/sdks/v2/README.md +++ /dev/null @@ -1,1550 +0,0 @@ -# V2 -(*Ledger.V2*) - -### Available Operations - -* [GetInfo](#getinfo) - Show server information -* [ListLedgers](#listledgers) - List ledgers -* [GetLedger](#getledger) - Get a ledger -* [CreateLedger](#createledger) - Create a ledger -* [UpdateLedgerMetadata](#updateledgermetadata) - Update ledger metadata -* [DeleteLedgerMetadata](#deleteledgermetadata) - Delete ledger metadata by key -* [GetLedgerInfo](#getledgerinfo) - Get information about a ledger -* [CreateBulk](#createbulk) - Bulk request -* [CountAccounts](#countaccounts) - Count the accounts from a ledger -* [ListAccounts](#listaccounts) - List accounts from a ledger -* [GetAccount](#getaccount) - Get account by its address -* [AddMetadataToAccount](#addmetadatatoaccount) - Add metadata to an account -* [DeleteAccountMetadata](#deleteaccountmetadata) - Delete metadata by key -* [ReadStats](#readstats) - Get statistics from a ledger -* [CountTransactions](#counttransactions) - Count the transactions from a ledger -* [ListTransactions](#listtransactions) - List transactions from a ledger -* [CreateTransaction](#createtransaction) - Create a new transaction to a ledger -* [GetTransaction](#gettransaction) - Get transaction from a ledger by its ID -* [AddMetadataOnTransaction](#addmetadataontransaction) - Set the metadata of a transaction by its ID -* [DeleteTransactionMetadata](#deletetransactionmetadata) - Delete metadata by key -* [RevertTransaction](#reverttransaction) - Revert a ledger transaction by its ID -* [GetBalancesAggregated](#getbalancesaggregated) - Get the aggregated balances from selected accounts -* [GetVolumesWithBalances](#getvolumeswithbalances) - Get list of volumes with balances for (account/asset) -* [ListLogs](#listlogs) - List the logs from a ledger -* [ImportLogs](#importlogs) -* [ExportLogs](#exportlogs) - Export logs - -## GetInfo - -Show server information - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - - ctx := context.Background() - res, err := s.Ledger.V2.GetInfo(ctx) - if err != nil { - log.Fatal(err) - } - if res.V2ConfigInfoResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2GetInfoResponse](../../models/operations/v2getinforesponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListLedgers - -List ledgers - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var pageSize *int64 = client.Int64(100) - - var cursor *string = client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ==") - ctx := context.Background() - res, err := s.Ledger.V2.ListLedgers(ctx, pageSize, cursor) - if err != nil { - log.Fatal(err) - } - if res.V2LedgerListResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `pageSize` | **int64* | :heavy_minus_sign: | The maximum number of results to return per page.
| 100 | -| `cursor` | **string* | :heavy_minus_sign: | Parameter used in pagination requests. Maximum page size is set to 15.
Set to the value of next for the next page of results.
Set to the value of previous for the previous page of results.
No other parameters can be set when this parameter is set.
| aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2ListLedgersResponse](../../models/operations/v2listledgersresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetLedger - -Get a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.GetLedger(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.V2GetLedgerResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2GetLedgerResponse](../../models/operations/v2getledgerresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CreateLedger - -Create a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var v2CreateLedgerRequest *components.V2CreateLedgerRequest = &components.V2CreateLedgerRequest{ - Metadata: map[string]string{ - "admin": "true", - }, - } - ctx := context.Background() - res, err := s.Ledger.V2.CreateLedger(ctx, ledger, v2CreateLedgerRequest) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `v2CreateLedgerRequest` | [*components.V2CreateLedgerRequest](../../models/components/v2createledgerrequest.md) | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2CreateLedgerResponse](../../models/operations/v2createledgerresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## UpdateLedgerMetadata - -Update ledger metadata - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var requestBody map[string]string = map[string]string{ - "admin": "true", - } - ctx := context.Background() - res, err := s.Ledger.V2.UpdateLedgerMetadata(ctx, ledger, requestBody) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `requestBody` | map[string]*string* | :heavy_minus_sign: | N/A | {
"admin": "true"
} | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2UpdateLedgerMetadataResponse](../../models/operations/v2updateledgermetadataresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## DeleteLedgerMetadata - -Delete ledger metadata by key - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var key string = "foo" - ctx := context.Background() - res, err := s.Ledger.V2.DeleteLedgerMetadata(ctx, ledger, key) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `key` | *string* | :heavy_check_mark: | Key to remove. | foo | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2DeleteLedgerMetadataResponse](../../models/operations/v2deleteledgermetadataresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetLedgerInfo - -Get information about a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.GetLedgerInfo(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.V2LedgerInfoResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2GetLedgerInfoResponse](../../models/operations/v2getledgerinforesponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CreateBulk - -Bulk request - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var requestBody []components.V2BulkElement = []components.V2BulkElement{ - components.CreateV2BulkElementV2BulkElementCreateTransaction( - components.V2BulkElementCreateTransaction{ - Action: "", - Data: &components.V2PostTransaction{ - Postings: []components.V2Posting{ - components.V2Posting{ - Amount: big.NewInt(100), - Asset: "COIN", - Destination: "users:002", - Source: "users:001", - }, - }, - Script: &components.V2PostTransactionScript{ - Plain: "vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - ", - Vars: map[string]any{ - "user": "users:042", - }, - }, - Reference: client.String("ref:001"), - Metadata: map[string]string{ - "admin": "true", - }, - }, - }, - ), - } - ctx := context.Background() - res, err := s.Ledger.V2.CreateBulk(ctx, ledger, requestBody) - if err != nil { - log.Fatal(err) - } - if res.V2BulkResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `requestBody` | [][components.V2BulkElement](../../models/components/v2bulkelement.md) | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2CreateBulkResponse](../../models/operations/v2createbulkresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CountAccounts - -Count the accounts from a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "time" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.CountAccounts(ctx, ledger, nil, nil) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `requestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2CountAccountsResponse](../../models/operations/v2countaccountsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListAccounts - -List accounts from a ledger, sorted by address in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2ListAccountsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V2.ListAccounts(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.V2AccountsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2ListAccountsRequest](../../models/operations/v2listaccountsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2ListAccountsResponse](../../models/operations/v2listaccountsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetAccount - -Get account by its address - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "time" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address string = "users:001" - ctx := context.Background() - res, err := s.Ledger.V2.GetAccount(ctx, ledger, address, nil, nil) - if err != nil { - log.Fatal(err) - } - if res.V2AccountResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | *string* | :heavy_check_mark: | Exact address of the account. It must match the following regular expressions pattern:
```
^\w+(:\w+)*$
```
| users:001 | -| `expand` | **string* | :heavy_minus_sign: | N/A | | -| `pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2GetAccountResponse](../../models/operations/v2getaccountresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## AddMetadataToAccount - -Add metadata to an account - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2AddMetadataToAccountRequest{ - Ledger: "ledger001", - Address: "users:001", - DryRun: client.Bool(true), - RequestBody: map[string]string{ - "admin": "true", - }, - } - ctx := context.Background() - res, err := s.Ledger.V2.AddMetadataToAccount(ctx, request) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2AddMetadataToAccountRequest](../../models/operations/v2addmetadatatoaccountrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2AddMetadataToAccountResponse](../../models/operations/v2addmetadatatoaccountresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## DeleteAccountMetadata - -Delete metadata by key - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var address string = "" - - var key string = "foo" - ctx := context.Background() - res, err := s.Ledger.V2.DeleteAccountMetadata(ctx, ledger, address, key) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `address` | *string* | :heavy_check_mark: | Account address | | -| `key` | *string* | :heavy_check_mark: | The key to remove. | foo | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2DeleteAccountMetadataResponse](../../models/operations/v2deleteaccountmetadataresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ReadStats - -Get statistics from a ledger. (aggregate metrics on accounts and transactions) - - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.ReadStats(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res.V2StatsResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | name of the ledger | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2ReadStatsResponse](../../models/operations/v2readstatsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CountTransactions - -Count the transactions from a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "time" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.CountTransactions(ctx, ledger, nil, nil) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `requestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2CountTransactionsResponse](../../models/operations/v2counttransactionsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListTransactions - -List transactions from a ledger, sorted by id in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2ListTransactionsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V2.ListTransactions(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.V2TransactionsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2ListTransactionsRequest](../../models/operations/v2listtransactionsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2ListTransactionsResponse](../../models/operations/v2listtransactionsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## CreateTransaction - -Create a new transaction to a ledger - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - v2PostTransaction := components.V2PostTransaction{ - Postings: []components.V2Posting{ - components.V2Posting{ - Amount: big.NewInt(100), - Asset: "COIN", - Destination: "users:002", - Source: "users:001", - }, - }, - Script: &components.V2PostTransactionScript{ - Plain: "vars { - account $user - } - send [COIN 10] ( - source = @world - destination = $user - ) - ", - Vars: map[string]any{ - "user": "users:042", - }, - }, - Reference: client.String("ref:001"), - Metadata: map[string]string{ - "admin": "true", - }, - } - - var dryRun *bool = client.Bool(true) - ctx := context.Background() - res, err := s.Ledger.V2.CreateTransaction(ctx, ledger, v2PostTransaction, dryRun, nil) - if err != nil { - log.Fatal(err) - } - if res.V2CreateTransactionResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `v2PostTransaction` | [components.V2PostTransaction](../../models/components/v2posttransaction.md) | :heavy_check_mark: | The request body must contain at least one of the following objects:
- `postings`: suitable for simple transactions
- `script`: enabling more complex transactions with Numscript
| | -| `dryRun` | **bool* | :heavy_minus_sign: | Set the dryRun mode. dry run mode doesn't add the logs to the database or publish a message to the message broker. | true | -| `idempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2CreateTransactionResponse](../../models/operations/v2createtransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetTransaction - -Get transaction from a ledger by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "time" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var id *big.Int = big.NewInt(1234) - ctx := context.Background() - res, err := s.Ledger.V2.GetTransaction(ctx, ledger, id, nil, nil) - if err != nil { - log.Fatal(err) - } - if res.V2GetTransactionResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `id` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `expand` | **string* | :heavy_minus_sign: | N/A | | -| `pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2GetTransactionResponse](../../models/operations/v2gettransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## AddMetadataOnTransaction - -Set the metadata of a transaction by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2AddMetadataOnTransactionRequest{ - Ledger: "ledger001", - ID: big.NewInt(1234), - DryRun: client.Bool(true), - RequestBody: map[string]string{ - "admin": "true", - }, - } - ctx := context.Background() - res, err := s.Ledger.V2.AddMetadataOnTransaction(ctx, request) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2AddMetadataOnTransactionRequest](../../models/operations/v2addmetadataontransactionrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2AddMetadataOnTransactionResponse](../../models/operations/v2addmetadataontransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## DeleteTransactionMetadata - -Delete metadata by key - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var id *big.Int = big.NewInt(1234) - - var key string = "foo" - ctx := context.Background() - res, err := s.Ledger.V2.DeleteTransactionMetadata(ctx, ledger, id, key) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `id` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `key` | *string* | :heavy_check_mark: | The key to remove. | foo | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2DeleteTransactionMetadataResponse](../../models/operations/v2deletetransactionmetadataresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## RevertTransaction - -Revert a ledger transaction by its ID - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "math/big" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - - var id *big.Int = big.NewInt(1234) - ctx := context.Background() - res, err := s.Ledger.V2.RevertTransaction(ctx, ledger, id, nil, nil) - if err != nil { - log.Fatal(err) - } - if res.V2RevertTransactionResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `id` | [*big.Int](https://pkg.go.dev/math/big#Int) | :heavy_check_mark: | Transaction ID. | 1234 | -| `force` | **bool* | :heavy_minus_sign: | Force revert | | -| `atEffectiveDate` | **bool* | :heavy_minus_sign: | Revert transaction at effective date of the original tx | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2RevertTransactionResponse](../../models/operations/v2reverttransactionresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetBalancesAggregated - -Get the aggregated balances from selected accounts - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "time" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.GetBalancesAggregated(ctx, ledger, nil, nil, nil) - if err != nil { - log.Fatal(err) - } - if res.V2AggregateBalancesResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `pit` | [*time.Time](https://pkg.go.dev/time#Time) | :heavy_minus_sign: | N/A | | -| `useInsertionDate` | **bool* | :heavy_minus_sign: | Use insertion date instead of effective date | | -| `requestBody` | map[string]*any* | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2GetBalancesAggregatedResponse](../../models/operations/v2getbalancesaggregatedresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## GetVolumesWithBalances - -Get list of volumes with balances for (account/asset) - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2GetVolumesWithBalancesRequest{ - PageSize: client.Int64(100), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - Ledger: "ledger001", - GroupBy: client.Int64(3), - } - ctx := context.Background() - res, err := s.Ledger.V2.GetVolumesWithBalances(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.V2VolumesWithBalanceCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2GetVolumesWithBalancesRequest](../../models/operations/v2getvolumeswithbalancesrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2GetVolumesWithBalancesResponse](../../models/operations/v2getvolumeswithbalancesresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ListLogs - -List the logs from a ledger, sorted by ID in descending order. - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "github.com/formancehq/stack/ledger/client/models/operations" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - request := operations.V2ListLogsRequest{ - Ledger: "ledger001", - PageSize: client.Int64(100), - Cursor: client.String("aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ=="), - } - ctx := context.Background() - res, err := s.Ledger.V2.ListLogs(ctx, request) - if err != nil { - log.Fatal(err) - } - if res.V2LogsCursorResponse != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | -| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | -| `request` | [operations.V2ListLogsRequest](../../models/operations/v2listlogsrequest.md) | :heavy_check_mark: | The request object to use for the request. | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | - - -### Response - -**[*operations.V2ListLogsResponse](../../models/operations/v2listlogsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ImportLogs - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.ImportLogs(ctx, ledger, nil) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `requestBody` | **string* | :heavy_minus_sign: | N/A | | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2ImportLogsResponse](../../models/operations/v2importlogsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------------- | ------------------------- | ------------------------- | -| sdkerrors.V2ErrorResponse | default | application/json | -| sdkerrors.SDKError | 4xx-5xx | */* | - -## ExportLogs - -Export logs - -### Example Usage - -```go -package main - -import( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client" - "context" - "log" -) - -func main() { - s := client.New( - client.WithSecurity(components.Security{ - ClientID: "", - ClientSecret: "", - }), - ) - var ledger string = "ledger001" - ctx := context.Background() - res, err := s.Ledger.V2.ExportLogs(ctx, ledger) - if err != nil { - log.Fatal(err) - } - if res != nil { - // handle response - } -} -``` - -### Parameters - -| Parameter | Type | Required | Description | Example | -| -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | -| `ctx` | [context.Context](https://pkg.go.dev/context#Context) | :heavy_check_mark: | The context to use for the request. | | -| `ledger` | *string* | :heavy_check_mark: | Name of the ledger. | ledger001 | -| `opts` | [][operations.Option](../../models/operations/option.md) | :heavy_minus_sign: | The options for this request. | | - - -### Response - -**[*operations.V2ExportLogsResponse](../../models/operations/v2exportlogsresponse.md), error** -| Error Object | Status Code | Content Type | -| ------------------ | ------------------ | ------------------ | -| sdkerrors.SDKError | 4xx-5xx | */* | diff --git a/components/ledger/pkg/client/formance.go b/components/ledger/pkg/client/formance.go deleted file mode 100644 index fc75add9ac..0000000000 --- a/components/ledger/pkg/client/formance.go +++ /dev/null @@ -1,171 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package client - -import ( - "context" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/hooks" - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/retry" - "net/http" - "time" -) - -// ServerList contains the list of servers available to the SDK -var ServerList = []string{ - "http://localhost:8080/", -} - -// HTTPClient provides an interface for suplying the SDK with a custom HTTP client -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -// String provides a helper function to return a pointer to a string -func String(s string) *string { return &s } - -// Bool provides a helper function to return a pointer to a bool -func Bool(b bool) *bool { return &b } - -// Int provides a helper function to return a pointer to an int -func Int(i int) *int { return &i } - -// Int64 provides a helper function to return a pointer to an int64 -func Int64(i int64) *int64 { return &i } - -// Float32 provides a helper function to return a pointer to a float32 -func Float32(f float32) *float32 { return &f } - -// Float64 provides a helper function to return a pointer to a float64 -func Float64(f float64) *float64 { return &f } - -type sdkConfiguration struct { - Client HTTPClient - Security func(context.Context) (interface{}, error) - ServerURL string - ServerIndex int - Language string - OpenAPIDocVersion string - SDKVersion string - GenVersion string - UserAgent string - RetryConfig *retry.Config - Hooks *hooks.Hooks - Timeout *time.Duration -} - -func (c *sdkConfiguration) GetServerDetails() (string, map[string]string) { - if c.ServerURL != "" { - return c.ServerURL, nil - } - - return ServerList[c.ServerIndex], nil -} - -type Formance struct { - Ledger *Ledger - - sdkConfiguration sdkConfiguration -} - -type SDKOption func(*Formance) - -// WithServerURL allows the overriding of the default server URL -func WithServerURL(serverURL string) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.ServerURL = serverURL - } -} - -// WithTemplatedServerURL allows the overriding of the default server URL with a templated URL populated with the provided parameters -func WithTemplatedServerURL(serverURL string, params map[string]string) SDKOption { - return func(sdk *Formance) { - if params != nil { - serverURL = utils.ReplaceParameters(serverURL, params) - } - - sdk.sdkConfiguration.ServerURL = serverURL - } -} - -// WithServerIndex allows the overriding of the default server by index -func WithServerIndex(serverIndex int) SDKOption { - return func(sdk *Formance) { - if serverIndex < 0 || serverIndex >= len(ServerList) { - panic(fmt.Errorf("server index %d out of range", serverIndex)) - } - - sdk.sdkConfiguration.ServerIndex = serverIndex - } -} - -// WithClient allows the overriding of the default HTTP client used by the SDK -func WithClient(client HTTPClient) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.Client = client - } -} - -// WithSecurity configures the SDK to use the provided security details -func WithSecurity(security components.Security) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.Security = utils.AsSecuritySource(security) - } -} - -// WithSecuritySource configures the SDK to invoke the Security Source function on each method call to determine authentication -func WithSecuritySource(security func(context.Context) (components.Security, error)) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.Security = func(ctx context.Context) (interface{}, error) { - return security(ctx) - } - } -} - -func WithRetryConfig(retryConfig retry.Config) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.RetryConfig = &retryConfig - } -} - -// WithTimeout Optional request timeout applied to each operation -func WithTimeout(timeout time.Duration) SDKOption { - return func(sdk *Formance) { - sdk.sdkConfiguration.Timeout = &timeout - } -} - -// New creates a new instance of the SDK with the provided options -func New(opts ...SDKOption) *Formance { - sdk := &Formance{ - sdkConfiguration: sdkConfiguration{ - Language: "go", - OpenAPIDocVersion: "LEDGER_VERSION", - SDKVersion: "0.3.0", - GenVersion: "2.384.1", - UserAgent: "speakeasy-sdk/go 0.3.0 2.384.1 LEDGER_VERSION github.com/formancehq/stack/ledger/client", - Hooks: hooks.New(), - }, - } - for _, opt := range opts { - opt(sdk) - } - - // Use WithClient to override the default client if you would like to customize the timeout - if sdk.sdkConfiguration.Client == nil { - sdk.sdkConfiguration.Client = &http.Client{Timeout: 60 * time.Second} - } - - currentServerURL, _ := sdk.sdkConfiguration.GetServerDetails() - serverURL := currentServerURL - serverURL, sdk.sdkConfiguration.Client = sdk.sdkConfiguration.Hooks.SDKInit(currentServerURL, sdk.sdkConfiguration.Client) - if serverURL != currentServerURL { - sdk.sdkConfiguration.ServerURL = serverURL - } - - sdk.Ledger = newLedger(sdk.sdkConfiguration) - - return sdk -} diff --git a/components/ledger/pkg/client/go.mod b/components/ledger/pkg/client/go.mod deleted file mode 100644 index 4aac575123..0000000000 --- a/components/ledger/pkg/client/go.mod +++ /dev/null @@ -1,10 +0,0 @@ - -module github.com/formancehq/stack/ledger/client - -go 1.20 - -require ( - github.com/cenkalti/backoff/v4 v4.2.0 - github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 - github.com/spyzhov/ajson v0.8.0 -) diff --git a/components/ledger/pkg/client/go.sum b/components/ledger/pkg/client/go.sum deleted file mode 100644 index 0fee03f70d..0000000000 --- a/components/ledger/pkg/client/go.sum +++ /dev/null @@ -1,3 +0,0 @@ -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= -github.com/spyzhov/ajson v0.8.0/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA= diff --git a/components/ledger/pkg/client/internal/hooks/clientcredentials.go b/components/ledger/pkg/client/internal/hooks/clientcredentials.go deleted file mode 100644 index a7ab671206..0000000000 --- a/components/ledger/pkg/client/internal/hooks/clientcredentials.go +++ /dev/null @@ -1,251 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package hooks - -import ( - "bytes" - "context" - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/models/components" - "io" - "net/http" - "net/url" - "strings" - "time" -) - -type session struct { - Credentials *credentials - Token string - ExpiresAt *int64 - Scopes []string -} - -type tokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn *int64 `json:"expires_in"` -} - -type credentials struct { - ClientID string - ClientSecret string - TokenURL string -} - -type clientCredentialsHook struct { - baseURL string - client HTTPClient - sessions map[string]*session -} - -var ( - _ sdkInitHook = (*clientCredentialsHook)(nil) - _ beforeRequestHook = (*clientCredentialsHook)(nil) - _ afterErrorHook = (*clientCredentialsHook)(nil) -) - -func NewClientCredentialsHook() *clientCredentialsHook { - return &clientCredentialsHook{ - sessions: make(map[string]*session), - } -} - -func (c *clientCredentialsHook) SDKInit(baseURL string, client HTTPClient) (string, HTTPClient) { - c.baseURL = baseURL - c.client = client - return baseURL, client -} - -func (c *clientCredentialsHook) BeforeRequest(ctx BeforeRequestContext, req *http.Request) (*http.Request, error) { - if ctx.OAuth2Scopes == nil { - // OAuth2 not in use - return req, nil - } - - credentials, err := c.getCredentials(ctx.Context, ctx.SecuritySource) - if err != nil { - return nil, &FailEarly{Cause: err} - } - if credentials == nil { - return req, err - } - - sessionKey := getSessionKey(credentials.ClientID, credentials.ClientSecret) - sess, ok := c.sessions[sessionKey] - if !ok || !hasRequiredScopes(sess.Scopes, ctx.OAuth2Scopes) || hasTokenExpired(sess.ExpiresAt) { - s, err := c.doTokenRequest(ctx.Context, credentials, getScopes(ctx.OAuth2Scopes, sess)) - if err != nil { - return nil, fmt.Errorf("failed to get token: %w", err) - } - - c.sessions[sessionKey] = s - sess = s - } - - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", sess.Token)) - - return req, nil -} - -func (c *clientCredentialsHook) AfterError(ctx AfterErrorContext, res *http.Response, err error) (*http.Response, error) { - if ctx.OAuth2Scopes == nil { - // OAuth2 not in use - return res, err - } - - // We don't want to refresh the token if the error is not related to the token - if err != nil { - return res, err - } - - credentials, err := c.getCredentials(ctx.Context, ctx.SecuritySource) - if err != nil { - return nil, &FailEarly{Cause: err} - } - if credentials == nil { - return res, err - } - - if res != nil && res.StatusCode == http.StatusUnauthorized { - sessionKey := getSessionKey(credentials.ClientID, credentials.ClientSecret) - delete(c.sessions, sessionKey) - } - - return res, err -} - -func (c *clientCredentialsHook) doTokenRequest(ctx context.Context, credentials *credentials, scopes []string) (*session, error) { - values := url.Values{} - values.Set("grant_type", "client_credentials") - values.Set("client_id", credentials.ClientID) - values.Set("client_secret", credentials.ClientSecret) - - if len(scopes) > 0 { - values.Set("scope", strings.Join(scopes, " ")) - } - - tokenURL := credentials.TokenURL - u, err := url.Parse(tokenURL) - if err != nil { - return nil, fmt.Errorf("failed to parse token URL: %w", err) - } - if !u.IsAbs() { - tokenURL, err = url.JoinPath(c.baseURL, tokenURL) - if err != nil { - return nil, fmt.Errorf("failed to parse token URL: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, bytes.NewBufferString(values.Encode())) - if err != nil { - return nil, fmt.Errorf("failed to create token request: %w", err) - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - res, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to send token request: %w", err) - } - defer res.Body.Close() - - if res.StatusCode < 200 || res.StatusCode >= 300 { - body, _ := io.ReadAll(res.Body) - return nil, fmt.Errorf("unexpected status code: %d: %s", res.StatusCode, body) - } - - var tokenRes tokenResponse - if err := json.NewDecoder(res.Body).Decode(&tokenRes); err != nil { - return nil, fmt.Errorf("failed to decode token response: %w", err) - } - - if tokenRes.TokenType != "Bearer" { - return nil, fmt.Errorf("unexpected token type: %s", tokenRes.TokenType) - } - - var expiresAt *int64 - if tokenRes.ExpiresIn != nil { - expiresAt = new(int64) - *expiresAt = time.Now().Unix() + *tokenRes.ExpiresIn - } - - return &session{ - Credentials: credentials, - Token: tokenRes.AccessToken, - ExpiresAt: expiresAt, - Scopes: scopes, - }, nil -} - -func (c *clientCredentialsHook) getCredentials(ctx context.Context, source func(ctx context.Context) (interface{}, error)) (*credentials, error) { - if source == nil { - return nil, nil - } - - sec, err := source(ctx) - if err != nil { - return nil, err - } - - security, ok := sec.(components.Security) - - if !ok { - return nil, fmt.Errorf("unexpected security type: %T", sec) - } - - return &credentials{ - ClientID: security.ClientID, - ClientSecret: security.ClientSecret, - TokenURL: security.GetTokenURL(), - }, nil -} - -func getSessionKey(clientID, clientSecret string) string { - key := fmt.Sprintf("%s:%s", clientID, clientSecret) - hash := md5.Sum([]byte(key)) - return hex.EncodeToString(hash[:]) -} - -func hasRequiredScopes(scopes []string, requiredScopes []string) bool { - for _, requiredScope := range requiredScopes { - found := false - for _, scope := range scopes { - if scope == requiredScope { - found = true - break - } - } - if !found { - return false - } - } - return true -} - -func getScopes(requiredScopes []string, sess *session) []string { - scopes := requiredScopes - if sess != nil { - for _, scope := range sess.Scopes { - found := false - for _, requiredScope := range requiredScopes { - if scope == requiredScope { - found = true - break - } - } - if !found { - scopes = append(scopes, scope) - } - } - } - - return scopes -} - -func hasTokenExpired(expiresAt *int64) bool { - return expiresAt == nil || time.Now().Unix()+60 >= *expiresAt -} diff --git a/components/ledger/pkg/client/internal/hooks/hooks.go b/components/ledger/pkg/client/internal/hooks/hooks.go deleted file mode 100644 index a1fbccf19e..0000000000 --- a/components/ledger/pkg/client/internal/hooks/hooks.go +++ /dev/null @@ -1,152 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package hooks - -import ( - "context" - "errors" - "net/http" -) - -type FailEarly struct { - Cause error -} - -var _ error = (*FailEarly)(nil) - -func (f *FailEarly) Error() string { - return f.Cause.Error() -} - -// HTTPClient provides an interface for supplying the SDK with a custom HTTP client -type HTTPClient interface { - Do(req *http.Request) (*http.Response, error) -} - -type HookContext struct { - Context context.Context - OperationID string - OAuth2Scopes []string - SecuritySource func(context.Context) (interface{}, error) -} - -type BeforeRequestContext struct { - HookContext -} - -type AfterSuccessContext struct { - HookContext -} - -type AfterErrorContext struct { - HookContext -} - -// sdkInitHook is called when the SDK is initializing. The hook can modify and return a new baseURL and HTTP client to be used by the SDK. -type sdkInitHook interface { - SDKInit(baseURL string, client HTTPClient) (string, HTTPClient) -} - -// beforeRequestHook is called before the SDK sends a request. The hook can modify the request before it is sent or return an error to stop the request from being sent. -type beforeRequestHook interface { - BeforeRequest(hookCtx BeforeRequestContext, req *http.Request) (*http.Request, error) -} - -// afterSuccessHook is called after the SDK receives a response. The hook can modify the response before it is handled or return an error to stop the response from being handled. -type afterSuccessHook interface { - AfterSuccess(hookCtx AfterSuccessContext, res *http.Response) (*http.Response, error) -} - -// afterErrorHook is called after the SDK encounters an error, or a non-successful response. The hook can modify the response if available otherwise modify the error. -// All afterErrorHook hooks are called and returning an error won't stop the other hooks from being called. But if you want to stop the other hooks from being called, you can return a FailEarly error wrapping your error. -type afterErrorHook interface { - AfterError(hookCtx AfterErrorContext, res *http.Response, err error) (*http.Response, error) -} - -type Hooks struct { - sdkInitHooks []sdkInitHook - beforeRequestHook []beforeRequestHook - afterSuccessHook []afterSuccessHook - afterErrorHook []afterErrorHook -} - -func New() *Hooks { - cc := NewClientCredentialsHook() - - h := &Hooks{ - sdkInitHooks: []sdkInitHook{ - cc, - }, - beforeRequestHook: []beforeRequestHook{ - cc, - }, - afterSuccessHook: []afterSuccessHook{}, - afterErrorHook: []afterErrorHook{ - cc, - }, - } - - initHooks(h) - - return h -} - -// registerSDKInitHook registers a hook to be used by the SDK for the initialization event. -func (h *Hooks) registerSDKInitHook(hook sdkInitHook) { - h.sdkInitHooks = append(h.sdkInitHooks, hook) -} - -// registerBeforeRequestHook registers a hook to be used by the SDK for the before request event. -func (h *Hooks) registerBeforeRequestHook(hook beforeRequestHook) { - h.beforeRequestHook = append(h.beforeRequestHook, hook) -} - -// registerAfterSuccessHook registers a hook to be used by the SDK for the after success event. -func (h *Hooks) registerAfterSuccessHook(hook afterSuccessHook) { - h.afterSuccessHook = append(h.afterSuccessHook, hook) -} - -// registerAfterErrorHook registers a hook to be used by the SDK for the after error event. -func (h *Hooks) registerAfterErrorHook(hook afterErrorHook) { - h.afterErrorHook = append(h.afterErrorHook, hook) -} - -func (h *Hooks) SDKInit(baseURL string, client HTTPClient) (string, HTTPClient) { - for _, hook := range h.sdkInitHooks { - baseURL, client = hook.SDKInit(baseURL, client) - } - return baseURL, client -} - -func (h *Hooks) BeforeRequest(hookCtx BeforeRequestContext, req *http.Request) (*http.Request, error) { - for _, hook := range h.beforeRequestHook { - var err error - req, err = hook.BeforeRequest(hookCtx, req) - if err != nil { - return req, err - } - } - return req, nil -} - -func (h *Hooks) AfterSuccess(hookCtx AfterSuccessContext, res *http.Response) (*http.Response, error) { - for _, hook := range h.afterSuccessHook { - var err error - res, err = hook.AfterSuccess(hookCtx, res) - if err != nil { - return res, err - } - } - return res, nil -} - -func (h *Hooks) AfterError(hookCtx AfterErrorContext, res *http.Response, err error) (*http.Response, error) { - for _, hook := range h.afterErrorHook { - res, err = hook.AfterError(hookCtx, res, err) - var fe *FailEarly - if errors.As(err, &fe) { - return nil, fe.Cause - } - } - return res, err -} diff --git a/components/ledger/pkg/client/internal/hooks/registration.go b/components/ledger/pkg/client/internal/hooks/registration.go deleted file mode 100644 index fa131be812..0000000000 --- a/components/ledger/pkg/client/internal/hooks/registration.go +++ /dev/null @@ -1,18 +0,0 @@ -package hooks - -/* - * This file is only ever generated once on the first generation and then is free to be modified. - * Any hooks you wish to add should be registered in the initHooks function. Feel free to define - * your hooks in this file or in separate files in the hooks package. - * - * Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance. - */ - -func initHooks(h *Hooks) { - // exampleHook := &ExampleHook{} - - // h.registerSDKInitHook(exampleHook) - // h.registerBeforeRequestHook(exampleHook) - // h.registerAfterErrorHook(exampleHook) - // h.registerAfterSuccessHook(exampleHook) -} diff --git a/components/ledger/pkg/client/internal/utils/contenttype.go b/components/ledger/pkg/client/internal/utils/contenttype.go deleted file mode 100644 index f6487e01eb..0000000000 --- a/components/ledger/pkg/client/internal/utils/contenttype.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "fmt" - "mime" - "strings" -) - -func MatchContentType(contentType string, pattern string) bool { - if contentType == "" { - contentType = "application/octet-stream" - } - - if contentType == pattern || pattern == "*" || pattern == "*/*" { - return true - } - - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return false - } - - if mediaType == pattern { - return true - } - - parts := strings.Split(mediaType, "/") - if len(parts) == 2 { - if fmt.Sprintf("%s/*", parts[0]) == pattern || fmt.Sprintf("*/%s", parts[1]) == pattern { - return true - } - } - - return false -} diff --git a/components/ledger/pkg/client/internal/utils/form.go b/components/ledger/pkg/client/internal/utils/form.go deleted file mode 100644 index dcc36fffe1..0000000000 --- a/components/ledger/pkg/client/internal/utils/form.go +++ /dev/null @@ -1,117 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "fmt" - "math/big" - "net/url" - "reflect" - "strings" - "time" - - "github.com/ericlagergren/decimal" - - "github.com/formancehq/stack/ledger/client/types" -) - -func populateForm(paramName string, explode bool, objType reflect.Type, objValue reflect.Value, delimiter string, getFieldName func(reflect.StructField) string) url.Values { - - formValues := url.Values{} - - if isNil(objType, objValue) { - return formValues - } - - if objType.Kind() == reflect.Pointer { - objType = objType.Elem() - objValue = objValue.Elem() - } - - switch objType.Kind() { - case reflect.Struct: - switch objValue.Interface().(type) { - case time.Time: - formValues.Add(paramName, valToString(objValue.Interface())) - case types.Date: - formValues.Add(paramName, valToString(objValue.Interface())) - case big.Int: - formValues.Add(paramName, valToString(objValue.Interface())) - case decimal.Big: - formValues.Add(paramName, valToString(objValue.Interface())) - default: - var items []string - - for i := 0; i < objType.NumField(); i++ { - fieldType := objType.Field(i) - valType := objValue.Field(i) - - if isNil(fieldType.Type, valType) { - continue - } - - if valType.Kind() == reflect.Pointer { - valType = valType.Elem() - } - - fieldName := getFieldName(fieldType) - if fieldName == "" { - continue - } - - if explode { - formValues.Add(fieldName, valToString(valType.Interface())) - } else { - items = append(items, fmt.Sprintf("%s%s%s", fieldName, delimiter, valToString(valType.Interface()))) - } - } - - if len(items) > 0 { - formValues.Add(paramName, strings.Join(items, delimiter)) - } - } - case reflect.Map: - items := []string{} - - iter := objValue.MapRange() - for iter.Next() { - if explode { - formValues.Add(iter.Key().String(), valToString(iter.Value().Interface())) - } else { - items = append(items, fmt.Sprintf("%s%s%s", iter.Key().String(), delimiter, valToString(iter.Value().Interface()))) - } - } - - if len(items) > 0 { - formValues.Add(paramName, strings.Join(items, delimiter)) - } - case reflect.Slice, reflect.Array: - values := parseDelimitedArray(explode, objValue, delimiter) - for _, v := range values { - formValues.Add(paramName, v) - } - default: - formValues.Add(paramName, valToString(objValue.Interface())) - } - - return formValues -} - -func parseDelimitedArray(explode bool, objValue reflect.Value, delimiter string) []string { - values := []string{} - items := []string{} - - for i := 0; i < objValue.Len(); i++ { - if explode { - values = append(values, valToString(objValue.Index(i).Interface())) - } else { - items = append(items, valToString(objValue.Index(i).Interface())) - } - } - - if len(items) > 0 { - values = append(values, strings.Join(items, delimiter)) - } - - return values -} diff --git a/components/ledger/pkg/client/internal/utils/headers.go b/components/ledger/pkg/client/internal/utils/headers.go deleted file mode 100644 index a07608bdc6..0000000000 --- a/components/ledger/pkg/client/internal/utils/headers.go +++ /dev/null @@ -1,124 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "fmt" - "net/http" - "reflect" - "strings" -) - -func PopulateHeaders(_ context.Context, req *http.Request, headers interface{}, globals interface{}) { - globalsAlreadyPopulated := populateHeaders(headers, globals, req.Header, []string{}) - if globals != nil { - _ = populateHeaders(globals, nil, req.Header, globalsAlreadyPopulated) - } -} - -func populateHeaders(headers interface{}, globals interface{}, reqHeaders http.Header, skipFields []string) []string { - headerParamsStructType, headerParamsValType := dereferencePointers(reflect.TypeOf(headers), reflect.ValueOf(headers)) - - globalsAlreadyPopulated := []string{} - - for i := 0; i < headerParamsStructType.NumField(); i++ { - fieldType := headerParamsStructType.Field(i) - valType := headerParamsValType.Field(i) - - if contains(skipFields, fieldType.Name) { - continue - } - - if globals != nil { - var globalFound bool - fieldType, valType, globalFound = populateFromGlobals(fieldType, valType, headerParamTagKey, globals) - if globalFound { - globalsAlreadyPopulated = append(globalsAlreadyPopulated, fieldType.Name) - } - } - - tag := parseParamTag(headerParamTagKey, fieldType, "simple", false) - if tag == nil { - continue - } - - value := serializeHeader(fieldType.Type, valType, tag.Explode) - if value != "" { - reqHeaders.Add(tag.ParamName, value) - } - } - - return globalsAlreadyPopulated -} - -func serializeHeader(objType reflect.Type, objValue reflect.Value, explode bool) string { - if isNil(objType, objValue) { - return "" - } - - if objType.Kind() == reflect.Pointer { - objType = objType.Elem() - objValue = objValue.Elem() - } - - switch objType.Kind() { - case reflect.Struct: - items := []string{} - - for i := 0; i < objType.NumField(); i++ { - fieldType := objType.Field(i) - valType := objValue.Field(i) - - if isNil(fieldType.Type, valType) { - continue - } - - if fieldType.Type.Kind() == reflect.Pointer { - valType = valType.Elem() - } - - tag := parseParamTag(headerParamTagKey, fieldType, "simple", false) - if tag == nil { - continue - } - - fieldName := tag.ParamName - - if fieldName == "" { - continue - } - - if explode { - items = append(items, fmt.Sprintf("%s=%s", fieldName, valToString(valType.Interface()))) - } else { - items = append(items, fieldName, valToString(valType.Interface())) - } - } - - return strings.Join(items, ",") - case reflect.Map: - items := []string{} - - iter := objValue.MapRange() - for iter.Next() { - if explode { - items = append(items, fmt.Sprintf("%s=%s", iter.Key().String(), valToString(iter.Value().Interface()))) - } else { - items = append(items, iter.Key().String(), valToString(iter.Value().Interface())) - } - } - - return strings.Join(items, ",") - case reflect.Slice, reflect.Array: - items := []string{} - - for i := 0; i < objValue.Len(); i++ { - items = append(items, valToString(objValue.Index(i).Interface())) - } - - return strings.Join(items, ",") - default: - return valToString(objValue.Interface()) - } -} diff --git a/components/ledger/pkg/client/internal/utils/json.go b/components/ledger/pkg/client/internal/utils/json.go deleted file mode 100644 index 2ef330e53c..0000000000 --- a/components/ledger/pkg/client/internal/utils/json.go +++ /dev/null @@ -1,670 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "bytes" - "encoding/json" - "fmt" - "math/big" - "reflect" - "strconv" - "strings" - "time" - "unsafe" - - "github.com/formancehq/stack/ledger/client/types" - - "github.com/ericlagergren/decimal" -) - -func MarshalJSON(v interface{}, tag reflect.StructTag, topLevel bool) ([]byte, error) { - typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) - - switch { - case isModelType(typ): - if topLevel { - return json.Marshal(v) - } - - if isNil(typ, val) { - return []byte("null"), nil - } - - out := map[string]json.RawMessage{} - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - fieldVal := val.Field(i) - - fieldName := field.Name - - omitEmpty := false - jsonTag := field.Tag.Get("json") - if jsonTag != "" { - for _, tag := range strings.Split(jsonTag, ",") { - if tag == "omitempty" { - omitEmpty = true - } else { - fieldName = tag - } - } - } - - if isNil(field.Type, fieldVal) && field.Tag.Get("const") == "" { - if omitEmpty { - continue - } - } - - if !field.IsExported() && field.Tag.Get("const") == "" { - continue - } - - additionalProperties := field.Tag.Get("additionalProperties") - if fieldName == "-" && additionalProperties == "" { - continue - } - - if additionalProperties == "true" { - if isNil(field.Type, fieldVal) { - continue - } - fieldVal := trueReflectValue(fieldVal) - if fieldVal.Type().Kind() != reflect.Map { - return nil, fmt.Errorf("additionalProperties must be a map") - } - - for _, key := range fieldVal.MapKeys() { - r, err := marshalValue(fieldVal.MapIndex(key).Interface(), field.Tag) - if err != nil { - return nil, err - } - - out[key.String()] = r - } - - continue - } - - var fv interface{} - - if field.IsExported() { - fv = fieldVal.Interface() - } else { - pt := reflect.New(typ).Elem() - pt.Set(val) - - pf := pt.Field(i) - - fv = reflect.NewAt(pf.Type(), unsafe.Pointer(pf.UnsafeAddr())).Elem().Interface() - } - - r, err := marshalValue(fv, field.Tag) - if err != nil { - return nil, err - } - - out[fieldName] = r - } - - return json.Marshal(out) - default: - return marshalValue(v, tag) - } -} - -func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool, disallowUnknownFields bool) error { - if reflect.TypeOf(v).Kind() != reflect.Ptr { - return fmt.Errorf("v must be a pointer") - } - - typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) - - switch { - case isModelType(typ): - if topLevel || bytes.Equal(b, []byte("null")) { - d := json.NewDecoder(bytes.NewReader(b)) - if disallowUnknownFields { - d.DisallowUnknownFields() - } - return d.Decode(v) - } - - var unmarhsaled map[string]json.RawMessage - - if err := json.Unmarshal(b, &unmarhsaled); err != nil { - return err - } - - var additionalPropertiesField *reflect.StructField - var additionalPropertiesValue *reflect.Value - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - fieldVal := val.Field(i) - - fieldName := field.Name - - jsonTag := field.Tag.Get("json") - if jsonTag != "" { - for _, tag := range strings.Split(jsonTag, ",") { - if tag != "omitempty" { - fieldName = tag - } - } - } - - if field.Tag.Get("additionalProperties") == "true" { - additionalPropertiesField = &field - additionalPropertiesValue = &fieldVal - continue - } - - // If we receive a value for a const field ignore it but mark it as unmarshaled - if field.Tag.Get("const") != "" { - if r, ok := unmarhsaled[fieldName]; ok { - val := string(r) - if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { - val = val[1 : len(val)-1] - } - if val != field.Tag.Get("const") { - return fmt.Errorf("const field %s does not match expected value %s", fieldName, field.Tag.Get("const")) - } - - delete(unmarhsaled, fieldName) - } - } else if !field.IsExported() { - continue - } - - value, ok := unmarhsaled[fieldName] - if !ok { - defaultTag := field.Tag.Get("default") - if defaultTag != "" { - value = handleDefaultConstValue(defaultTag, fieldVal.Interface(), field.Tag) - ok = true - } - } else { - delete(unmarhsaled, fieldName) - } - - if ok { - if err := unmarshalValue(value, fieldVal, field.Tag, disallowUnknownFields); err != nil { - return err - } - } - } - - keys := make([]string, 0, len(unmarhsaled)) - for k := range unmarhsaled { - keys = append(keys, k) - } - - if len(keys) > 0 { - if disallowUnknownFields && (additionalPropertiesField == nil || additionalPropertiesValue == nil) { - return fmt.Errorf("unknown fields: %v", keys) - } - - if additionalPropertiesField != nil && additionalPropertiesValue != nil { - typeOfMap := additionalPropertiesField.Type - if additionalPropertiesValue.Type().Kind() == reflect.Interface { - typeOfMap = reflect.TypeOf(map[string]interface{}{}) - } else if additionalPropertiesValue.Type().Kind() != reflect.Map { - return fmt.Errorf("additionalProperties must be a map") - } - - mapValue := reflect.MakeMap(typeOfMap) - - for key, value := range unmarhsaled { - val := reflect.New(typeOfMap.Elem()) - - if err := unmarshalValue(value, val, additionalPropertiesField.Tag, disallowUnknownFields); err != nil { - return err - } - - if val.Elem().Type().String() == typeOfMap.Elem().String() { - mapValue.SetMapIndex(reflect.ValueOf(key), val.Elem()) - } else { - mapValue.SetMapIndex(reflect.ValueOf(key), trueReflectValue(val)) - } - - } - if additionalPropertiesValue.Type().Kind() == reflect.Interface { - additionalPropertiesValue.Set(mapValue) - } else { - additionalPropertiesValue.Set(mapValue) - } - } - } - default: - return unmarshalValue(b, reflect.ValueOf(v), tag, disallowUnknownFields) - } - - return nil -} - -func marshalValue(v interface{}, tag reflect.StructTag) (json.RawMessage, error) { - constTag := tag.Get("const") - if constTag != "" { - return handleDefaultConstValue(constTag, v, tag), nil - } - - if isNil(reflect.TypeOf(v), reflect.ValueOf(v)) { - defaultTag := tag.Get("default") - if defaultTag != "" { - return handleDefaultConstValue(defaultTag, v, tag), nil - } - - return []byte("null"), nil - } - - typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) - switch typ.Kind() { - case reflect.Int64: - format := tag.Get("integer") - if format == "string" { - b := val.Interface().(int64) - return []byte(fmt.Sprintf(`"%d"`, b)), nil - } - case reflect.Float64: - format := tag.Get("number") - if format == "string" { - b := val.Interface().(float64) - return []byte(fmt.Sprintf(`"%g"`, b)), nil - } - case reflect.Map: - if isNil(typ, val) { - return []byte("null"), nil - } - - out := map[string]json.RawMessage{} - - for _, key := range val.MapKeys() { - itemVal := val.MapIndex(key) - - if isNil(itemVal.Type(), itemVal) { - out[key.String()] = []byte("null") - continue - } - - r, err := marshalValue(itemVal.Interface(), tag) - if err != nil { - return nil, err - } - - out[key.String()] = r - } - - return json.Marshal(out) - case reflect.Slice, reflect.Array: - if isNil(typ, val) { - return []byte("null"), nil - } - - out := []json.RawMessage{} - - for i := 0; i < val.Len(); i++ { - itemVal := val.Index(i) - - if isNil(itemVal.Type(), itemVal) { - out = append(out, []byte("null")) - continue - } - - r, err := marshalValue(itemVal.Interface(), tag) - if err != nil { - return nil, err - } - - out = append(out, r) - } - - return json.Marshal(out) - case reflect.Struct: - switch typ { - case reflect.TypeOf(time.Time{}): - return []byte(fmt.Sprintf(`"%s"`, val.Interface().(time.Time).Format(time.RFC3339Nano))), nil - case reflect.TypeOf(big.Int{}): - format := tag.Get("bigint") - if format == "string" { - b := val.Interface().(big.Int) - return []byte(fmt.Sprintf(`"%s"`, (&b).String())), nil - } - case reflect.TypeOf(decimal.Big{}): - format := tag.Get("decimal") - if format == "number" { - b := val.Interface().(decimal.Big) - f, ok := (&b).Float64() - if ok { - return []byte(b.String()), nil - } - - return []byte(fmt.Sprintf(`%f`, f)), nil - } - } - } - - return json.Marshal(v) -} - -func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { - if tagValue == "null" { - return []byte("null") - } - - typ := dereferenceTypePointer(reflect.TypeOf(val)) - switch typ { - case reflect.TypeOf(time.Time{}): - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - case reflect.TypeOf(big.Int{}): - bigIntTag := tag.Get("bigint") - if bigIntTag == "string" { - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - } - case reflect.TypeOf(int64(0)): - format := tag.Get("integer") - if format == "string" { - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - } - case reflect.TypeOf(float64(0)): - format := tag.Get("number") - if format == "string" { - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - } - case reflect.TypeOf(decimal.Big{}): - decimalTag := tag.Get("decimal") - if decimalTag != "number" { - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - } - case reflect.TypeOf(types.Date{}): - return []byte(fmt.Sprintf(`"%s"`, tagValue)) - default: - if typ.Kind() == reflect.String { - return []byte(fmt.Sprintf("%q", tagValue)) - } - } - - return []byte(tagValue) -} - -func unmarshalValue(value json.RawMessage, v reflect.Value, tag reflect.StructTag, disallowUnknownFields bool) error { - if bytes.Equal(value, []byte("null")) { - if v.CanAddr() { - return json.Unmarshal(value, v.Addr().Interface()) - } else { - return json.Unmarshal(value, v.Interface()) - } - } - - typ := dereferenceTypePointer(v.Type()) - - switch typ.Kind() { - case reflect.Int64: - var b int64 - - format := tag.Get("integer") - if format == "string" { - var s string - if err := json.Unmarshal(value, &s); err != nil { - return err - } - - var err error - b, err = strconv.ParseInt(s, 10, 64) - if err != nil { - return fmt.Errorf("failed to parse string as int64: %w", err) - } - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(typ)) - } - v = v.Elem() - } - - v.Set(reflect.ValueOf(b)) - return nil - } - case reflect.Float64: - var b float64 - - format := tag.Get("number") - if format == "string" { - var s string - if err := json.Unmarshal(value, &s); err != nil { - return err - } - - var err error - b, err = strconv.ParseFloat(s, 64) - if err != nil { - return fmt.Errorf("failed to parse string as float64: %w", err) - } - - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(typ)) - } - v = v.Elem() - } - - v.Set(reflect.ValueOf(b)) - return nil - } - case reflect.Map: - if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) { - if v.CanAddr() { - return json.Unmarshal(value, v.Addr().Interface()) - } else { - return json.Unmarshal(value, v.Interface()) - } - } - - var unmarhsaled map[string]json.RawMessage - - if err := json.Unmarshal(value, &unmarhsaled); err != nil { - return err - } - - m := reflect.MakeMap(typ) - - for k, value := range unmarhsaled { - itemVal := reflect.New(typ.Elem()) - - if err := unmarshalValue(value, itemVal, tag, disallowUnknownFields); err != nil { - return err - } - - m.SetMapIndex(reflect.ValueOf(k), itemVal.Elem()) - } - - v.Set(m) - return nil - case reflect.Slice, reflect.Array: - if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) { - if v.CanAddr() { - return json.Unmarshal(value, v.Addr().Interface()) - } else { - return json.Unmarshal(value, v.Interface()) - } - } - - var unmarhsaled []json.RawMessage - - if err := json.Unmarshal(value, &unmarhsaled); err != nil { - return err - } - - arrVal := v - - for _, value := range unmarhsaled { - itemVal := reflect.New(typ.Elem()) - - if err := unmarshalValue(value, itemVal, tag, disallowUnknownFields); err != nil { - return err - } - - arrVal = reflect.Append(arrVal, itemVal.Elem()) - } - - v.Set(arrVal) - return nil - case reflect.Struct: - switch typ { - case reflect.TypeOf(time.Time{}): - var s string - if err := json.Unmarshal(value, &s); err != nil { - return err - } - - t, err := time.Parse(time.RFC3339Nano, s) - if err != nil { - return fmt.Errorf("failed to parse string as time.Time: %w", err) - } - - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(typ)) - } - v = v.Elem() - } - - v.Set(reflect.ValueOf(t)) - return nil - case reflect.TypeOf(big.Int{}): - var b *big.Int - - format := tag.Get("bigint") - if format == "string" { - var s string - if err := json.Unmarshal(value, &s); err != nil { - return err - } - - var ok bool - b, ok = new(big.Int).SetString(s, 10) - if !ok { - return fmt.Errorf("failed to parse string as big.Int") - } - } else { - if err := json.Unmarshal(value, &b); err != nil { - return err - } - } - - if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { - v = v.Elem() - } - - v.Set(reflect.ValueOf(b)) - return nil - case reflect.TypeOf(decimal.Big{}): - var d *decimal.Big - format := tag.Get("decimal") - if format == "number" { - var ok bool - d, ok = new(decimal.Big).SetString(string(value)) - if !ok { - return fmt.Errorf("failed to parse number as decimal.Big") - } - } else { - if err := json.Unmarshal(value, &d); err != nil { - return err - } - } - - if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { - v = v.Elem() - } - - v.Set(reflect.ValueOf(d)) - return nil - case reflect.TypeOf(types.Date{}): - var s string - - if err := json.Unmarshal(value, &s); err != nil { - return err - } - - d, err := types.DateFromString(s) - if err != nil { - return fmt.Errorf("failed to parse string as types.Date: %w", err) - } - - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(typ)) - } - v = v.Elem() - } - - v.Set(reflect.ValueOf(d)) - return nil - } - } - - var val interface{} - - if v.CanAddr() { - val = v.Addr().Interface() - } else { - val = v.Interface() - } - - d := json.NewDecoder(bytes.NewReader(value)) - if disallowUnknownFields { - d.DisallowUnknownFields() - } - return d.Decode(val) -} - -func dereferencePointers(typ reflect.Type, val reflect.Value) (reflect.Type, reflect.Value) { - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - val = val.Elem() - } else { - return typ, val - } - - return dereferencePointers(typ, val) -} - -func dereferenceTypePointer(typ reflect.Type) reflect.Type { - if typ.Kind() == reflect.Ptr { - typ = typ.Elem() - } else { - return typ - } - - return dereferenceTypePointer(typ) -} - -func isComplexValueType(typ reflect.Type) bool { - switch typ.Kind() { - case reflect.Struct: - switch typ { - case reflect.TypeOf(time.Time{}): - fallthrough - case reflect.TypeOf(big.Int{}): - fallthrough - case reflect.TypeOf(decimal.Big{}): - fallthrough - case reflect.TypeOf(types.Date{}): - return true - } - } - - return false -} - -func isModelType(typ reflect.Type) bool { - if isComplexValueType(typ) { - return false - } - - if typ.Kind() == reflect.Struct { - return true - } - - return false -} diff --git a/components/ledger/pkg/client/internal/utils/pathparams.go b/components/ledger/pkg/client/internal/utils/pathparams.go deleted file mode 100644 index 5d3b7aa681..0000000000 --- a/components/ledger/pkg/client/internal/utils/pathparams.go +++ /dev/null @@ -1,172 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "fmt" - "math/big" - "net/url" - "reflect" - "strings" - "time" - - "github.com/ericlagergren/decimal" - - "github.com/formancehq/stack/ledger/client/types" -) - -func GenerateURL(_ context.Context, serverURL, path string, pathParams interface{}, globals interface{}) (string, error) { - uri := strings.TrimSuffix(serverURL, "/") + path - - parsedParameters := map[string]string{} - - globalsAlreadyPopulated, err := populateParsedParameters(pathParams, globals, parsedParameters, []string{}) - if err != nil { - return "", err - } - - if globals != nil { - _, err = populateParsedParameters(globals, nil, parsedParameters, globalsAlreadyPopulated) - if err != nil { - return "", err - } - } - - // TODO should we handle the case where there are no matching path params? - return ReplaceParameters(uri, parsedParameters), nil -} - -func populateParsedParameters(pathParams interface{}, globals interface{}, parsedParameters map[string]string, skipFields []string) ([]string, error) { - pathParamsStructType, pathParamsValType := dereferencePointers(reflect.TypeOf(pathParams), reflect.ValueOf(pathParams)) - - globalsAlreadyPopulated := []string{} - - for i := 0; i < pathParamsStructType.NumField(); i++ { - fieldType := pathParamsStructType.Field(i) - valType := pathParamsValType.Field(i) - - if contains(skipFields, fieldType.Name) { - continue - } - - requestTag := getRequestTag(fieldType) - if requestTag != nil { - continue - } - - ppTag := parseParamTag(pathParamTagKey, fieldType, "simple", false) - if ppTag == nil { - continue - } - - if globals != nil { - var globalFound bool - fieldType, valType, globalFound = populateFromGlobals(fieldType, valType, pathParamTagKey, globals) - if globalFound { - globalsAlreadyPopulated = append(globalsAlreadyPopulated, fieldType.Name) - } - } - - if ppTag.Serialization != "" { - vals, err := populateSerializedParams(ppTag, fieldType.Type, valType) - if err != nil { - return nil, err - } - for k, v := range vals { - parsedParameters[k] = url.PathEscape(v) - } - } else { - // TODO: support other styles - switch ppTag.Style { - case "simple": - simpleParams := getSimplePathParams(ppTag.ParamName, fieldType.Type, valType, ppTag.Explode) - for k, v := range simpleParams { - parsedParameters[k] = v - } - } - } - } - - return globalsAlreadyPopulated, nil -} - -func getSimplePathParams(parentName string, objType reflect.Type, objValue reflect.Value, explode bool) map[string]string { - pathParams := make(map[string]string) - - if isNil(objType, objValue) { - return nil - } - - if objType.Kind() == reflect.Ptr { - objType = objType.Elem() - objValue = objValue.Elem() - } - - switch objType.Kind() { - case reflect.Array, reflect.Slice: - if objValue.Len() == 0 { - return nil - } - var ppVals []string - for i := 0; i < objValue.Len(); i++ { - ppVals = append(ppVals, valToString(objValue.Index(i).Interface())) - } - pathParams[parentName] = strings.Join(ppVals, ",") - case reflect.Map: - if objValue.Len() == 0 { - return nil - } - var ppVals []string - objMap := objValue.MapRange() - for objMap.Next() { - if explode { - ppVals = append(ppVals, fmt.Sprintf("%s=%s", objMap.Key().String(), valToString(objMap.Value().Interface()))) - } else { - ppVals = append(ppVals, fmt.Sprintf("%s,%s", objMap.Key().String(), valToString(objMap.Value().Interface()))) - } - } - pathParams[parentName] = strings.Join(ppVals, ",") - case reflect.Struct: - switch objValue.Interface().(type) { - case time.Time: - pathParams[parentName] = valToString(objValue.Interface()) - case types.Date: - pathParams[parentName] = valToString(objValue.Interface()) - case big.Int: - pathParams[parentName] = valToString(objValue.Interface()) - case decimal.Big: - pathParams[parentName] = valToString(objValue.Interface()) - default: - var ppVals []string - for i := 0; i < objType.NumField(); i++ { - fieldType := objType.Field(i) - valType := objValue.Field(i) - - ppTag := parseParamTag(pathParamTagKey, fieldType, "simple", explode) - if ppTag == nil { - continue - } - - if isNil(fieldType.Type, valType) { - continue - } - - if fieldType.Type.Kind() == reflect.Pointer { - valType = valType.Elem() - } - - if explode { - ppVals = append(ppVals, fmt.Sprintf("%s=%s", ppTag.ParamName, valToString(valType.Interface()))) - } else { - ppVals = append(ppVals, fmt.Sprintf("%s,%s", ppTag.ParamName, valToString(valType.Interface()))) - } - } - pathParams[parentName] = strings.Join(ppVals, ",") - } - default: - pathParams[parentName] = valToString(objValue.Interface()) - } - - return pathParams -} diff --git a/components/ledger/pkg/client/internal/utils/queryparams.go b/components/ledger/pkg/client/internal/utils/queryparams.go deleted file mode 100644 index d83bf61d79..0000000000 --- a/components/ledger/pkg/client/internal/utils/queryparams.go +++ /dev/null @@ -1,259 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "net/http" - "net/url" - "reflect" - "time" - - "github.com/ericlagergren/decimal" - - "github.com/formancehq/stack/ledger/client/types" -) - -func PopulateQueryParams(_ context.Context, req *http.Request, queryParams interface{}, globals interface{}) error { - // Query parameters may already be present from overriding URL - if req.URL.RawQuery != "" { - return nil - } - - values := url.Values{} - - globalsAlreadyPopulated, err := populateQueryParams(queryParams, globals, values, []string{}) - if err != nil { - return err - } - - if globals != nil { - _, err = populateQueryParams(globals, nil, values, globalsAlreadyPopulated) - if err != nil { - return err - } - } - - req.URL.RawQuery = values.Encode() - - return nil -} - -func populateQueryParams(queryParams interface{}, globals interface{}, values url.Values, skipFields []string) ([]string, error) { - queryParamsStructType, queryParamsValType := dereferencePointers(reflect.TypeOf(queryParams), reflect.ValueOf(queryParams)) - - globalsAlreadyPopulated := []string{} - - for i := 0; i < queryParamsStructType.NumField(); i++ { - fieldType := queryParamsStructType.Field(i) - valType := queryParamsValType.Field(i) - - if contains(skipFields, fieldType.Name) { - continue - } - - requestTag := getRequestTag(fieldType) - if requestTag != nil { - continue - } - - qpTag := parseQueryParamTag(fieldType) - if qpTag == nil { - continue - } - - if globals != nil { - var globalFound bool - fieldType, valType, globalFound = populateFromGlobals(fieldType, valType, queryParamTagKey, globals) - if globalFound { - globalsAlreadyPopulated = append(globalsAlreadyPopulated, fieldType.Name) - } - } - - if qpTag.Serialization != "" { - vals, err := populateSerializedParams(qpTag, fieldType.Type, valType) - if err != nil { - return nil, err - } - for k, v := range vals { - values.Add(k, v) - } - } else { - switch qpTag.Style { - case "deepObject": - vals := populateDeepObjectParams(qpTag, fieldType.Type, valType) - for k, v := range vals { - for _, vv := range v { - values.Add(k, vv) - } - } - case "form": - vals := populateFormParams(qpTag, fieldType.Type, valType, ",") - for k, v := range vals { - for _, vv := range v { - values.Add(k, vv) - } - } - case "pipeDelimited": - vals := populateFormParams(qpTag, fieldType.Type, valType, "|") - for k, v := range vals { - for _, vv := range v { - values.Add(k, vv) - } - } - default: - return nil, fmt.Errorf("unsupported style: %s", qpTag.Style) - } - } - } - - return globalsAlreadyPopulated, nil -} - -func populateSerializedParams(tag *paramTag, objType reflect.Type, objValue reflect.Value) (map[string]string, error) { - if isNil(objType, objValue) { - return nil, nil - } - - if objType.Kind() == reflect.Pointer { - objValue = objValue.Elem() - } - - values := map[string]string{} - - switch tag.Serialization { - case "json": - data, err := json.Marshal(objValue.Interface()) - if err != nil { - return nil, fmt.Errorf("error marshaling json: %v", err) - } - values[tag.ParamName] = string(data) - } - - return values, nil -} - -func populateDeepObjectParams(tag *paramTag, objType reflect.Type, objValue reflect.Value) url.Values { - values := url.Values{} - - if isNil(objType, objValue) { - return values - } - - if objValue.Kind() == reflect.Pointer { - objValue = objValue.Elem() - } - - switch objValue.Kind() { - case reflect.Map: - populateDeepObjectParamsMap(values, tag.ParamName, objValue) - case reflect.Struct: - populateDeepObjectParamsStruct(values, tag.ParamName, objValue) - } - - return values -} - -func populateDeepObjectParamsArray(qsValues url.Values, priorScope string, value reflect.Value) { - if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { - return - } - - for i := 0; i < value.Len(); i++ { - qsValues.Add(priorScope, valToString(value.Index(i).Interface())) - } -} - -func populateDeepObjectParamsMap(qsValues url.Values, priorScope string, mapValue reflect.Value) { - if mapValue.Kind() != reflect.Map { - return - } - - iter := mapValue.MapRange() - - for iter.Next() { - scope := priorScope + "[" + iter.Key().String() + "]" - iterValue := iter.Value() - - switch iterValue.Kind() { - case reflect.Array, reflect.Slice: - populateDeepObjectParamsArray(qsValues, scope, iterValue) - case reflect.Map: - populateDeepObjectParamsMap(qsValues, scope, iterValue) - default: - qsValues.Add(scope, valToString(iterValue.Interface())) - } - } -} - -func populateDeepObjectParamsStruct(qsValues url.Values, priorScope string, structValue reflect.Value) { - if structValue.Kind() != reflect.Struct { - return - } - - structType := structValue.Type() - - for i := 0; i < structType.NumField(); i++ { - field := structType.Field(i) - fieldValue := structValue.Field(i) - - if isNil(field.Type, fieldValue) { - continue - } - - if fieldValue.Kind() == reflect.Pointer { - fieldValue = fieldValue.Elem() - } - - qpTag := parseQueryParamTag(field) - - if qpTag == nil { - continue - } - - scope := priorScope + "[" + qpTag.ParamName + "]" - - switch fieldValue.Kind() { - case reflect.Array, reflect.Slice: - populateDeepObjectParamsArray(qsValues, scope, fieldValue) - case reflect.Map: - populateDeepObjectParamsMap(qsValues, scope, fieldValue) - case reflect.Struct: - switch fieldValue.Type() { - case reflect.TypeOf(big.Int{}), reflect.TypeOf(decimal.Big{}), reflect.TypeOf(time.Time{}), reflect.TypeOf(types.Date{}): - qsValues.Add(scope, valToString(fieldValue.Interface())) - - continue - } - - populateDeepObjectParamsStruct(qsValues, scope, fieldValue) - default: - qsValues.Add(scope, valToString(fieldValue.Interface())) - } - } -} - -func populateFormParams(tag *paramTag, objType reflect.Type, objValue reflect.Value, delimiter string) url.Values { - return populateForm(tag.ParamName, tag.Explode, objType, objValue, delimiter, func(fieldType reflect.StructField) string { - qpTag := parseQueryParamTag(fieldType) - if qpTag == nil { - return "" - } - - return qpTag.ParamName - }) -} - -type paramTag struct { - Style string - Explode bool - ParamName string - Serialization string -} - -func parseQueryParamTag(field reflect.StructField) *paramTag { - return parseParamTag(queryParamTagKey, field, "form", true) -} diff --git a/components/ledger/pkg/client/internal/utils/requestbody.go b/components/ledger/pkg/client/internal/utils/requestbody.go deleted file mode 100644 index 0fd33cf653..0000000000 --- a/components/ledger/pkg/client/internal/utils/requestbody.go +++ /dev/null @@ -1,409 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "bytes" - "context" - "fmt" - "io" - "mime/multipart" - "net/url" - "reflect" - "regexp" -) - -const ( - requestTagKey = "request" - multipartFormTagKey = "multipartForm" - formTagKey = "form" -) - -var ( - jsonEncodingRegex = regexp.MustCompile(`(application|text)\/.*?\+*json.*`) - multipartEncodingRegex = regexp.MustCompile(`multipart\/.*`) - urlEncodedEncodingRegex = regexp.MustCompile(`application\/x-www-form-urlencoded.*`) -) - -func SerializeRequestBody(_ context.Context, request interface{}, nullable, optional bool, requestFieldName, serializationMethod, tag string) (io.Reader, string, error) { - bodyReader, contentType, err := serializeRequestBody(request, nullable, optional, requestFieldName, serializationMethod, tag) - if err != nil { - return nil, "", fmt.Errorf("error serializing request body: %w", err) - } - - if bodyReader == nil && !optional { - return nil, "", fmt.Errorf("request body is required") - } - - return bodyReader, contentType, nil -} - -func serializeRequestBody(request interface{}, nullable, optional bool, requestFieldName, serializationMethod, tag string) (io.Reader, string, error) { - requestStructType := reflect.TypeOf(request) - requestValType := reflect.ValueOf(request) - - if isNil(requestStructType, requestValType) { - if !nullable && optional { - return nil, "", nil - } - - return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag) - } - - if requestStructType.Kind() == reflect.Pointer { - requestStructType = requestStructType.Elem() - requestValType = requestValType.Elem() - } - - if requestStructType.Kind() != reflect.Struct { - return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag) - } - - requestField, ok := requestStructType.FieldByName(requestFieldName) - - if ok { - tag := getRequestTag(requestField) - if tag != nil { - // request object (non-flattened) - requestVal := requestValType.FieldByName(requestFieldName) - if isNil(requestField.Type, requestVal) { - if !nullable && optional { - return nil, "", nil - } - - return serializeContentType(requestFieldName, tag.MediaType, requestVal, string(requestField.Tag)) - } - - return serializeContentType(requestFieldName, tag.MediaType, requestVal, string(requestField.Tag)) - } - } - - // flattened request object - return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], reflect.ValueOf(request), tag) -} - -func serializeContentType(fieldName string, mediaType string, val reflect.Value, tag string) (*bytes.Buffer, string, error) { - buf := &bytes.Buffer{} - - if isNil(val.Type(), val) { - // TODO: what does a null mean for other content types? Just returning an empty buffer for now - if jsonEncodingRegex.MatchString(mediaType) { - if _, err := buf.Write([]byte("null")); err != nil { - return nil, "", err - } - } - - return buf, mediaType, nil - } - - switch { - case jsonEncodingRegex.MatchString(mediaType): - data, err := MarshalJSON(val.Interface(), reflect.StructTag(tag), true) - if err != nil { - return nil, "", err - } - - if _, err := buf.Write(data); err != nil { - return nil, "", err - } - case multipartEncodingRegex.MatchString(mediaType): - var err error - mediaType, err = encodeMultipartFormData(buf, val.Interface()) - if err != nil { - return nil, "", err - } - case urlEncodedEncodingRegex.MatchString(mediaType): - if err := encodeFormData(fieldName, buf, val.Interface()); err != nil { - return nil, "", err - } - default: - val = reflect.Indirect(val) - - switch { - case val.Type().Kind() == reflect.String: - if _, err := buf.WriteString(valToString(val.Interface())); err != nil { - return nil, "", err - } - case val.Type() == reflect.TypeOf([]byte(nil)): - if _, err := buf.Write(val.Bytes()); err != nil { - return nil, "", err - } - default: - return nil, "", fmt.Errorf("invalid request body type %s for mediaType %s", val.Type(), mediaType) - } - } - - return buf, mediaType, nil -} - -func encodeMultipartFormData(w io.Writer, data interface{}) (string, error) { - requestStructType := reflect.TypeOf(data) - requestValType := reflect.ValueOf(data) - - if requestStructType.Kind() == reflect.Pointer { - requestStructType = requestStructType.Elem() - requestValType = requestValType.Elem() - } - - writer := multipart.NewWriter(w) - - for i := 0; i < requestStructType.NumField(); i++ { - field := requestStructType.Field(i) - fieldType := field.Type - valType := requestValType.Field(i) - - if isNil(fieldType, valType) { - continue - } - - if fieldType.Kind() == reflect.Pointer { - fieldType = fieldType.Elem() - valType = valType.Elem() - } - - tag := parseMultipartFormTag(field) - if tag.File { - if err := encodeMultipartFormDataFile(writer, fieldType, valType); err != nil { - writer.Close() - return "", err - } - } else if tag.JSON { - jw, err := writer.CreateFormField(tag.Name) - if err != nil { - writer.Close() - return "", err - } - d, err := MarshalJSON(valType.Interface(), field.Tag, true) - if err != nil { - writer.Close() - return "", err - } - if _, err := jw.Write(d); err != nil { - writer.Close() - return "", err - } - } else { - switch fieldType.Kind() { - case reflect.Slice, reflect.Array: - values := parseDelimitedArray(true, valType, ",") - for _, v := range values { - if err := writer.WriteField(tag.Name+"[]", v); err != nil { - writer.Close() - return "", err - } - } - default: - if err := writer.WriteField(tag.Name, valToString(valType.Interface())); err != nil { - writer.Close() - return "", err - } - } - } - } - - if err := writer.Close(); err != nil { - return "", err - } - - return writer.FormDataContentType(), nil -} - -func encodeMultipartFormDataFile(w *multipart.Writer, fieldType reflect.Type, valType reflect.Value) error { - if fieldType.Kind() != reflect.Struct { - return fmt.Errorf("invalid type %s for multipart/form-data file", valType.Type()) - } - - var fieldName string - var fileName string - var content []byte - - for i := 0; i < fieldType.NumField(); i++ { - field := fieldType.Field(i) - val := valType.Field(i) - - tag := parseMultipartFormTag(field) - if !tag.Content && tag.Name == "" { - continue - } - - if tag.Content { - content = val.Bytes() - } else { - fieldName = tag.Name - fileName = val.String() - } - } - - if fieldName == "" || fileName == "" || content == nil { - return fmt.Errorf("invalid multipart/form-data file") - } - - fw, err := w.CreateFormFile(fieldName, fileName) - if err != nil { - return err - } - if _, err := fw.Write(content); err != nil { - return err - } - - return nil -} - -func encodeFormData(fieldName string, w io.Writer, data interface{}) error { - requestType := reflect.TypeOf(data) - requestValType := reflect.ValueOf(data) - - if requestType.Kind() == reflect.Pointer { - requestType = requestType.Elem() - requestValType = requestValType.Elem() - } - - dataValues := url.Values{} - - switch requestType.Kind() { - case reflect.Struct: - for i := 0; i < requestType.NumField(); i++ { - field := requestType.Field(i) - fieldType := field.Type - valType := requestValType.Field(i) - - if isNil(fieldType, valType) { - continue - } - - if fieldType.Kind() == reflect.Pointer { - fieldType = fieldType.Elem() - valType = valType.Elem() - } - - tag := parseFormTag(field) - if tag.JSON { - data, err := MarshalJSON(valType.Interface(), field.Tag, true) - if err != nil { - return err - } - dataValues.Set(tag.Name, string(data)) - } else { - switch tag.Style { - // TODO: support other styles - case "form": - values := populateForm(tag.Name, tag.Explode, fieldType, valType, ",", func(sf reflect.StructField) string { - tag := parseFormTag(field) - if tag == nil { - return "" - } - - return tag.Name - }) - for k, v := range values { - for _, vv := range v { - dataValues.Add(k, vv) - } - } - } - } - } - case reflect.Map: - for _, k := range requestValType.MapKeys() { - v := requestValType.MapIndex(k) - dataValues.Set(fmt.Sprintf("%v", k.Interface()), valToString(v.Interface())) - } - case reflect.Slice, reflect.Array: - for i := 0; i < requestValType.Len(); i++ { - v := requestValType.Index(i) - dataValues.Set(fieldName, valToString(v.Interface())) - } - } - - if _, err := w.Write([]byte(dataValues.Encode())); err != nil { - return err - } - - return nil -} - -type requestTag struct { - MediaType string -} - -func getRequestTag(field reflect.StructField) *requestTag { - // example `request:"mediaType=multipart/form-data"` - values := parseStructTag(requestTagKey, field) - if values == nil { - return nil - } - - tag := &requestTag{ - MediaType: "application/octet-stream", - } - - for k, v := range values { - switch k { - case "mediaType": - tag.MediaType = v - } - } - - return tag -} - -type multipartFormTag struct { - File bool - Content bool - JSON bool - Name string -} - -func parseMultipartFormTag(field reflect.StructField) *multipartFormTag { - // example `multipartForm:"name=file"` - values := parseStructTag(multipartFormTagKey, field) - - tag := &multipartFormTag{} - - for k, v := range values { - switch k { - case "file": - tag.File = v == "true" - case "content": - tag.Content = v == "true" - case "name": - tag.Name = v - case "json": - tag.JSON = v == "true" - } - } - - return tag -} - -type formTag struct { - Name string - JSON bool - Style string - Explode bool -} - -func parseFormTag(field reflect.StructField) *formTag { - // example `form:"name=propName,style=spaceDelimited,explode"` - values := parseStructTag(formTagKey, field) - - tag := &formTag{ - Style: "form", - Explode: true, - } - - for k, v := range values { - switch k { - case "name": - tag.Name = v - case "json": - tag.JSON = v == "true" - case "style": - tag.Style = v - case "explode": - tag.Explode = v == "true" - } - } - - return tag -} diff --git a/components/ledger/pkg/client/internal/utils/retries.go b/components/ledger/pkg/client/internal/utils/retries.go deleted file mode 100644 index 1763518a8a..0000000000 --- a/components/ledger/pkg/client/internal/utils/retries.go +++ /dev/null @@ -1,110 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "errors" - "fmt" - "github.com/cenkalti/backoff/v4" - "github.com/formancehq/stack/ledger/client/retry" - "net/http" - "net/url" - "strconv" - "strings" - "time" -) - -var errRequestFailed = errors.New("request failed") - -// Deprecated: Use retry.BackoffStrategy instead. -type BackoffStrategy = retry.BackoffStrategy - -// Deprecated: Use retry.Config instead. -type RetryConfig = retry.Config - -type Retries struct { - Config *retry.Config - StatusCodes []string -} - -func Retry(ctx context.Context, r Retries, action func() (*http.Response, error)) (*http.Response, error) { - switch r.Config.Strategy { - case "backoff": - if r.Config.Backoff == nil { - return action() - } - - config := backoff.NewExponentialBackOff() - config.InitialInterval = time.Duration(r.Config.Backoff.InitialInterval) * time.Millisecond - config.MaxInterval = time.Duration(r.Config.Backoff.MaxInterval) * time.Millisecond - config.Multiplier = r.Config.Backoff.Exponent - config.MaxElapsedTime = time.Duration(r.Config.Backoff.MaxElapsedTime) * time.Millisecond - config.Reset() - - var resp *http.Response - - err := backoff.Retry(func() error { - if resp != nil { - resp.Body.Close() - } - - select { - case <-ctx.Done(): - return backoff.Permanent(ctx.Err()) - default: - } - - res, err := action() - if err != nil { - urlError := new(url.Error) - if errors.As(err, &urlError) { - if (urlError.Temporary() || urlError.Timeout()) && r.Config.RetryConnectionErrors { - return err - } - } - - return backoff.Permanent(err) - } - resp = res - if res == nil { - return fmt.Errorf("no response") - } - - for _, code := range r.StatusCodes { - if strings.Contains(strings.ToUpper(code), "X") { - codeRange, err := strconv.Atoi(code[:1]) - if err != nil { - continue - } - - s := res.StatusCode / 100 - - if s >= codeRange && s < codeRange+1 { - return errRequestFailed - } - } else { - parsedCode, err := strconv.Atoi(code) - if err != nil { - continue - } - - if res.StatusCode == parsedCode { - return errRequestFailed - } - } - } - - resp = res - - return nil - }, config) - if err != nil && !errors.Is(err, errRequestFailed) { - return nil, err - } - - return resp, nil - default: - return action() - } -} diff --git a/components/ledger/pkg/client/internal/utils/security.go b/components/ledger/pkg/client/internal/utils/security.go deleted file mode 100644 index d2558cd730..0000000000 --- a/components/ledger/pkg/client/internal/utils/security.go +++ /dev/null @@ -1,274 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "encoding/base64" - "fmt" - "net/http" - "reflect" - "strings" -) - -const ( - securityTagKey = "security" -) - -type securityTag struct { - Option bool - Scheme bool - Name string - Type string - SubType string -} - -func PopulateSecurity(ctx context.Context, req *http.Request, securitySource func(context.Context) (interface{}, error)) error { - if securitySource == nil { - return nil - } - - security, err := securitySource(ctx) - if err != nil { - return err - } - - headers := make(map[string]string) - queryParams := make(map[string]string) - - securityValType := trueReflectValue(reflect.ValueOf(security)) - securityStructType := securityValType.Type() - - if isNil(securityStructType, securityValType) { - return nil - } - - if securityStructType.Kind() == reflect.Ptr { - securityStructType = securityStructType.Elem() - securityValType = securityValType.Elem() - } - - for i := 0; i < securityStructType.NumField(); i++ { - fieldType := securityStructType.Field(i) - valType := securityValType.Field(i) - - kind := valType.Kind() - - if isNil(fieldType.Type, valType) { - continue - } - - if fieldType.Type.Kind() == reflect.Pointer { - kind = valType.Elem().Kind() - } - - secTag := parseSecurityTag(fieldType) - if secTag != nil { - if secTag.Option { - handleSecurityOption(headers, queryParams, valType.Interface()) - } else if secTag.Scheme { - // Special case for basic auth which could be a flattened struct - if secTag.SubType == "basic" && kind != reflect.Struct { - parseSecurityScheme(headers, queryParams, secTag, security) - } else { - parseSecurityScheme(headers, queryParams, secTag, valType.Interface()) - } - } - } - } - - for key, value := range headers { - req.Header.Add(key, value) - } - - query := req.URL.Query() - for key, value := range queryParams { - query.Add(key, value) - } - req.URL.RawQuery = query.Encode() - - return nil -} - -func handleSecurityOption(headers, queryParams map[string]string, option interface{}) { - optionValType := trueReflectValue(reflect.ValueOf(option)) - optionStructType := optionValType.Type() - - if isNil(optionStructType, optionValType) { - return - } - - for i := 0; i < optionStructType.NumField(); i++ { - fieldType := optionStructType.Field(i) - valType := optionValType.Field(i) - - secTag := parseSecurityTag(fieldType) - if secTag != nil && secTag.Scheme { - parseSecurityScheme(headers, queryParams, secTag, valType.Interface()) - } - } -} - -func parseSecurityScheme(headers, queryParams map[string]string, schemeTag *securityTag, scheme interface{}) { - schemeVal := trueReflectValue(reflect.ValueOf(scheme)) - schemeType := schemeVal.Type() - - if isNil(schemeType, schemeVal) { - return - } - - if schemeType.Kind() == reflect.Struct { - if schemeTag.Type == "http" && schemeTag.SubType == "basic" { - handleBasicAuthScheme(headers, schemeVal.Interface()) - return - } - - for i := 0; i < schemeType.NumField(); i++ { - fieldType := schemeType.Field(i) - valType := schemeVal.Field(i) - - if isNil(fieldType.Type, valType) { - continue - } - - if fieldType.Type.Kind() == reflect.Ptr { - valType = valType.Elem() - } - - secTag := parseSecurityTag(fieldType) - if secTag == nil || secTag.Name == "" { - return - } - - parseSecuritySchemeValue(headers, queryParams, schemeTag, secTag, valType.Interface()) - } - } else { - parseSecuritySchemeValue(headers, queryParams, schemeTag, schemeTag, schemeVal.Interface()) - } -} - -func parseSecuritySchemeValue(headers, queryParams map[string]string, schemeTag *securityTag, secTag *securityTag, val interface{}) { - switch schemeTag.Type { - case "apiKey": - switch schemeTag.SubType { - case "header": - headers[secTag.Name] = valToString(val) - case "query": - queryParams[secTag.Name] = valToString(val) - case "cookie": - headers["Cookie"] = fmt.Sprintf("%s=%s", secTag.Name, valToString(val)) - default: - panic("not supported") - } - case "openIdConnect": - headers[secTag.Name] = prefixBearer(valToString(val)) - case "oauth2": - if schemeTag.SubType != "client_credentials" { - headers[secTag.Name] = prefixBearer(valToString(val)) - } - case "http": - switch schemeTag.SubType { - case "bearer": - headers[secTag.Name] = prefixBearer(valToString(val)) - default: - panic("not supported") - } - default: - panic("not supported") - } -} - -func prefixBearer(authHeaderValue string) string { - if strings.HasPrefix(strings.ToLower(authHeaderValue), "bearer ") { - return authHeaderValue - } - - return fmt.Sprintf("Bearer %s", authHeaderValue) -} - -func handleBasicAuthScheme(headers map[string]string, scheme interface{}) { - schemeStructType := reflect.TypeOf(scheme) - schemeValType := reflect.ValueOf(scheme) - - var username, password string - - for i := 0; i < schemeStructType.NumField(); i++ { - fieldType := schemeStructType.Field(i) - valType := schemeValType.Field(i) - - if fieldType.Type.Kind() == reflect.Ptr { - valType = valType.Elem() - } - - secTag := parseSecurityTag(fieldType) - if secTag == nil || secTag.Name == "" { - continue - } - - switch secTag.Name { - case "username": - username = valType.String() - case "password": - password = valType.String() - } - } - - headers["Authorization"] = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))) -} - -func parseSecurityTag(field reflect.StructField) *securityTag { - tag := field.Tag.Get(securityTagKey) - if tag == "" { - return nil - } - - option := false - scheme := false - name := "" - securityType := "" - securitySubType := "" - - options := strings.Split(tag, ",") - for _, optionConf := range options { - parts := strings.Split(optionConf, "=") - if len(parts) < 1 || len(parts) > 2 { - continue - } - - switch parts[0] { - case "name": - name = parts[1] - case "type": - securityType = parts[1] - case "subtype": - securitySubType = parts[1] - case "option": - option = true - case "scheme": - scheme = true - } - } - - // TODO: validate tag? - - return &securityTag{ - Option: option, - Scheme: scheme, - Name: name, - Type: securityType, - SubType: securitySubType, - } -} - -func trueReflectValue(val reflect.Value) reflect.Value { - kind := val.Type().Kind() - for kind == reflect.Interface || kind == reflect.Ptr { - innerVal := val.Elem() - if !innerVal.IsValid() { - break - } - val = innerVal - kind = val.Type().Kind() - } - return val -} diff --git a/components/ledger/pkg/client/internal/utils/utils.go b/components/ledger/pkg/client/internal/utils/utils.go deleted file mode 100644 index adb2f7055b..0000000000 --- a/components/ledger/pkg/client/internal/utils/utils.go +++ /dev/null @@ -1,230 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package utils - -import ( - "context" - "fmt" - "io" - "math/big" - "reflect" - "regexp" - "strconv" - "strings" - "time" - - "github.com/ericlagergren/decimal" -) - -const ( - queryParamTagKey = "queryParam" - headerParamTagKey = "header" - pathParamTagKey = "pathParam" -) - -var ( - paramRegex = regexp.MustCompile(`({.*?})`) - SerializationMethodToContentType = map[string]string{ - "json": "application/json", - "form": "application/x-www-form-urlencoded", - "multipart": "multipart/form-data", - "raw": "application/octet-stream", - "string": "text/plain", - } -) - -func UnmarshalJsonFromResponseBody(body io.Reader, out interface{}, tag string) error { - data, err := io.ReadAll(body) - if err != nil { - return fmt.Errorf("error reading response body: %w", err) - } - if err := UnmarshalJSON(data, out, reflect.StructTag(tag), true, false); err != nil { - return fmt.Errorf("error unmarshalling json response body: %w", err) - } - - return nil -} - -func ReplaceParameters(stringWithParams string, params map[string]string) string { - if len(params) == 0 { - return stringWithParams - } - - return paramRegex.ReplaceAllStringFunc(stringWithParams, func(match string) string { - match = match[1 : len(match)-1] - return params[match] - }) -} - -func Contains(slice []string, item string) bool { - for _, s := range slice { - if s == item { - return true - } - } - return false -} - -func MatchStatusCodes(expectedCodes []string, statusCode int) bool { - for _, codeStr := range expectedCodes { - code, err := strconv.Atoi(codeStr) - if err == nil { - if code == statusCode { - return true - } - continue - } - - codeRange, err := strconv.Atoi(string(codeStr[0])) - if err != nil { - continue - } - - if statusCode >= (codeRange*100) && statusCode < ((codeRange+1)*100) { - return true - } - } - - return false -} - -func AsSecuritySource(security interface{}) func(context.Context) (interface{}, error) { - return func(context.Context) (interface{}, error) { - return security, nil - } -} - -func parseStructTag(tagKey string, field reflect.StructField) map[string]string { - tag := field.Tag.Get(tagKey) - if tag == "" { - return nil - } - - values := map[string]string{} - - options := strings.Split(tag, ",") - for _, optionConf := range options { - parts := strings.Split(optionConf, "=") - - switch len(parts) { - case 1: - // flag option - parts = append(parts, "true") - case 2: - // key=value option - default: - // invalid option - continue - } - - values[parts[0]] = parts[1] - } - - return values -} - -func parseParamTag(tagKey string, field reflect.StructField, defaultStyle string, defaultExplode bool) *paramTag { - // example `{tagKey}:"style=simple,explode=false,name=apiID"` - values := parseStructTag(tagKey, field) - if values == nil { - return nil - } - - tag := ¶mTag{ - Style: defaultStyle, - Explode: defaultExplode, - ParamName: strings.ToLower(field.Name), - } - - for k, v := range values { - switch k { - case "style": - tag.Style = v - case "explode": - tag.Explode = v == "true" - case "name": - tag.ParamName = v - case "serialization": - tag.Serialization = v - } - } - - return tag -} - -func valToString(val interface{}) string { - switch v := val.(type) { - case time.Time: - return v.Format(time.RFC3339Nano) - case big.Int: - return v.String() - case decimal.Big: - return v.String() - default: - return fmt.Sprintf("%v", v) - } -} - -func populateFromGlobals(fieldType reflect.StructField, valType reflect.Value, paramType string, globals interface{}) (reflect.StructField, reflect.Value, bool) { - if globals == nil { - return fieldType, valType, false - } - - globalsStruct := reflect.TypeOf(globals) - globalsStructVal := reflect.ValueOf(globals) - - globalsField, found := globalsStruct.FieldByName(fieldType.Name) - if !found { - return fieldType, valType, false - } - - if fieldType.Type.Kind() != reflect.Ptr || !valType.IsNil() { - return fieldType, valType, true - } - - globalsVal := globalsStructVal.FieldByName(fieldType.Name) - - if !globalsVal.IsValid() { - return fieldType, valType, false - } - - switch paramType { - case queryParamTagKey: - qpTag := parseQueryParamTag(globalsField) - if qpTag == nil { - return fieldType, valType, false - } - default: - tag := parseParamTag(paramType, fieldType, "simple", false) - if tag == nil { - return fieldType, valType, false - } - } - - return globalsField, globalsVal, true -} - -func isNil(typ reflect.Type, val reflect.Value) bool { - // `reflect.TypeOf(nil) == nil` so calling typ.Kind() will cause a nil pointer - // dereference panic. Catch it and return early. - // https://github.com/golang/go/issues/51649 - // https://github.com/golang/go/issues/54208 - if typ == nil { - return true - } - - if typ.Kind() == reflect.Ptr || typ.Kind() == reflect.Map || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Interface { - return val.IsNil() - } - - return false -} - -func contains(arr []string, str string) bool { - for _, a := range arr { - if a == str { - return true - } - } - return false -} diff --git a/components/ledger/pkg/client/ledger.go b/components/ledger/pkg/client/ledger.go deleted file mode 100644 index 25fcac4080..0000000000 --- a/components/ledger/pkg/client/ledger.go +++ /dev/null @@ -1,18 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package client - -type Ledger struct { - V1 *V1 - V2 *V2 - - sdkConfiguration sdkConfiguration -} - -func newLedger(sdkConfig sdkConfiguration) *Ledger { - return &Ledger{ - sdkConfiguration: sdkConfig, - V1: newV1(sdkConfig), - V2: newV2(sdkConfig), - } -} diff --git a/components/ledger/pkg/client/models/components/account.go b/components/ledger/pkg/client/models/components/account.go deleted file mode 100644 index c3a0821928..0000000000 --- a/components/ledger/pkg/client/models/components/account.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Account struct { - Address string `json:"address"` - Type *string `json:"type,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` -} - -func (o *Account) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *Account) GetType() *string { - if o == nil { - return nil - } - return o.Type -} - -func (o *Account) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/accountresponse.go b/components/ledger/pkg/client/models/components/accountresponse.go deleted file mode 100644 index 8272827c4a..0000000000 --- a/components/ledger/pkg/client/models/components/accountresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type AccountResponse struct { - Data AccountWithVolumesAndBalances `json:"data"` -} - -func (o *AccountResponse) GetData() AccountWithVolumesAndBalances { - if o == nil { - return AccountWithVolumesAndBalances{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/accountscursorresponse.go b/components/ledger/pkg/client/models/components/accountscursorresponse.go deleted file mode 100644 index da12f0630a..0000000000 --- a/components/ledger/pkg/client/models/components/accountscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Cursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []Account `json:"data"` -} - -func (o *Cursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *Cursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *Cursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *Cursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *Cursor) GetData() []Account { - if o == nil { - return []Account{} - } - return o.Data -} - -type AccountsCursorResponse struct { - Cursor Cursor `json:"cursor"` -} - -func (o *AccountsCursorResponse) GetCursor() Cursor { - if o == nil { - return Cursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/accountwithvolumesandbalances.go b/components/ledger/pkg/client/models/components/accountwithvolumesandbalances.go deleted file mode 100644 index 7a2311e123..0000000000 --- a/components/ledger/pkg/client/models/components/accountwithvolumesandbalances.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type AccountWithVolumesAndBalances struct { - Address string `json:"address"` - Type *string `json:"type,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Volumes map[string]Volume `json:"volumes,omitempty"` - Balances map[string]*big.Int `json:"balances,omitempty"` -} - -func (a AccountWithVolumesAndBalances) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(a, "", false) -} - -func (a *AccountWithVolumesAndBalances) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &a, "", false, false); err != nil { - return err - } - return nil -} - -func (o *AccountWithVolumesAndBalances) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *AccountWithVolumesAndBalances) GetType() *string { - if o == nil { - return nil - } - return o.Type -} - -func (o *AccountWithVolumesAndBalances) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *AccountWithVolumesAndBalances) GetVolumes() map[string]Volume { - if o == nil { - return nil - } - return o.Volumes -} - -func (o *AccountWithVolumesAndBalances) GetBalances() map[string]*big.Int { - if o == nil { - return nil - } - return o.Balances -} diff --git a/components/ledger/pkg/client/models/components/aggregatebalancesresponse.go b/components/ledger/pkg/client/models/components/aggregatebalancesresponse.go deleted file mode 100644 index 98d34348de..0000000000 --- a/components/ledger/pkg/client/models/components/aggregatebalancesresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type AggregateBalancesResponse struct { - Data map[string]int64 `json:"data"` -} - -func (o *AggregateBalancesResponse) GetData() map[string]int64 { - if o == nil { - return map[string]int64{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/balancescursorresponse.go b/components/ledger/pkg/client/models/components/balancescursorresponse.go deleted file mode 100644 index ffa4e640ca..0000000000 --- a/components/ledger/pkg/client/models/components/balancescursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type BalancesCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []map[string]map[string]int64 `json:"data"` -} - -func (o *BalancesCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *BalancesCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *BalancesCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *BalancesCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *BalancesCursorResponseCursor) GetData() []map[string]map[string]int64 { - if o == nil { - return []map[string]map[string]int64{} - } - return o.Data -} - -type BalancesCursorResponse struct { - Cursor BalancesCursorResponseCursor `json:"cursor"` -} - -func (o *BalancesCursorResponse) GetCursor() BalancesCursorResponseCursor { - if o == nil { - return BalancesCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/config.go b/components/ledger/pkg/client/models/components/config.go deleted file mode 100644 index 9d082125e0..0000000000 --- a/components/ledger/pkg/client/models/components/config.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Config struct { - Storage LedgerStorage `json:"storage"` -} - -func (o *Config) GetStorage() LedgerStorage { - if o == nil { - return LedgerStorage{} - } - return o.Storage -} diff --git a/components/ledger/pkg/client/models/components/configinfo.go b/components/ledger/pkg/client/models/components/configinfo.go deleted file mode 100644 index c157304daa..0000000000 --- a/components/ledger/pkg/client/models/components/configinfo.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type ConfigInfo struct { - Config Config `json:"config"` - Server string `json:"server"` - Version string `json:"version"` -} - -func (o *ConfigInfo) GetConfig() Config { - if o == nil { - return Config{} - } - return o.Config -} - -func (o *ConfigInfo) GetServer() string { - if o == nil { - return "" - } - return o.Server -} - -func (o *ConfigInfo) GetVersion() string { - if o == nil { - return "" - } - return o.Version -} diff --git a/components/ledger/pkg/client/models/components/configinforesponse.go b/components/ledger/pkg/client/models/components/configinforesponse.go deleted file mode 100644 index 00b9ef3081..0000000000 --- a/components/ledger/pkg/client/models/components/configinforesponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type ConfigInfoResponse struct { - Data ConfigInfo `json:"data"` -} - -func (o *ConfigInfoResponse) GetData() ConfigInfo { - if o == nil { - return ConfigInfo{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/contract.go b/components/ledger/pkg/client/models/components/contract.go deleted file mode 100644 index caab5a7983..0000000000 --- a/components/ledger/pkg/client/models/components/contract.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Expr struct { -} - -type Contract struct { - Account *string `json:"account,omitempty"` - Expr Expr `json:"expr"` -} - -func (o *Contract) GetAccount() *string { - if o == nil { - return nil - } - return o.Account -} - -func (o *Contract) GetExpr() Expr { - if o == nil { - return Expr{} - } - return o.Expr -} diff --git a/components/ledger/pkg/client/models/components/errorsenum.go b/components/ledger/pkg/client/models/components/errorsenum.go deleted file mode 100644 index d38e729ed8..0000000000 --- a/components/ledger/pkg/client/models/components/errorsenum.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" -) - -type ErrorsEnum string - -const ( - ErrorsEnumInternal ErrorsEnum = "INTERNAL" - ErrorsEnumInsufficientFund ErrorsEnum = "INSUFFICIENT_FUND" - ErrorsEnumValidation ErrorsEnum = "VALIDATION" - ErrorsEnumConflict ErrorsEnum = "CONFLICT" - ErrorsEnumNoScript ErrorsEnum = "NO_SCRIPT" - ErrorsEnumCompilationFailed ErrorsEnum = "COMPILATION_FAILED" - ErrorsEnumMetadataOverride ErrorsEnum = "METADATA_OVERRIDE" - ErrorsEnumNotFound ErrorsEnum = "NOT_FOUND" -) - -func (e ErrorsEnum) ToPointer() *ErrorsEnum { - return &e -} -func (e *ErrorsEnum) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "INTERNAL": - fallthrough - case "INSUFFICIENT_FUND": - fallthrough - case "VALIDATION": - fallthrough - case "CONFLICT": - fallthrough - case "NO_SCRIPT": - fallthrough - case "COMPILATION_FAILED": - fallthrough - case "METADATA_OVERRIDE": - fallthrough - case "NOT_FOUND": - *e = ErrorsEnum(v) - return nil - default: - return fmt.Errorf("invalid value for ErrorsEnum: %v", v) - } -} diff --git a/components/ledger/pkg/client/models/components/httpmetadata.go b/components/ledger/pkg/client/models/components/httpmetadata.go deleted file mode 100644 index e18bdc060a..0000000000 --- a/components/ledger/pkg/client/models/components/httpmetadata.go +++ /dev/null @@ -1,28 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "net/http" -) - -type HTTPMetadata struct { - // Raw HTTP response; suitable for custom response parsing - Response *http.Response `json:"-"` - // Raw HTTP request; suitable for debugging - Request *http.Request `json:"-"` -} - -func (o *HTTPMetadata) GetResponse() *http.Response { - if o == nil { - return nil - } - return o.Response -} - -func (o *HTTPMetadata) GetRequest() *http.Request { - if o == nil { - return nil - } - return o.Request -} diff --git a/components/ledger/pkg/client/models/components/ledgerinfo.go b/components/ledger/pkg/client/models/components/ledgerinfo.go deleted file mode 100644 index bcc42a98b7..0000000000 --- a/components/ledger/pkg/client/models/components/ledgerinfo.go +++ /dev/null @@ -1,33 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Storage struct { - Migrations []MigrationInfo `json:"migrations,omitempty"` -} - -func (o *Storage) GetMigrations() []MigrationInfo { - if o == nil { - return nil - } - return o.Migrations -} - -type LedgerInfo struct { - Name *string `json:"name,omitempty"` - Storage *Storage `json:"storage,omitempty"` -} - -func (o *LedgerInfo) GetName() *string { - if o == nil { - return nil - } - return o.Name -} - -func (o *LedgerInfo) GetStorage() *Storage { - if o == nil { - return nil - } - return o.Storage -} diff --git a/components/ledger/pkg/client/models/components/ledgerinforesponse.go b/components/ledger/pkg/client/models/components/ledgerinforesponse.go deleted file mode 100644 index cba04ab2f8..0000000000 --- a/components/ledger/pkg/client/models/components/ledgerinforesponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type LedgerInfoResponse struct { - Data *LedgerInfo `json:"data,omitempty"` -} - -func (o *LedgerInfoResponse) GetData() *LedgerInfo { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/ledgerstorage.go b/components/ledger/pkg/client/models/components/ledgerstorage.go deleted file mode 100644 index 30e3302e7b..0000000000 --- a/components/ledger/pkg/client/models/components/ledgerstorage.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type LedgerStorage struct { - Driver string `json:"driver"` - Ledgers []string `json:"ledgers"` -} - -func (o *LedgerStorage) GetDriver() string { - if o == nil { - return "" - } - return o.Driver -} - -func (o *LedgerStorage) GetLedgers() []string { - if o == nil { - return []string{} - } - return o.Ledgers -} diff --git a/components/ledger/pkg/client/models/components/log.go b/components/ledger/pkg/client/models/components/log.go deleted file mode 100644 index 1dc0112797..0000000000 --- a/components/ledger/pkg/client/models/components/log.go +++ /dev/null @@ -1,90 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type Type string - -const ( - TypeNewTransaction Type = "NEW_TRANSACTION" - TypeSetMetadata Type = "SET_METADATA" -) - -func (e Type) ToPointer() *Type { - return &e -} -func (e *Type) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "NEW_TRANSACTION": - fallthrough - case "SET_METADATA": - *e = Type(v) - return nil - default: - return fmt.Errorf("invalid value for Type: %v", v) - } -} - -type Log struct { - ID int64 `json:"id"` - Type Type `json:"type"` - Data map[string]any `json:"data"` - Hash string `json:"hash"` - Date time.Time `json:"date"` -} - -func (l Log) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(l, "", false) -} - -func (l *Log) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &l, "", false, false); err != nil { - return err - } - return nil -} - -func (o *Log) GetID() int64 { - if o == nil { - return 0 - } - return o.ID -} - -func (o *Log) GetType() Type { - if o == nil { - return Type("") - } - return o.Type -} - -func (o *Log) GetData() map[string]any { - if o == nil { - return map[string]any{} - } - return o.Data -} - -func (o *Log) GetHash() string { - if o == nil { - return "" - } - return o.Hash -} - -func (o *Log) GetDate() time.Time { - if o == nil { - return time.Time{} - } - return o.Date -} diff --git a/components/ledger/pkg/client/models/components/logscursorresponse.go b/components/ledger/pkg/client/models/components/logscursorresponse.go deleted file mode 100644 index 091c46930e..0000000000 --- a/components/ledger/pkg/client/models/components/logscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type LogsCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []Log `json:"data"` -} - -func (o *LogsCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *LogsCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *LogsCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *LogsCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *LogsCursorResponseCursor) GetData() []Log { - if o == nil { - return []Log{} - } - return o.Data -} - -type LogsCursorResponse struct { - Cursor LogsCursorResponseCursor `json:"cursor"` -} - -func (o *LogsCursorResponse) GetCursor() LogsCursorResponseCursor { - if o == nil { - return LogsCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/mapping.go b/components/ledger/pkg/client/models/components/mapping.go deleted file mode 100644 index f14fcab66d..0000000000 --- a/components/ledger/pkg/client/models/components/mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Mapping struct { - Contracts []Contract `json:"contracts"` -} - -func (o *Mapping) GetContracts() []Contract { - if o == nil { - return []Contract{} - } - return o.Contracts -} diff --git a/components/ledger/pkg/client/models/components/mappingresponse.go b/components/ledger/pkg/client/models/components/mappingresponse.go deleted file mode 100644 index cda00d26be..0000000000 --- a/components/ledger/pkg/client/models/components/mappingresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type MappingResponse struct { - Data *Mapping `json:"data,omitempty"` -} - -func (o *MappingResponse) GetData() *Mapping { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/migrationinfo.go b/components/ledger/pkg/client/models/components/migrationinfo.go deleted file mode 100644 index 427c31624d..0000000000 --- a/components/ledger/pkg/client/models/components/migrationinfo.go +++ /dev/null @@ -1,82 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type State string - -const ( - StateToDo State = "TO DO" - StateDone State = "DONE" -) - -func (e State) ToPointer() *State { - return &e -} -func (e *State) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "TO DO": - fallthrough - case "DONE": - *e = State(v) - return nil - default: - return fmt.Errorf("invalid value for State: %v", v) - } -} - -type MigrationInfo struct { - Version *int64 `json:"version,omitempty"` - Name *string `json:"name,omitempty"` - Date *time.Time `json:"date,omitempty"` - State *State `json:"state,omitempty"` -} - -func (m MigrationInfo) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(m, "", false) -} - -func (m *MigrationInfo) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &m, "", false, false); err != nil { - return err - } - return nil -} - -func (o *MigrationInfo) GetVersion() *int64 { - if o == nil { - return nil - } - return o.Version -} - -func (o *MigrationInfo) GetName() *string { - if o == nil { - return nil - } - return o.Name -} - -func (o *MigrationInfo) GetDate() *time.Time { - if o == nil { - return nil - } - return o.Date -} - -func (o *MigrationInfo) GetState() *State { - if o == nil { - return nil - } - return o.State -} diff --git a/components/ledger/pkg/client/models/components/posting.go b/components/ledger/pkg/client/models/components/posting.go deleted file mode 100644 index ab77c35b8f..0000000000 --- a/components/ledger/pkg/client/models/components/posting.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type Posting struct { - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` - Destination string `json:"destination"` - Source string `json:"source"` -} - -func (p Posting) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(p, "", false) -} - -func (p *Posting) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &p, "", false, false); err != nil { - return err - } - return nil -} - -func (o *Posting) GetAmount() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Amount -} - -func (o *Posting) GetAsset() string { - if o == nil { - return "" - } - return o.Asset -} - -func (o *Posting) GetDestination() string { - if o == nil { - return "" - } - return o.Destination -} - -func (o *Posting) GetSource() string { - if o == nil { - return "" - } - return o.Source -} diff --git a/components/ledger/pkg/client/models/components/posttransaction.go b/components/ledger/pkg/client/models/components/posttransaction.go deleted file mode 100644 index a580b461fd..0000000000 --- a/components/ledger/pkg/client/models/components/posttransaction.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type PostTransactionScript struct { - Plain string `json:"plain"` - Vars map[string]any `json:"vars,omitempty"` -} - -func (o *PostTransactionScript) GetPlain() string { - if o == nil { - return "" - } - return o.Plain -} - -func (o *PostTransactionScript) GetVars() map[string]any { - if o == nil { - return nil - } - return o.Vars -} - -type PostTransaction struct { - Timestamp *time.Time `json:"timestamp,omitempty"` - Postings []Posting `json:"postings,omitempty"` - Script *PostTransactionScript `json:"script,omitempty"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` -} - -func (p PostTransaction) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(p, "", false) -} - -func (p *PostTransaction) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &p, "", false, false); err != nil { - return err - } - return nil -} - -func (o *PostTransaction) GetTimestamp() *time.Time { - if o == nil { - return nil - } - return o.Timestamp -} - -func (o *PostTransaction) GetPostings() []Posting { - if o == nil { - return nil - } - return o.Postings -} - -func (o *PostTransaction) GetScript() *PostTransactionScript { - if o == nil { - return nil - } - return o.Script -} - -func (o *PostTransaction) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *PostTransaction) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/script.go b/components/ledger/pkg/client/models/components/script.go deleted file mode 100644 index 7193035d40..0000000000 --- a/components/ledger/pkg/client/models/components/script.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Script struct { - Plain string `json:"plain"` - Vars map[string]any `json:"vars,omitempty"` - // Reference to attach to the generated transaction - Reference *string `json:"reference,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` -} - -func (o *Script) GetPlain() string { - if o == nil { - return "" - } - return o.Plain -} - -func (o *Script) GetVars() map[string]any { - if o == nil { - return nil - } - return o.Vars -} - -func (o *Script) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *Script) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/scriptresponse.go b/components/ledger/pkg/client/models/components/scriptresponse.go deleted file mode 100644 index 21dd044568..0000000000 --- a/components/ledger/pkg/client/models/components/scriptresponse.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type ScriptResponse struct { - ErrorCode *ErrorsEnum `json:"errorCode,omitempty"` - ErrorMessage *string `json:"errorMessage,omitempty"` - Details *string `json:"details,omitempty"` - Transaction *Transaction `json:"transaction,omitempty"` -} - -func (o *ScriptResponse) GetErrorCode() *ErrorsEnum { - if o == nil { - return nil - } - return o.ErrorCode -} - -func (o *ScriptResponse) GetErrorMessage() *string { - if o == nil { - return nil - } - return o.ErrorMessage -} - -func (o *ScriptResponse) GetDetails() *string { - if o == nil { - return nil - } - return o.Details -} - -func (o *ScriptResponse) GetTransaction() *Transaction { - if o == nil { - return nil - } - return o.Transaction -} diff --git a/components/ledger/pkg/client/models/components/security.go b/components/ledger/pkg/client/models/components/security.go deleted file mode 100644 index f40aee125d..0000000000 --- a/components/ledger/pkg/client/models/components/security.go +++ /dev/null @@ -1,42 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" -) - -type Security struct { - ClientID string `security:"scheme,type=oauth2,subtype=client_credentials,name=clientID"` - ClientSecret string `security:"scheme,type=oauth2,subtype=client_credentials,name=clientSecret"` - tokenURL string `const:"/api/auth/oauth/token"` -} - -func (s Security) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(s, "", false) -} - -func (s *Security) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &s, "", false, false); err != nil { - return err - } - return nil -} - -func (o *Security) GetClientID() string { - if o == nil { - return "" - } - return o.ClientID -} - -func (o *Security) GetClientSecret() string { - if o == nil { - return "" - } - return o.ClientSecret -} - -func (o *Security) GetTokenURL() string { - return "/api/auth/oauth/token" -} diff --git a/components/ledger/pkg/client/models/components/stats.go b/components/ledger/pkg/client/models/components/stats.go deleted file mode 100644 index f29eed0428..0000000000 --- a/components/ledger/pkg/client/models/components/stats.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Stats struct { - Accounts int64 `json:"accounts"` - Transactions int64 `json:"transactions"` -} - -func (o *Stats) GetAccounts() int64 { - if o == nil { - return 0 - } - return o.Accounts -} - -func (o *Stats) GetTransactions() int64 { - if o == nil { - return 0 - } - return o.Transactions -} diff --git a/components/ledger/pkg/client/models/components/statsresponse.go b/components/ledger/pkg/client/models/components/statsresponse.go deleted file mode 100644 index 617c7c067b..0000000000 --- a/components/ledger/pkg/client/models/components/statsresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type StatsResponse struct { - Data Stats `json:"data"` -} - -func (o *StatsResponse) GetData() Stats { - if o == nil { - return Stats{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/transaction.go b/components/ledger/pkg/client/models/components/transaction.go deleted file mode 100644 index db820be2c8..0000000000 --- a/components/ledger/pkg/client/models/components/transaction.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" - "time" -) - -type Transaction struct { - Timestamp time.Time `json:"timestamp"` - Postings []Posting `json:"postings"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Txid *big.Int `json:"txid"` - PreCommitVolumes map[string]map[string]Volume `json:"preCommitVolumes,omitempty"` - PostCommitVolumes map[string]map[string]Volume `json:"postCommitVolumes,omitempty"` -} - -func (t Transaction) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(t, "", false) -} - -func (t *Transaction) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &t, "", false, false); err != nil { - return err - } - return nil -} - -func (o *Transaction) GetTimestamp() time.Time { - if o == nil { - return time.Time{} - } - return o.Timestamp -} - -func (o *Transaction) GetPostings() []Posting { - if o == nil { - return []Posting{} - } - return o.Postings -} - -func (o *Transaction) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *Transaction) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *Transaction) GetTxid() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Txid -} - -func (o *Transaction) GetPreCommitVolumes() map[string]map[string]Volume { - if o == nil { - return nil - } - return o.PreCommitVolumes -} - -func (o *Transaction) GetPostCommitVolumes() map[string]map[string]Volume { - if o == nil { - return nil - } - return o.PostCommitVolumes -} diff --git a/components/ledger/pkg/client/models/components/transactiondata.go b/components/ledger/pkg/client/models/components/transactiondata.go deleted file mode 100644 index 58cfdeb1e0..0000000000 --- a/components/ledger/pkg/client/models/components/transactiondata.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type TransactionData struct { - Postings []Posting `json:"postings"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Timestamp *time.Time `json:"timestamp,omitempty"` -} - -func (t TransactionData) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(t, "", false) -} - -func (t *TransactionData) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &t, "", false, false); err != nil { - return err - } - return nil -} - -func (o *TransactionData) GetPostings() []Posting { - if o == nil { - return []Posting{} - } - return o.Postings -} - -func (o *TransactionData) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *TransactionData) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *TransactionData) GetTimestamp() *time.Time { - if o == nil { - return nil - } - return o.Timestamp -} diff --git a/components/ledger/pkg/client/models/components/transactionresponse.go b/components/ledger/pkg/client/models/components/transactionresponse.go deleted file mode 100644 index fe0d4ffaf7..0000000000 --- a/components/ledger/pkg/client/models/components/transactionresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type TransactionResponse struct { - Data Transaction `json:"data"` -} - -func (o *TransactionResponse) GetData() Transaction { - if o == nil { - return Transaction{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/transactions.go b/components/ledger/pkg/client/models/components/transactions.go deleted file mode 100644 index 9a72dcf8d2..0000000000 --- a/components/ledger/pkg/client/models/components/transactions.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Transactions struct { - Transactions []TransactionData `json:"transactions"` -} - -func (o *Transactions) GetTransactions() []TransactionData { - if o == nil { - return []TransactionData{} - } - return o.Transactions -} diff --git a/components/ledger/pkg/client/models/components/transactionscursorresponse.go b/components/ledger/pkg/client/models/components/transactionscursorresponse.go deleted file mode 100644 index 1845a85804..0000000000 --- a/components/ledger/pkg/client/models/components/transactionscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type TransactionsCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []Transaction `json:"data"` -} - -func (o *TransactionsCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *TransactionsCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *TransactionsCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *TransactionsCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *TransactionsCursorResponseCursor) GetData() []Transaction { - if o == nil { - return []Transaction{} - } - return o.Data -} - -type TransactionsCursorResponse struct { - Cursor TransactionsCursorResponseCursor `json:"cursor"` -} - -func (o *TransactionsCursorResponse) GetCursor() TransactionsCursorResponseCursor { - if o == nil { - return TransactionsCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/transactionsresponse.go b/components/ledger/pkg/client/models/components/transactionsresponse.go deleted file mode 100644 index 534b644211..0000000000 --- a/components/ledger/pkg/client/models/components/transactionsresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type TransactionsResponse struct { - Data []Transaction `json:"data"` -} - -func (o *TransactionsResponse) GetData() []Transaction { - if o == nil { - return []Transaction{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2account.go b/components/ledger/pkg/client/models/components/v2account.go deleted file mode 100644 index 8d8c58f652..0000000000 --- a/components/ledger/pkg/client/models/components/v2account.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2Account struct { - Address string `json:"address"` - Metadata map[string]string `json:"metadata"` - Volumes map[string]V2Volume `json:"volumes,omitempty"` - EffectiveVolumes map[string]V2Volume `json:"effectiveVolumes,omitempty"` -} - -func (o *V2Account) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *V2Account) GetMetadata() map[string]string { - if o == nil { - return map[string]string{} - } - return o.Metadata -} - -func (o *V2Account) GetVolumes() map[string]V2Volume { - if o == nil { - return nil - } - return o.Volumes -} - -func (o *V2Account) GetEffectiveVolumes() map[string]V2Volume { - if o == nil { - return nil - } - return o.EffectiveVolumes -} diff --git a/components/ledger/pkg/client/models/components/v2accountresponse.go b/components/ledger/pkg/client/models/components/v2accountresponse.go deleted file mode 100644 index c4017dd0d7..0000000000 --- a/components/ledger/pkg/client/models/components/v2accountresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2AccountResponse struct { - Data V2Account `json:"data"` -} - -func (o *V2AccountResponse) GetData() V2Account { - if o == nil { - return V2Account{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2accountscursorresponse.go b/components/ledger/pkg/client/models/components/v2accountscursorresponse.go deleted file mode 100644 index d8453cb367..0000000000 --- a/components/ledger/pkg/client/models/components/v2accountscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2AccountsCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []V2Account `json:"data"` -} - -func (o *V2AccountsCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *V2AccountsCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *V2AccountsCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *V2AccountsCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *V2AccountsCursorResponseCursor) GetData() []V2Account { - if o == nil { - return []V2Account{} - } - return o.Data -} - -type V2AccountsCursorResponse struct { - Cursor V2AccountsCursorResponseCursor `json:"cursor"` -} - -func (o *V2AccountsCursorResponse) GetCursor() V2AccountsCursorResponseCursor { - if o == nil { - return V2AccountsCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/v2aggregatebalancesresponse.go b/components/ledger/pkg/client/models/components/v2aggregatebalancesresponse.go deleted file mode 100644 index 5ac12c30c5..0000000000 --- a/components/ledger/pkg/client/models/components/v2aggregatebalancesresponse.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2AggregateBalancesResponse struct { - Data map[string]*big.Int `json:"data"` -} - -func (v V2AggregateBalancesResponse) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2AggregateBalancesResponse) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2AggregateBalancesResponse) GetData() map[string]*big.Int { - if o == nil { - return map[string]*big.Int{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelement.go b/components/ledger/pkg/client/models/components/v2bulkelement.go deleted file mode 100644 index 5e27394910..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelement.go +++ /dev/null @@ -1,149 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" -) - -type V2BulkElementType string - -const ( - V2BulkElementTypeCreateTransaction V2BulkElementType = "CREATE_TRANSACTION" - V2BulkElementTypeAddMetadata V2BulkElementType = "ADD_METADATA" - V2BulkElementTypeRevertTransaction V2BulkElementType = "REVERT_TRANSACTION" - V2BulkElementTypeDeleteMetadata V2BulkElementType = "DELETE_METADATA" -) - -type V2BulkElement struct { - V2BulkElementCreateTransaction *V2BulkElementCreateTransaction - V2BulkElementAddMetadata *V2BulkElementAddMetadata - V2BulkElementRevertTransaction *V2BulkElementRevertTransaction - V2BulkElementDeleteMetadata *V2BulkElementDeleteMetadata - - Type V2BulkElementType -} - -func CreateV2BulkElementCreateTransaction(createTransaction V2BulkElementCreateTransaction) V2BulkElement { - typ := V2BulkElementTypeCreateTransaction - - typStr := string(typ) - createTransaction.Action = typStr - - return V2BulkElement{ - V2BulkElementCreateTransaction: &createTransaction, - Type: typ, - } -} - -func CreateV2BulkElementAddMetadata(addMetadata V2BulkElementAddMetadata) V2BulkElement { - typ := V2BulkElementTypeAddMetadata - - typStr := string(typ) - addMetadata.Action = typStr - - return V2BulkElement{ - V2BulkElementAddMetadata: &addMetadata, - Type: typ, - } -} - -func CreateV2BulkElementRevertTransaction(revertTransaction V2BulkElementRevertTransaction) V2BulkElement { - typ := V2BulkElementTypeRevertTransaction - - typStr := string(typ) - revertTransaction.Action = typStr - - return V2BulkElement{ - V2BulkElementRevertTransaction: &revertTransaction, - Type: typ, - } -} - -func CreateV2BulkElementDeleteMetadata(deleteMetadata V2BulkElementDeleteMetadata) V2BulkElement { - typ := V2BulkElementTypeDeleteMetadata - - typStr := string(typ) - deleteMetadata.Action = typStr - - return V2BulkElement{ - V2BulkElementDeleteMetadata: &deleteMetadata, - Type: typ, - } -} - -func (u *V2BulkElement) UnmarshalJSON(data []byte) error { - - type discriminator struct { - Action string `json:"action"` - } - - dis := new(discriminator) - if err := json.Unmarshal(data, &dis); err != nil { - return fmt.Errorf("could not unmarshal discriminator: %w", err) - } - - switch dis.Action { - case "CREATE_TRANSACTION": - v2BulkElementCreateTransaction := new(V2BulkElementCreateTransaction) - if err := utils.UnmarshalJSON(data, &v2BulkElementCreateTransaction, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Action == CREATE_TRANSACTION) type V2BulkElementCreateTransaction within V2BulkElement: %w", string(data), err) - } - - u.V2BulkElementCreateTransaction = v2BulkElementCreateTransaction - u.Type = V2BulkElementTypeCreateTransaction - return nil - case "ADD_METADATA": - v2BulkElementAddMetadata := new(V2BulkElementAddMetadata) - if err := utils.UnmarshalJSON(data, &v2BulkElementAddMetadata, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Action == ADD_METADATA) type V2BulkElementAddMetadata within V2BulkElement: %w", string(data), err) - } - - u.V2BulkElementAddMetadata = v2BulkElementAddMetadata - u.Type = V2BulkElementTypeAddMetadata - return nil - case "REVERT_TRANSACTION": - v2BulkElementRevertTransaction := new(V2BulkElementRevertTransaction) - if err := utils.UnmarshalJSON(data, &v2BulkElementRevertTransaction, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Action == REVERT_TRANSACTION) type V2BulkElementRevertTransaction within V2BulkElement: %w", string(data), err) - } - - u.V2BulkElementRevertTransaction = v2BulkElementRevertTransaction - u.Type = V2BulkElementTypeRevertTransaction - return nil - case "DELETE_METADATA": - v2BulkElementDeleteMetadata := new(V2BulkElementDeleteMetadata) - if err := utils.UnmarshalJSON(data, &v2BulkElementDeleteMetadata, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (Action == DELETE_METADATA) type V2BulkElementDeleteMetadata within V2BulkElement: %w", string(data), err) - } - - u.V2BulkElementDeleteMetadata = v2BulkElementDeleteMetadata - u.Type = V2BulkElementTypeDeleteMetadata - return nil - } - - return fmt.Errorf("could not unmarshal `%s` into any supported union types for V2BulkElement", string(data)) -} - -func (u V2BulkElement) MarshalJSON() ([]byte, error) { - if u.V2BulkElementCreateTransaction != nil { - return utils.MarshalJSON(u.V2BulkElementCreateTransaction, "", true) - } - - if u.V2BulkElementAddMetadata != nil { - return utils.MarshalJSON(u.V2BulkElementAddMetadata, "", true) - } - - if u.V2BulkElementRevertTransaction != nil { - return utils.MarshalJSON(u.V2BulkElementRevertTransaction, "", true) - } - - if u.V2BulkElementDeleteMetadata != nil { - return utils.MarshalJSON(u.V2BulkElementDeleteMetadata, "", true) - } - - return nil, errors.New("could not marshal union type V2BulkElement: all fields are null") -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelementaddmetadata.go b/components/ledger/pkg/client/models/components/v2bulkelementaddmetadata.go deleted file mode 100644 index c43dd7a905..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelementaddmetadata.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type Data struct { - TargetID V2TargetID `json:"targetId"` - TargetType V2TargetType `json:"targetType"` - Metadata map[string]string `json:"metadata"` -} - -func (o *Data) GetTargetID() V2TargetID { - if o == nil { - return V2TargetID{} - } - return o.TargetID -} - -func (o *Data) GetTargetType() V2TargetType { - if o == nil { - return V2TargetType("") - } - return o.TargetType -} - -func (o *Data) GetMetadata() map[string]string { - if o == nil { - return map[string]string{} - } - return o.Metadata -} - -type V2BulkElementAddMetadata struct { - Action string `json:"action"` - Ik *string `json:"ik,omitempty"` - Data *Data `json:"data,omitempty"` -} - -func (o *V2BulkElementAddMetadata) GetAction() string { - if o == nil { - return "" - } - return o.Action -} - -func (o *V2BulkElementAddMetadata) GetIk() *string { - if o == nil { - return nil - } - return o.Ik -} - -func (o *V2BulkElementAddMetadata) GetData() *Data { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelementcreatetransaction.go b/components/ledger/pkg/client/models/components/v2bulkelementcreatetransaction.go deleted file mode 100644 index fac83e6044..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelementcreatetransaction.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2BulkElementCreateTransaction struct { - Action string `json:"action"` - Ik *string `json:"ik,omitempty"` - Data *V2PostTransaction `json:"data,omitempty"` -} - -func (o *V2BulkElementCreateTransaction) GetAction() string { - if o == nil { - return "" - } - return o.Action -} - -func (o *V2BulkElementCreateTransaction) GetIk() *string { - if o == nil { - return nil - } - return o.Ik -} - -func (o *V2BulkElementCreateTransaction) GetData() *V2PostTransaction { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelementdeletemetadata.go b/components/ledger/pkg/client/models/components/v2bulkelementdeletemetadata.go deleted file mode 100644 index d5d8705899..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelementdeletemetadata.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2BulkElementDeleteMetadataData struct { - TargetID V2TargetID `json:"targetId"` - TargetType V2TargetType `json:"targetType"` - Key string `json:"key"` -} - -func (o *V2BulkElementDeleteMetadataData) GetTargetID() V2TargetID { - if o == nil { - return V2TargetID{} - } - return o.TargetID -} - -func (o *V2BulkElementDeleteMetadataData) GetTargetType() V2TargetType { - if o == nil { - return V2TargetType("") - } - return o.TargetType -} - -func (o *V2BulkElementDeleteMetadataData) GetKey() string { - if o == nil { - return "" - } - return o.Key -} - -type V2BulkElementDeleteMetadata struct { - Action string `json:"action"` - Ik *string `json:"ik,omitempty"` - Data *V2BulkElementDeleteMetadataData `json:"data,omitempty"` -} - -func (o *V2BulkElementDeleteMetadata) GetAction() string { - if o == nil { - return "" - } - return o.Action -} - -func (o *V2BulkElementDeleteMetadata) GetIk() *string { - if o == nil { - return nil - } - return o.Ik -} - -func (o *V2BulkElementDeleteMetadata) GetData() *V2BulkElementDeleteMetadataData { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelementresult.go b/components/ledger/pkg/client/models/components/v2bulkelementresult.go deleted file mode 100644 index 16a7af557a..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelementresult.go +++ /dev/null @@ -1,271 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" -) - -type V2BulkElementResultError struct { - ResponseType string `json:"responseType"` - ErrorCode string `json:"errorCode"` - ErrorDescription string `json:"errorDescription"` - ErrorDetails *string `json:"errorDetails,omitempty"` -} - -func (o *V2BulkElementResultError) GetResponseType() string { - if o == nil { - return "" - } - return o.ResponseType -} - -func (o *V2BulkElementResultError) GetErrorCode() string { - if o == nil { - return "" - } - return o.ErrorCode -} - -func (o *V2BulkElementResultError) GetErrorDescription() string { - if o == nil { - return "" - } - return o.ErrorDescription -} - -func (o *V2BulkElementResultError) GetErrorDetails() *string { - if o == nil { - return nil - } - return o.ErrorDetails -} - -type V2BulkElementResultDeleteMetadata struct { - ResponseType string `json:"responseType"` -} - -func (o *V2BulkElementResultDeleteMetadata) GetResponseType() string { - if o == nil { - return "" - } - return o.ResponseType -} - -type V2BulkElementResultRevertTransaction struct { - ResponseType string `json:"responseType"` - Data V2Transaction `json:"data"` -} - -func (o *V2BulkElementResultRevertTransaction) GetResponseType() string { - if o == nil { - return "" - } - return o.ResponseType -} - -func (o *V2BulkElementResultRevertTransaction) GetData() V2Transaction { - if o == nil { - return V2Transaction{} - } - return o.Data -} - -type V2BulkElementResultAddMetadata struct { - ResponseType string `json:"responseType"` -} - -func (o *V2BulkElementResultAddMetadata) GetResponseType() string { - if o == nil { - return "" - } - return o.ResponseType -} - -type V2BulkElementResultCreateTransaction struct { - ResponseType string `json:"responseType"` - Data V2Transaction `json:"data"` -} - -func (o *V2BulkElementResultCreateTransaction) GetResponseType() string { - if o == nil { - return "" - } - return o.ResponseType -} - -func (o *V2BulkElementResultCreateTransaction) GetData() V2Transaction { - if o == nil { - return V2Transaction{} - } - return o.Data -} - -type V2BulkElementResultType string - -const ( - V2BulkElementResultTypeCreateTransaction V2BulkElementResultType = "CREATE_TRANSACTION" - V2BulkElementResultTypeAddMetadata V2BulkElementResultType = "ADD_METADATA" - V2BulkElementResultTypeRevertTransaction V2BulkElementResultType = "REVERT_TRANSACTION" - V2BulkElementResultTypeDeleteMetadata V2BulkElementResultType = "DELETE_METADATA" - V2BulkElementResultTypeError V2BulkElementResultType = "ERROR" -) - -type V2BulkElementResult struct { - V2BulkElementResultCreateTransaction *V2BulkElementResultCreateTransaction - V2BulkElementResultAddMetadata *V2BulkElementResultAddMetadata - V2BulkElementResultRevertTransaction *V2BulkElementResultRevertTransaction - V2BulkElementResultDeleteMetadata *V2BulkElementResultDeleteMetadata - V2BulkElementResultError *V2BulkElementResultError - - Type V2BulkElementResultType -} - -func CreateV2BulkElementResultCreateTransaction(createTransaction V2BulkElementResultCreateTransaction) V2BulkElementResult { - typ := V2BulkElementResultTypeCreateTransaction - - typStr := string(typ) - createTransaction.ResponseType = typStr - - return V2BulkElementResult{ - V2BulkElementResultCreateTransaction: &createTransaction, - Type: typ, - } -} - -func CreateV2BulkElementResultAddMetadata(addMetadata V2BulkElementResultAddMetadata) V2BulkElementResult { - typ := V2BulkElementResultTypeAddMetadata - - typStr := string(typ) - addMetadata.ResponseType = typStr - - return V2BulkElementResult{ - V2BulkElementResultAddMetadata: &addMetadata, - Type: typ, - } -} - -func CreateV2BulkElementResultRevertTransaction(revertTransaction V2BulkElementResultRevertTransaction) V2BulkElementResult { - typ := V2BulkElementResultTypeRevertTransaction - - typStr := string(typ) - revertTransaction.ResponseType = typStr - - return V2BulkElementResult{ - V2BulkElementResultRevertTransaction: &revertTransaction, - Type: typ, - } -} - -func CreateV2BulkElementResultDeleteMetadata(deleteMetadata V2BulkElementResultDeleteMetadata) V2BulkElementResult { - typ := V2BulkElementResultTypeDeleteMetadata - - typStr := string(typ) - deleteMetadata.ResponseType = typStr - - return V2BulkElementResult{ - V2BulkElementResultDeleteMetadata: &deleteMetadata, - Type: typ, - } -} - -func CreateV2BulkElementResultError(error V2BulkElementResultError) V2BulkElementResult { - typ := V2BulkElementResultTypeError - - typStr := string(typ) - error.ResponseType = typStr - - return V2BulkElementResult{ - V2BulkElementResultError: &error, - Type: typ, - } -} - -func (u *V2BulkElementResult) UnmarshalJSON(data []byte) error { - - type discriminator struct { - ResponseType string `json:"responseType"` - } - - dis := new(discriminator) - if err := json.Unmarshal(data, &dis); err != nil { - return fmt.Errorf("could not unmarshal discriminator: %w", err) - } - - switch dis.ResponseType { - case "CREATE_TRANSACTION": - v2BulkElementResultCreateTransaction := new(V2BulkElementResultCreateTransaction) - if err := utils.UnmarshalJSON(data, &v2BulkElementResultCreateTransaction, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (ResponseType == CREATE_TRANSACTION) type V2BulkElementResultCreateTransaction within V2BulkElementResult: %w", string(data), err) - } - - u.V2BulkElementResultCreateTransaction = v2BulkElementResultCreateTransaction - u.Type = V2BulkElementResultTypeCreateTransaction - return nil - case "ADD_METADATA": - v2BulkElementResultAddMetadata := new(V2BulkElementResultAddMetadata) - if err := utils.UnmarshalJSON(data, &v2BulkElementResultAddMetadata, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (ResponseType == ADD_METADATA) type V2BulkElementResultAddMetadata within V2BulkElementResult: %w", string(data), err) - } - - u.V2BulkElementResultAddMetadata = v2BulkElementResultAddMetadata - u.Type = V2BulkElementResultTypeAddMetadata - return nil - case "REVERT_TRANSACTION": - v2BulkElementResultRevertTransaction := new(V2BulkElementResultRevertTransaction) - if err := utils.UnmarshalJSON(data, &v2BulkElementResultRevertTransaction, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (ResponseType == REVERT_TRANSACTION) type V2BulkElementResultRevertTransaction within V2BulkElementResult: %w", string(data), err) - } - - u.V2BulkElementResultRevertTransaction = v2BulkElementResultRevertTransaction - u.Type = V2BulkElementResultTypeRevertTransaction - return nil - case "DELETE_METADATA": - v2BulkElementResultDeleteMetadata := new(V2BulkElementResultDeleteMetadata) - if err := utils.UnmarshalJSON(data, &v2BulkElementResultDeleteMetadata, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (ResponseType == DELETE_METADATA) type V2BulkElementResultDeleteMetadata within V2BulkElementResult: %w", string(data), err) - } - - u.V2BulkElementResultDeleteMetadata = v2BulkElementResultDeleteMetadata - u.Type = V2BulkElementResultTypeDeleteMetadata - return nil - case "ERROR": - v2BulkElementResultError := new(V2BulkElementResultError) - if err := utils.UnmarshalJSON(data, &v2BulkElementResultError, "", true, false); err != nil { - return fmt.Errorf("could not unmarshal `%s` into expected (ResponseType == ERROR) type V2BulkElementResultError within V2BulkElementResult: %w", string(data), err) - } - - u.V2BulkElementResultError = v2BulkElementResultError - u.Type = V2BulkElementResultTypeError - return nil - } - - return fmt.Errorf("could not unmarshal `%s` into any supported union types for V2BulkElementResult", string(data)) -} - -func (u V2BulkElementResult) MarshalJSON() ([]byte, error) { - if u.V2BulkElementResultCreateTransaction != nil { - return utils.MarshalJSON(u.V2BulkElementResultCreateTransaction, "", true) - } - - if u.V2BulkElementResultAddMetadata != nil { - return utils.MarshalJSON(u.V2BulkElementResultAddMetadata, "", true) - } - - if u.V2BulkElementResultRevertTransaction != nil { - return utils.MarshalJSON(u.V2BulkElementResultRevertTransaction, "", true) - } - - if u.V2BulkElementResultDeleteMetadata != nil { - return utils.MarshalJSON(u.V2BulkElementResultDeleteMetadata, "", true) - } - - if u.V2BulkElementResultError != nil { - return utils.MarshalJSON(u.V2BulkElementResultError, "", true) - } - - return nil, errors.New("could not marshal union type V2BulkElementResult: all fields are null") -} diff --git a/components/ledger/pkg/client/models/components/v2bulkelementreverttransaction.go b/components/ledger/pkg/client/models/components/v2bulkelementreverttransaction.go deleted file mode 100644 index 3365f10798..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkelementreverttransaction.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2BulkElementRevertTransactionData struct { - ID *big.Int `json:"id"` - Force *bool `json:"force,omitempty"` - AtEffectiveDate *bool `json:"atEffectiveDate,omitempty"` -} - -func (v V2BulkElementRevertTransactionData) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2BulkElementRevertTransactionData) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2BulkElementRevertTransactionData) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2BulkElementRevertTransactionData) GetForce() *bool { - if o == nil { - return nil - } - return o.Force -} - -func (o *V2BulkElementRevertTransactionData) GetAtEffectiveDate() *bool { - if o == nil { - return nil - } - return o.AtEffectiveDate -} - -type V2BulkElementRevertTransaction struct { - Action string `json:"action"` - Ik *string `json:"ik,omitempty"` - Data *V2BulkElementRevertTransactionData `json:"data,omitempty"` -} - -func (o *V2BulkElementRevertTransaction) GetAction() string { - if o == nil { - return "" - } - return o.Action -} - -func (o *V2BulkElementRevertTransaction) GetIk() *string { - if o == nil { - return nil - } - return o.Ik -} - -func (o *V2BulkElementRevertTransaction) GetData() *V2BulkElementRevertTransactionData { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2bulkresponse.go b/components/ledger/pkg/client/models/components/v2bulkresponse.go deleted file mode 100644 index 06eace58fb..0000000000 --- a/components/ledger/pkg/client/models/components/v2bulkresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2BulkResponse struct { - Data []V2BulkElementResult `json:"data"` -} - -func (o *V2BulkResponse) GetData() []V2BulkElementResult { - if o == nil { - return []V2BulkElementResult{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2configinforesponse.go b/components/ledger/pkg/client/models/components/v2configinforesponse.go deleted file mode 100644 index f22a1df9a8..0000000000 --- a/components/ledger/pkg/client/models/components/v2configinforesponse.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2ConfigInfoResponse struct { - Server string `json:"server"` - Version string `json:"version"` -} - -func (o *V2ConfigInfoResponse) GetServer() string { - if o == nil { - return "" - } - return o.Server -} - -func (o *V2ConfigInfoResponse) GetVersion() string { - if o == nil { - return "" - } - return o.Version -} diff --git a/components/ledger/pkg/client/models/components/v2createledgerrequest.go b/components/ledger/pkg/client/models/components/v2createledgerrequest.go deleted file mode 100644 index 6d2dd9f420..0000000000 --- a/components/ledger/pkg/client/models/components/v2createledgerrequest.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2CreateLedgerRequest struct { - Bucket *string `json:"bucket,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -func (o *V2CreateLedgerRequest) GetBucket() *string { - if o == nil { - return nil - } - return o.Bucket -} - -func (o *V2CreateLedgerRequest) GetMetadata() map[string]string { - if o == nil { - return nil - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/v2createtransactionresponse.go b/components/ledger/pkg/client/models/components/v2createtransactionresponse.go deleted file mode 100644 index 6638ac8d4f..0000000000 --- a/components/ledger/pkg/client/models/components/v2createtransactionresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2CreateTransactionResponse struct { - Data V2Transaction `json:"data"` -} - -func (o *V2CreateTransactionResponse) GetData() V2Transaction { - if o == nil { - return V2Transaction{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2errorsenum.go b/components/ledger/pkg/client/models/components/v2errorsenum.go deleted file mode 100644 index defc9c108c..0000000000 --- a/components/ledger/pkg/client/models/components/v2errorsenum.go +++ /dev/null @@ -1,64 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" -) - -type V2ErrorsEnum string - -const ( - V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL" - V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND" - V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION" - V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT" - V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED" - V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE" - V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND" - V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING" - V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT" - V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS" - V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND" - V2ErrorsEnumImport V2ErrorsEnum = "IMPORT" -) - -func (e V2ErrorsEnum) ToPointer() *V2ErrorsEnum { - return &e -} -func (e *V2ErrorsEnum) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "INTERNAL": - fallthrough - case "INSUFFICIENT_FUND": - fallthrough - case "VALIDATION": - fallthrough - case "CONFLICT": - fallthrough - case "COMPILATION_FAILED": - fallthrough - case "METADATA_OVERRIDE": - fallthrough - case "NOT_FOUND": - fallthrough - case "REVERT_OCCURRING": - fallthrough - case "ALREADY_REVERT": - fallthrough - case "NO_POSTINGS": - fallthrough - case "LEDGER_NOT_FOUND": - fallthrough - case "IMPORT": - *e = V2ErrorsEnum(v) - return nil - default: - return fmt.Errorf("invalid value for V2ErrorsEnum: %v", v) - } -} diff --git a/components/ledger/pkg/client/models/components/v2expandedtransaction.go b/components/ledger/pkg/client/models/components/v2expandedtransaction.go deleted file mode 100644 index 976a7c6b28..0000000000 --- a/components/ledger/pkg/client/models/components/v2expandedtransaction.go +++ /dev/null @@ -1,87 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" - "time" -) - -type V2ExpandedTransaction struct { - Timestamp time.Time `json:"timestamp"` - Postings []V2Posting `json:"postings"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]string `json:"metadata"` - ID *big.Int `json:"id"` - Reverted bool `json:"reverted"` - PreCommitVolumes map[string]map[string]V2Volume `json:"preCommitVolumes,omitempty"` - PostCommitVolumes map[string]map[string]V2Volume `json:"postCommitVolumes,omitempty"` -} - -func (v V2ExpandedTransaction) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2ExpandedTransaction) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2ExpandedTransaction) GetTimestamp() time.Time { - if o == nil { - return time.Time{} - } - return o.Timestamp -} - -func (o *V2ExpandedTransaction) GetPostings() []V2Posting { - if o == nil { - return []V2Posting{} - } - return o.Postings -} - -func (o *V2ExpandedTransaction) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *V2ExpandedTransaction) GetMetadata() map[string]string { - if o == nil { - return map[string]string{} - } - return o.Metadata -} - -func (o *V2ExpandedTransaction) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2ExpandedTransaction) GetReverted() bool { - if o == nil { - return false - } - return o.Reverted -} - -func (o *V2ExpandedTransaction) GetPreCommitVolumes() map[string]map[string]V2Volume { - if o == nil { - return nil - } - return o.PreCommitVolumes -} - -func (o *V2ExpandedTransaction) GetPostCommitVolumes() map[string]map[string]V2Volume { - if o == nil { - return nil - } - return o.PostCommitVolumes -} diff --git a/components/ledger/pkg/client/models/components/v2getledgerresponse.go b/components/ledger/pkg/client/models/components/v2getledgerresponse.go deleted file mode 100644 index f264cd7368..0000000000 --- a/components/ledger/pkg/client/models/components/v2getledgerresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2GetLedgerResponse struct { - Data V2Ledger `json:"data"` -} - -func (o *V2GetLedgerResponse) GetData() V2Ledger { - if o == nil { - return V2Ledger{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2gettransactionresponse.go b/components/ledger/pkg/client/models/components/v2gettransactionresponse.go deleted file mode 100644 index d8772bc9a2..0000000000 --- a/components/ledger/pkg/client/models/components/v2gettransactionresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2GetTransactionResponse struct { - Data V2ExpandedTransaction `json:"data"` -} - -func (o *V2GetTransactionResponse) GetData() V2ExpandedTransaction { - if o == nil { - return V2ExpandedTransaction{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2ledger.go b/components/ledger/pkg/client/models/components/v2ledger.go deleted file mode 100644 index 32fca87709..0000000000 --- a/components/ledger/pkg/client/models/components/v2ledger.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type V2Ledger struct { - Name string `json:"name"` - AddedAt time.Time `json:"addedAt"` - Bucket string `json:"bucket"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -func (v V2Ledger) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Ledger) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Ledger) GetName() string { - if o == nil { - return "" - } - return o.Name -} - -func (o *V2Ledger) GetAddedAt() time.Time { - if o == nil { - return time.Time{} - } - return o.AddedAt -} - -func (o *V2Ledger) GetBucket() string { - if o == nil { - return "" - } - return o.Bucket -} - -func (o *V2Ledger) GetMetadata() map[string]string { - if o == nil { - return nil - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/v2ledgerinfo.go b/components/ledger/pkg/client/models/components/v2ledgerinfo.go deleted file mode 100644 index e806a1a9be..0000000000 --- a/components/ledger/pkg/client/models/components/v2ledgerinfo.go +++ /dev/null @@ -1,33 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2LedgerInfoStorage struct { - Migrations []V2MigrationInfo `json:"migrations,omitempty"` -} - -func (o *V2LedgerInfoStorage) GetMigrations() []V2MigrationInfo { - if o == nil { - return nil - } - return o.Migrations -} - -type V2LedgerInfo struct { - Name *string `json:"name,omitempty"` - Storage *V2LedgerInfoStorage `json:"storage,omitempty"` -} - -func (o *V2LedgerInfo) GetName() *string { - if o == nil { - return nil - } - return o.Name -} - -func (o *V2LedgerInfo) GetStorage() *V2LedgerInfoStorage { - if o == nil { - return nil - } - return o.Storage -} diff --git a/components/ledger/pkg/client/models/components/v2ledgerinforesponse.go b/components/ledger/pkg/client/models/components/v2ledgerinforesponse.go deleted file mode 100644 index 2d11516699..0000000000 --- a/components/ledger/pkg/client/models/components/v2ledgerinforesponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2LedgerInfoResponse struct { - Data *V2LedgerInfo `json:"data,omitempty"` -} - -func (o *V2LedgerInfoResponse) GetData() *V2LedgerInfo { - if o == nil { - return nil - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2ledgerlistresponse.go b/components/ledger/pkg/client/models/components/v2ledgerlistresponse.go deleted file mode 100644 index 053795379a..0000000000 --- a/components/ledger/pkg/client/models/components/v2ledgerlistresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2LedgerListResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []V2Ledger `json:"data"` -} - -func (o *V2LedgerListResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *V2LedgerListResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *V2LedgerListResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *V2LedgerListResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *V2LedgerListResponseCursor) GetData() []V2Ledger { - if o == nil { - return []V2Ledger{} - } - return o.Data -} - -type V2LedgerListResponse struct { - Cursor V2LedgerListResponseCursor `json:"cursor"` -} - -func (o *V2LedgerListResponse) GetCursor() V2LedgerListResponseCursor { - if o == nil { - return V2LedgerListResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/v2log.go b/components/ledger/pkg/client/models/components/v2log.go deleted file mode 100644 index faf4b8b6da..0000000000 --- a/components/ledger/pkg/client/models/components/v2log.go +++ /dev/null @@ -1,94 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" - "time" -) - -type V2LogType string - -const ( - V2LogTypeNewTransaction V2LogType = "NEW_TRANSACTION" - V2LogTypeSetMetadata V2LogType = "SET_METADATA" - V2LogTypeRevertedTransaction V2LogType = "REVERTED_TRANSACTION" -) - -func (e V2LogType) ToPointer() *V2LogType { - return &e -} -func (e *V2LogType) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "NEW_TRANSACTION": - fallthrough - case "SET_METADATA": - fallthrough - case "REVERTED_TRANSACTION": - *e = V2LogType(v) - return nil - default: - return fmt.Errorf("invalid value for V2LogType: %v", v) - } -} - -type V2Log struct { - ID *big.Int `json:"id"` - Type V2LogType `json:"type"` - Data map[string]any `json:"data"` - Hash string `json:"hash"` - Date time.Time `json:"date"` -} - -func (v V2Log) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Log) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Log) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2Log) GetType() V2LogType { - if o == nil { - return V2LogType("") - } - return o.Type -} - -func (o *V2Log) GetData() map[string]any { - if o == nil { - return map[string]any{} - } - return o.Data -} - -func (o *V2Log) GetHash() string { - if o == nil { - return "" - } - return o.Hash -} - -func (o *V2Log) GetDate() time.Time { - if o == nil { - return time.Time{} - } - return o.Date -} diff --git a/components/ledger/pkg/client/models/components/v2logscursorresponse.go b/components/ledger/pkg/client/models/components/v2logscursorresponse.go deleted file mode 100644 index 610a216193..0000000000 --- a/components/ledger/pkg/client/models/components/v2logscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2LogsCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []V2Log `json:"data"` -} - -func (o *V2LogsCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *V2LogsCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *V2LogsCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *V2LogsCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *V2LogsCursorResponseCursor) GetData() []V2Log { - if o == nil { - return []V2Log{} - } - return o.Data -} - -type V2LogsCursorResponse struct { - Cursor V2LogsCursorResponseCursor `json:"cursor"` -} - -func (o *V2LogsCursorResponse) GetCursor() V2LogsCursorResponseCursor { - if o == nil { - return V2LogsCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/v2migrationinfo.go b/components/ledger/pkg/client/models/components/v2migrationinfo.go deleted file mode 100644 index 07a0a68699..0000000000 --- a/components/ledger/pkg/client/models/components/v2migrationinfo.go +++ /dev/null @@ -1,82 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type V2MigrationInfoState string - -const ( - V2MigrationInfoStateToDo V2MigrationInfoState = "TO DO" - V2MigrationInfoStateDone V2MigrationInfoState = "DONE" -) - -func (e V2MigrationInfoState) ToPointer() *V2MigrationInfoState { - return &e -} -func (e *V2MigrationInfoState) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "TO DO": - fallthrough - case "DONE": - *e = V2MigrationInfoState(v) - return nil - default: - return fmt.Errorf("invalid value for V2MigrationInfoState: %v", v) - } -} - -type V2MigrationInfo struct { - Version *int64 `json:"version,omitempty"` - Name *string `json:"name,omitempty"` - Date *time.Time `json:"date,omitempty"` - State *V2MigrationInfoState `json:"state,omitempty"` -} - -func (v V2MigrationInfo) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2MigrationInfo) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2MigrationInfo) GetVersion() *int64 { - if o == nil { - return nil - } - return o.Version -} - -func (o *V2MigrationInfo) GetName() *string { - if o == nil { - return nil - } - return o.Name -} - -func (o *V2MigrationInfo) GetDate() *time.Time { - if o == nil { - return nil - } - return o.Date -} - -func (o *V2MigrationInfo) GetState() *V2MigrationInfoState { - if o == nil { - return nil - } - return o.State -} diff --git a/components/ledger/pkg/client/models/components/v2posting.go b/components/ledger/pkg/client/models/components/v2posting.go deleted file mode 100644 index f228d864ea..0000000000 --- a/components/ledger/pkg/client/models/components/v2posting.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2Posting struct { - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` - Destination string `json:"destination"` - Source string `json:"source"` -} - -func (v V2Posting) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Posting) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Posting) GetAmount() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Amount -} - -func (o *V2Posting) GetAsset() string { - if o == nil { - return "" - } - return o.Asset -} - -func (o *V2Posting) GetDestination() string { - if o == nil { - return "" - } - return o.Destination -} - -func (o *V2Posting) GetSource() string { - if o == nil { - return "" - } - return o.Source -} diff --git a/components/ledger/pkg/client/models/components/v2posttransaction.go b/components/ledger/pkg/client/models/components/v2posttransaction.go deleted file mode 100644 index 45ad155333..0000000000 --- a/components/ledger/pkg/client/models/components/v2posttransaction.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "time" -) - -type V2PostTransactionScript struct { - Plain string `json:"plain"` - Vars map[string]any `json:"vars,omitempty"` -} - -func (o *V2PostTransactionScript) GetPlain() string { - if o == nil { - return "" - } - return o.Plain -} - -func (o *V2PostTransactionScript) GetVars() map[string]any { - if o == nil { - return nil - } - return o.Vars -} - -type V2PostTransaction struct { - Timestamp *time.Time `json:"timestamp,omitempty"` - Postings []V2Posting `json:"postings,omitempty"` - Script *V2PostTransactionScript `json:"script,omitempty"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]string `json:"metadata"` -} - -func (v V2PostTransaction) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2PostTransaction) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2PostTransaction) GetTimestamp() *time.Time { - if o == nil { - return nil - } - return o.Timestamp -} - -func (o *V2PostTransaction) GetPostings() []V2Posting { - if o == nil { - return nil - } - return o.Postings -} - -func (o *V2PostTransaction) GetScript() *V2PostTransactionScript { - if o == nil { - return nil - } - return o.Script -} - -func (o *V2PostTransaction) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *V2PostTransaction) GetMetadata() map[string]string { - if o == nil { - return map[string]string{} - } - return o.Metadata -} diff --git a/components/ledger/pkg/client/models/components/v2reverttransactionresponse.go b/components/ledger/pkg/client/models/components/v2reverttransactionresponse.go deleted file mode 100644 index f1b8f9683b..0000000000 --- a/components/ledger/pkg/client/models/components/v2reverttransactionresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2RevertTransactionResponse struct { - Data V2Transaction `json:"data"` -} - -func (o *V2RevertTransactionResponse) GetData() V2Transaction { - if o == nil { - return V2Transaction{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2stats.go b/components/ledger/pkg/client/models/components/v2stats.go deleted file mode 100644 index 738a060b8e..0000000000 --- a/components/ledger/pkg/client/models/components/v2stats.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2Stats struct { - Accounts int64 `json:"accounts"` - Transactions *big.Int `json:"transactions"` -} - -func (v V2Stats) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Stats) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Stats) GetAccounts() int64 { - if o == nil { - return 0 - } - return o.Accounts -} - -func (o *V2Stats) GetTransactions() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Transactions -} diff --git a/components/ledger/pkg/client/models/components/v2statsresponse.go b/components/ledger/pkg/client/models/components/v2statsresponse.go deleted file mode 100644 index 951c758faa..0000000000 --- a/components/ledger/pkg/client/models/components/v2statsresponse.go +++ /dev/null @@ -1,14 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2StatsResponse struct { - Data V2Stats `json:"data"` -} - -func (o *V2StatsResponse) GetData() V2Stats { - if o == nil { - return V2Stats{} - } - return o.Data -} diff --git a/components/ledger/pkg/client/models/components/v2targetid.go b/components/ledger/pkg/client/models/components/v2targetid.go deleted file mode 100644 index 61f725f77b..0000000000 --- a/components/ledger/pkg/client/models/components/v2targetid.go +++ /dev/null @@ -1,73 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "errors" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2TargetIDType string - -const ( - V2TargetIDTypeStr V2TargetIDType = "str" - V2TargetIDTypeBigint V2TargetIDType = "bigint" -) - -type V2TargetID struct { - Str *string - Bigint *big.Int - - Type V2TargetIDType -} - -func CreateV2TargetIDStr(str string) V2TargetID { - typ := V2TargetIDTypeStr - - return V2TargetID{ - Str: &str, - Type: typ, - } -} - -func CreateV2TargetIDBigint(bigint *big.Int) V2TargetID { - typ := V2TargetIDTypeBigint - - return V2TargetID{ - Bigint: bigint, - Type: typ, - } -} - -func (u *V2TargetID) UnmarshalJSON(data []byte) error { - - var str string = "" - if err := utils.UnmarshalJSON(data, &str, "", true, true); err == nil { - u.Str = &str - u.Type = V2TargetIDTypeStr - return nil - } - - var bigint *big.Int = big.NewInt(0) - if err := utils.UnmarshalJSON(data, &bigint, "", true, true); err == nil { - u.Bigint = bigint - u.Type = V2TargetIDTypeBigint - return nil - } - - return fmt.Errorf("could not unmarshal `%s` into any supported union types for V2TargetID", string(data)) -} - -func (u V2TargetID) MarshalJSON() ([]byte, error) { - if u.Str != nil { - return utils.MarshalJSON(u.Str, "", true) - } - - if u.Bigint != nil { - return utils.MarshalJSON(u.Bigint, "", true) - } - - return nil, errors.New("could not marshal union type V2TargetID: all fields are null") -} diff --git a/components/ledger/pkg/client/models/components/v2targettype.go b/components/ledger/pkg/client/models/components/v2targettype.go deleted file mode 100644 index a1a365c60e..0000000000 --- a/components/ledger/pkg/client/models/components/v2targettype.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "encoding/json" - "fmt" -) - -type V2TargetType string - -const ( - V2TargetTypeTransaction V2TargetType = "TRANSACTION" - V2TargetTypeAccount V2TargetType = "ACCOUNT" -) - -func (e V2TargetType) ToPointer() *V2TargetType { - return &e -} -func (e *V2TargetType) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "TRANSACTION": - fallthrough - case "ACCOUNT": - *e = V2TargetType(v) - return nil - default: - return fmt.Errorf("invalid value for V2TargetType: %v", v) - } -} diff --git a/components/ledger/pkg/client/models/components/v2transaction.go b/components/ledger/pkg/client/models/components/v2transaction.go deleted file mode 100644 index 747a9738a5..0000000000 --- a/components/ledger/pkg/client/models/components/v2transaction.go +++ /dev/null @@ -1,71 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" - "time" -) - -type V2Transaction struct { - Timestamp time.Time `json:"timestamp"` - Postings []V2Posting `json:"postings"` - Reference *string `json:"reference,omitempty"` - Metadata map[string]string `json:"metadata"` - ID *big.Int `json:"id"` - Reverted bool `json:"reverted"` -} - -func (v V2Transaction) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Transaction) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Transaction) GetTimestamp() time.Time { - if o == nil { - return time.Time{} - } - return o.Timestamp -} - -func (o *V2Transaction) GetPostings() []V2Posting { - if o == nil { - return []V2Posting{} - } - return o.Postings -} - -func (o *V2Transaction) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *V2Transaction) GetMetadata() map[string]string { - if o == nil { - return map[string]string{} - } - return o.Metadata -} - -func (o *V2Transaction) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2Transaction) GetReverted() bool { - if o == nil { - return false - } - return o.Reverted -} diff --git a/components/ledger/pkg/client/models/components/v2transactionscursorresponse.go b/components/ledger/pkg/client/models/components/v2transactionscursorresponse.go deleted file mode 100644 index ed42ff90f5..0000000000 --- a/components/ledger/pkg/client/models/components/v2transactionscursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2TransactionsCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []V2ExpandedTransaction `json:"data"` -} - -func (o *V2TransactionsCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *V2TransactionsCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *V2TransactionsCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *V2TransactionsCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *V2TransactionsCursorResponseCursor) GetData() []V2ExpandedTransaction { - if o == nil { - return []V2ExpandedTransaction{} - } - return o.Data -} - -type V2TransactionsCursorResponse struct { - Cursor V2TransactionsCursorResponseCursor `json:"cursor"` -} - -func (o *V2TransactionsCursorResponse) GetCursor() V2TransactionsCursorResponseCursor { - if o == nil { - return V2TransactionsCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/v2volume.go b/components/ledger/pkg/client/models/components/v2volume.go deleted file mode 100644 index f945d94e0f..0000000000 --- a/components/ledger/pkg/client/models/components/v2volume.go +++ /dev/null @@ -1,46 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2Volume struct { - Input *big.Int `json:"input"` - Output *big.Int `json:"output"` - Balance *big.Int `json:"balance,omitempty"` -} - -func (v V2Volume) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2Volume) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2Volume) GetInput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Input -} - -func (o *V2Volume) GetOutput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Output -} - -func (o *V2Volume) GetBalance() *big.Int { - if o == nil { - return nil - } - return o.Balance -} diff --git a/components/ledger/pkg/client/models/components/v2volumeswithbalance.go b/components/ledger/pkg/client/models/components/v2volumeswithbalance.go deleted file mode 100644 index ffab6e30de..0000000000 --- a/components/ledger/pkg/client/models/components/v2volumeswithbalance.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type V2VolumesWithBalance struct { - Account string `json:"account"` - Asset string `json:"asset"` - Input *big.Int `json:"input"` - Output *big.Int `json:"output"` - Balance *big.Int `json:"balance"` -} - -func (v V2VolumesWithBalance) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2VolumesWithBalance) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2VolumesWithBalance) GetAccount() string { - if o == nil { - return "" - } - return o.Account -} - -func (o *V2VolumesWithBalance) GetAsset() string { - if o == nil { - return "" - } - return o.Asset -} - -func (o *V2VolumesWithBalance) GetInput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Input -} - -func (o *V2VolumesWithBalance) GetOutput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Output -} - -func (o *V2VolumesWithBalance) GetBalance() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Balance -} diff --git a/components/ledger/pkg/client/models/components/v2volumeswithbalancecursorresponse.go b/components/ledger/pkg/client/models/components/v2volumeswithbalancecursorresponse.go deleted file mode 100644 index b03a5cf092..0000000000 --- a/components/ledger/pkg/client/models/components/v2volumeswithbalancecursorresponse.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -type V2VolumesWithBalanceCursorResponseCursor struct { - PageSize int64 `json:"pageSize"` - HasMore bool `json:"hasMore"` - Previous *string `json:"previous,omitempty"` - Next *string `json:"next,omitempty"` - Data []V2VolumesWithBalance `json:"data"` -} - -func (o *V2VolumesWithBalanceCursorResponseCursor) GetPageSize() int64 { - if o == nil { - return 0 - } - return o.PageSize -} - -func (o *V2VolumesWithBalanceCursorResponseCursor) GetHasMore() bool { - if o == nil { - return false - } - return o.HasMore -} - -func (o *V2VolumesWithBalanceCursorResponseCursor) GetPrevious() *string { - if o == nil { - return nil - } - return o.Previous -} - -func (o *V2VolumesWithBalanceCursorResponseCursor) GetNext() *string { - if o == nil { - return nil - } - return o.Next -} - -func (o *V2VolumesWithBalanceCursorResponseCursor) GetData() []V2VolumesWithBalance { - if o == nil { - return []V2VolumesWithBalance{} - } - return o.Data -} - -type V2VolumesWithBalanceCursorResponse struct { - Cursor V2VolumesWithBalanceCursorResponseCursor `json:"cursor"` -} - -func (o *V2VolumesWithBalanceCursorResponse) GetCursor() V2VolumesWithBalanceCursorResponseCursor { - if o == nil { - return V2VolumesWithBalanceCursorResponseCursor{} - } - return o.Cursor -} diff --git a/components/ledger/pkg/client/models/components/volume.go b/components/ledger/pkg/client/models/components/volume.go deleted file mode 100644 index 3639b1a83c..0000000000 --- a/components/ledger/pkg/client/models/components/volume.go +++ /dev/null @@ -1,46 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package components - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "math/big" -) - -type Volume struct { - Input *big.Int `json:"input"` - Output *big.Int `json:"output"` - Balance *big.Int `json:"balance,omitempty"` -} - -func (v Volume) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *Volume) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *Volume) GetInput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Input -} - -func (o *Volume) GetOutput() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Output -} - -func (o *Volume) GetBalance() *big.Int { - if o == nil { - return nil - } - return o.Balance -} diff --git a/components/ledger/pkg/client/models/operations/addmetadataontransaction.go b/components/ledger/pkg/client/models/operations/addmetadataontransaction.go deleted file mode 100644 index a9d0a60b92..0000000000 --- a/components/ledger/pkg/client/models/operations/addmetadataontransaction.go +++ /dev/null @@ -1,61 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type AddMetadataOnTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - Txid *big.Int `pathParam:"style=simple,explode=false,name=txid"` - // metadata - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (a AddMetadataOnTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(a, "", false) -} - -func (a *AddMetadataOnTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &a, "", false, false); err != nil { - return err - } - return nil -} - -func (o *AddMetadataOnTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *AddMetadataOnTransactionRequest) GetTxid() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Txid -} - -func (o *AddMetadataOnTransactionRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type AddMetadataOnTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *AddMetadataOnTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/addmetadatatoaccount.go b/components/ledger/pkg/client/models/operations/addmetadatatoaccount.go deleted file mode 100644 index 35fca0062a..0000000000 --- a/components/ledger/pkg/client/models/operations/addmetadatatoaccount.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type AddMetadataToAccountRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Exact address of the account. It must match the following regular expressions pattern: - // ``` - // ^\w+(:\w+)*$ - // ``` - // - Address string `pathParam:"style=simple,explode=false,name=address"` - // metadata - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (o *AddMetadataToAccountRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *AddMetadataToAccountRequest) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *AddMetadataToAccountRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type AddMetadataToAccountResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *AddMetadataToAccountResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/countaccounts.go b/components/ledger/pkg/client/models/operations/countaccounts.go deleted file mode 100644 index b4304435f9..0000000000 --- a/components/ledger/pkg/client/models/operations/countaccounts.go +++ /dev/null @@ -1,56 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type CountAccountsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Filter accounts by address pattern (regular expression placed between ^ and $). - Address *string `queryParam:"style=form,explode=true,name=address"` - // Filter accounts by metadata key value pairs. The filter can be used like this metadata[key]=value1&metadata[a.nested.key]=value2 - Metadata map[string]any `queryParam:"style=deepObject,explode=true,name=metadata"` -} - -func (o *CountAccountsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *CountAccountsRequest) GetAddress() *string { - if o == nil { - return nil - } - return o.Address -} - -func (o *CountAccountsRequest) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -type CountAccountsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - Headers map[string][]string -} - -func (o *CountAccountsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *CountAccountsResponse) GetHeaders() map[string][]string { - if o == nil { - return map[string][]string{} - } - return o.Headers -} diff --git a/components/ledger/pkg/client/models/operations/counttransactions.go b/components/ledger/pkg/client/models/operations/counttransactions.go deleted file mode 100644 index b860993bbf..0000000000 --- a/components/ledger/pkg/client/models/operations/counttransactions.go +++ /dev/null @@ -1,122 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -// Metadata - Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. -type Metadata struct { -} - -type CountTransactionsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Filter transactions by reference field. - Reference *string `queryParam:"style=form,explode=true,name=reference"` - // Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). - Account *string `queryParam:"style=form,explode=true,name=account"` - // Filter transactions with postings involving given account at source (regular expression placed between ^ and $). - Source *string `queryParam:"style=form,explode=true,name=source"` - // Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). - Destination *string `queryParam:"style=form,explode=true,name=destination"` - // Filter transactions that occurred after this timestamp. - // The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - // - StartTime *time.Time `queryParam:"style=form,explode=true,name=startTime"` - // Filter transactions that occurred before this timestamp. - // The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - // - EndTime *time.Time `queryParam:"style=form,explode=true,name=endTime"` - // Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. - Metadata *Metadata `queryParam:"style=deepObject,explode=true,name=metadata"` -} - -func (c CountTransactionsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(c, "", false) -} - -func (c *CountTransactionsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &c, "", false, false); err != nil { - return err - } - return nil -} - -func (o *CountTransactionsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *CountTransactionsRequest) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *CountTransactionsRequest) GetAccount() *string { - if o == nil { - return nil - } - return o.Account -} - -func (o *CountTransactionsRequest) GetSource() *string { - if o == nil { - return nil - } - return o.Source -} - -func (o *CountTransactionsRequest) GetDestination() *string { - if o == nil { - return nil - } - return o.Destination -} - -func (o *CountTransactionsRequest) GetStartTime() *time.Time { - if o == nil { - return nil - } - return o.StartTime -} - -func (o *CountTransactionsRequest) GetEndTime() *time.Time { - if o == nil { - return nil - } - return o.EndTime -} - -func (o *CountTransactionsRequest) GetMetadata() *Metadata { - if o == nil { - return nil - } - return o.Metadata -} - -type CountTransactionsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - Headers map[string][]string -} - -func (o *CountTransactionsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *CountTransactionsResponse) GetHeaders() map[string][]string { - if o == nil { - return map[string][]string{} - } - return o.Headers -} diff --git a/components/ledger/pkg/client/models/operations/createtransaction.go b/components/ledger/pkg/client/models/operations/createtransaction.go deleted file mode 100644 index 6f883d3030..0000000000 --- a/components/ledger/pkg/client/models/operations/createtransaction.go +++ /dev/null @@ -1,60 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type CreateTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. - Preview *bool `queryParam:"style=form,explode=true,name=preview"` - // The request body must contain at least one of the following objects: - // - `postings`: suitable for simple transactions - // - `script`: enabling more complex transactions with Numscript - // - PostTransaction components.PostTransaction `request:"mediaType=application/json"` -} - -func (o *CreateTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *CreateTransactionRequest) GetPreview() *bool { - if o == nil { - return nil - } - return o.Preview -} - -func (o *CreateTransactionRequest) GetPostTransaction() components.PostTransaction { - if o == nil { - return components.PostTransaction{} - } - return o.PostTransaction -} - -type CreateTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - TransactionsResponse *components.TransactionsResponse -} - -func (o *CreateTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *CreateTransactionResponse) GetTransactionsResponse() *components.TransactionsResponse { - if o == nil { - return nil - } - return o.TransactionsResponse -} diff --git a/components/ledger/pkg/client/models/operations/createtransactions.go b/components/ledger/pkg/client/models/operations/createtransactions.go deleted file mode 100644 index 2a25422455..0000000000 --- a/components/ledger/pkg/client/models/operations/createtransactions.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type CreateTransactionsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - Transactions components.Transactions `request:"mediaType=application/json"` -} - -func (o *CreateTransactionsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *CreateTransactionsRequest) GetTransactions() components.Transactions { - if o == nil { - return components.Transactions{} - } - return o.Transactions -} - -type CreateTransactionsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - TransactionsResponse *components.TransactionsResponse -} - -func (o *CreateTransactionsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *CreateTransactionsResponse) GetTransactionsResponse() *components.TransactionsResponse { - if o == nil { - return nil - } - return o.TransactionsResponse -} diff --git a/components/ledger/pkg/client/models/operations/getaccount.go b/components/ledger/pkg/client/models/operations/getaccount.go deleted file mode 100644 index e4a7f6114d..0000000000 --- a/components/ledger/pkg/client/models/operations/getaccount.go +++ /dev/null @@ -1,52 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetAccountRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Exact address of the account. It must match the following regular expressions pattern: - // ``` - // ^\w+(:\w+)*$ - // ``` - // - Address string `pathParam:"style=simple,explode=false,name=address"` -} - -func (o *GetAccountRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *GetAccountRequest) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -type GetAccountResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - AccountResponse *components.AccountResponse -} - -func (o *GetAccountResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetAccountResponse) GetAccountResponse() *components.AccountResponse { - if o == nil { - return nil - } - return o.AccountResponse -} diff --git a/components/ledger/pkg/client/models/operations/getbalances.go b/components/ledger/pkg/client/models/operations/getbalances.go deleted file mode 100644 index b35954e174..0000000000 --- a/components/ledger/pkg/client/models/operations/getbalances.go +++ /dev/null @@ -1,92 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetBalancesRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Filter balances involving given account, either as source or destination. - Address *string `queryParam:"style=form,explode=true,name=address"` - // The maximum number of results to return per page. - // - PageSize *int64 `default:"15" queryParam:"style=form,explode=true,name=pageSize"` - // Pagination cursor, will return accounts after given address, in descending order. - After *string `queryParam:"style=form,explode=true,name=after"` - // Parameter used in pagination requests. Maximum page size is set to 1000. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` -} - -func (g GetBalancesRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(g, "", false) -} - -func (g *GetBalancesRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &g, "", false, false); err != nil { - return err - } - return nil -} - -func (o *GetBalancesRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *GetBalancesRequest) GetAddress() *string { - if o == nil { - return nil - } - return o.Address -} - -func (o *GetBalancesRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *GetBalancesRequest) GetAfter() *string { - if o == nil { - return nil - } - return o.After -} - -func (o *GetBalancesRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -type GetBalancesResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - BalancesCursorResponse *components.BalancesCursorResponse -} - -func (o *GetBalancesResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetBalancesResponse) GetBalancesCursorResponse() *components.BalancesCursorResponse { - if o == nil { - return nil - } - return o.BalancesCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/getbalancesaggregated.go b/components/ledger/pkg/client/models/operations/getbalancesaggregated.go deleted file mode 100644 index ce3f5a7541..0000000000 --- a/components/ledger/pkg/client/models/operations/getbalancesaggregated.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetBalancesAggregatedRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Filter balances involving given account, either as source or destination. - Address *string `queryParam:"style=form,explode=true,name=address"` - // Use insertion date instead of effective date - UseInsertionDate *bool `queryParam:"style=form,explode=true,name=useInsertionDate"` -} - -func (o *GetBalancesAggregatedRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *GetBalancesAggregatedRequest) GetAddress() *string { - if o == nil { - return nil - } - return o.Address -} - -func (o *GetBalancesAggregatedRequest) GetUseInsertionDate() *bool { - if o == nil { - return nil - } - return o.UseInsertionDate -} - -type GetBalancesAggregatedResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - AggregateBalancesResponse *components.AggregateBalancesResponse -} - -func (o *GetBalancesAggregatedResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetBalancesAggregatedResponse) GetAggregateBalancesResponse() *components.AggregateBalancesResponse { - if o == nil { - return nil - } - return o.AggregateBalancesResponse -} diff --git a/components/ledger/pkg/client/models/operations/getinfo.go b/components/ledger/pkg/client/models/operations/getinfo.go deleted file mode 100644 index 05ea225e03..0000000000 --- a/components/ledger/pkg/client/models/operations/getinfo.go +++ /dev/null @@ -1,27 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetInfoResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - ConfigInfoResponse *components.ConfigInfoResponse -} - -func (o *GetInfoResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetInfoResponse) GetConfigInfoResponse() *components.ConfigInfoResponse { - if o == nil { - return nil - } - return o.ConfigInfoResponse -} diff --git a/components/ledger/pkg/client/models/operations/getledgerinfo.go b/components/ledger/pkg/client/models/operations/getledgerinfo.go deleted file mode 100644 index 19a3909667..0000000000 --- a/components/ledger/pkg/client/models/operations/getledgerinfo.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetLedgerInfoRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *GetLedgerInfoRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type GetLedgerInfoResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - LedgerInfoResponse *components.LedgerInfoResponse -} - -func (o *GetLedgerInfoResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetLedgerInfoResponse) GetLedgerInfoResponse() *components.LedgerInfoResponse { - if o == nil { - return nil - } - return o.LedgerInfoResponse -} diff --git a/components/ledger/pkg/client/models/operations/getmapping.go b/components/ledger/pkg/client/models/operations/getmapping.go deleted file mode 100644 index 85223bb9d6..0000000000 --- a/components/ledger/pkg/client/models/operations/getmapping.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type GetMappingRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *GetMappingRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type GetMappingResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - MappingResponse *components.MappingResponse -} - -func (o *GetMappingResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetMappingResponse) GetMappingResponse() *components.MappingResponse { - if o == nil { - return nil - } - return o.MappingResponse -} diff --git a/components/ledger/pkg/client/models/operations/gettransaction.go b/components/ledger/pkg/client/models/operations/gettransaction.go deleted file mode 100644 index 4e96f83597..0000000000 --- a/components/ledger/pkg/client/models/operations/gettransaction.go +++ /dev/null @@ -1,61 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type GetTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - Txid *big.Int `pathParam:"style=simple,explode=false,name=txid"` -} - -func (g GetTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(g, "", false) -} - -func (g *GetTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &g, "", false, false); err != nil { - return err - } - return nil -} - -func (o *GetTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *GetTransactionRequest) GetTxid() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Txid -} - -type GetTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - TransactionResponse *components.TransactionResponse -} - -func (o *GetTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *GetTransactionResponse) GetTransactionResponse() *components.TransactionResponse { - if o == nil { - return nil - } - return o.TransactionResponse -} diff --git a/components/ledger/pkg/client/models/operations/listaccounts.go b/components/ledger/pkg/client/models/operations/listaccounts.go deleted file mode 100644 index bb51132098..0000000000 --- a/components/ledger/pkg/client/models/operations/listaccounts.go +++ /dev/null @@ -1,136 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" -) - -type ListAccountsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `default:"15" queryParam:"style=form,explode=true,name=pageSize"` - // Pagination cursor, will return accounts after given address, in descending order. - After *string `queryParam:"style=form,explode=true,name=after"` - // Filter accounts by address pattern (regular expression placed between ^ and $). - Address *string `queryParam:"style=form,explode=true,name=address"` - // Filter accounts by metadata key value pairs. Nested objects can be used as seen in the example below. - Metadata map[string]any `queryParam:"style=deepObject,explode=true,name=metadata"` - // Filter accounts by their balance (default operator is gte) - Balance *int64 `queryParam:"style=form,explode=true,name=balance"` - // Parameter used in pagination requests. Maximum page size is set to 1000. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - // Parameter used in pagination requests. Maximum page size is set to 1000. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // Deprecated, please use `cursor` instead. - // - // - // Deprecated field: This will be removed in a future release, please migrate away from it as soon as possible. - PaginationToken *string `queryParam:"style=form,explode=true,name=pagination_token"` -} - -func (l ListAccountsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(l, "", false) -} - -func (l *ListAccountsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &l, "", false, false); err != nil { - return err - } - return nil -} - -func (o *ListAccountsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *ListAccountsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *ListAccountsRequest) GetAfter() *string { - if o == nil { - return nil - } - return o.After -} - -func (o *ListAccountsRequest) GetAddress() *string { - if o == nil { - return nil - } - return o.Address -} - -func (o *ListAccountsRequest) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -func (o *ListAccountsRequest) GetBalance() *int64 { - if o == nil { - return nil - } - return o.Balance -} - -func (o *ListAccountsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *ListAccountsRequest) GetPaginationToken() *string { - if o == nil { - return nil - } - return o.PaginationToken -} - -type ListAccountsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - AccountsCursorResponse *components.AccountsCursorResponse - // Not found - ErrorResponse *sdkerrors.ErrorResponse -} - -func (o *ListAccountsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *ListAccountsResponse) GetAccountsCursorResponse() *components.AccountsCursorResponse { - if o == nil { - return nil - } - return o.AccountsCursorResponse -} - -func (o *ListAccountsResponse) GetErrorResponse() *sdkerrors.ErrorResponse { - if o == nil { - return nil - } - return o.ErrorResponse -} diff --git a/components/ledger/pkg/client/models/operations/listlogs.go b/components/ledger/pkg/client/models/operations/listlogs.go deleted file mode 100644 index b5a963d86d..0000000000 --- a/components/ledger/pkg/client/models/operations/listlogs.go +++ /dev/null @@ -1,106 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type ListLogsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `default:"15" queryParam:"style=form,explode=true,name=pageSize"` - // Pagination cursor, will return the logs after a given ID. (in descending order). - After *string `queryParam:"style=form,explode=true,name=after"` - // Filter transactions that occurred after this timestamp. - // The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - // - StartTime *time.Time `queryParam:"style=form,explode=true,name=startTime"` - // Filter transactions that occurred before this timestamp. - // The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - // - EndTime *time.Time `queryParam:"style=form,explode=true,name=endTime"` - // Parameter used in pagination requests. Maximum page size is set to 1000. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` -} - -func (l ListLogsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(l, "", false) -} - -func (l *ListLogsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &l, "", false, false); err != nil { - return err - } - return nil -} - -func (o *ListLogsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *ListLogsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *ListLogsRequest) GetAfter() *string { - if o == nil { - return nil - } - return o.After -} - -func (o *ListLogsRequest) GetStartTime() *time.Time { - if o == nil { - return nil - } - return o.StartTime -} - -func (o *ListLogsRequest) GetEndTime() *time.Time { - if o == nil { - return nil - } - return o.EndTime -} - -func (o *ListLogsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -type ListLogsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - LogsCursorResponse *components.LogsCursorResponse -} - -func (o *ListLogsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *ListLogsResponse) GetLogsCursorResponse() *components.LogsCursorResponse { - if o == nil { - return nil - } - return o.LogsCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/listtransactions.go b/components/ledger/pkg/client/models/operations/listtransactions.go deleted file mode 100644 index 6daa50137e..0000000000 --- a/components/ledger/pkg/client/models/operations/listtransactions.go +++ /dev/null @@ -1,151 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type ListTransactionsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `default:"15" queryParam:"style=form,explode=true,name=pageSize"` - // Pagination cursor, will return transactions after given txid (in descending order). - After *string `queryParam:"style=form,explode=true,name=after"` - // Find transactions by reference field. - Reference *string `queryParam:"style=form,explode=true,name=reference"` - // Filter transactions with postings involving given account, either as source or destination (regular expression placed between ^ and $). - Account *string `queryParam:"style=form,explode=true,name=account"` - // Filter transactions with postings involving given account at source (regular expression placed between ^ and $). - Source *string `queryParam:"style=form,explode=true,name=source"` - // Filter transactions with postings involving given account at destination (regular expression placed between ^ and $). - Destination *string `queryParam:"style=form,explode=true,name=destination"` - // Filter transactions that occurred after this timestamp. - // The format is RFC3339 and is inclusive (for example, "2023-01-02T15:04:01Z" includes the first second of 4th minute). - // - StartTime *time.Time `queryParam:"style=form,explode=true,name=startTime"` - // Filter transactions that occurred before this timestamp. - // The format is RFC3339 and is exclusive (for example, "2023-01-02T15:04:01Z" excludes the first second of 4th minute). - // - EndTime *time.Time `queryParam:"style=form,explode=true,name=endTime"` - // Parameter used in pagination requests. Maximum page size is set to 1000. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - // Filter transactions by metadata key value pairs. Nested objects can be used as seen in the example below. - Metadata map[string]any `queryParam:"style=deepObject,explode=true,name=metadata"` -} - -func (l ListTransactionsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(l, "", false) -} - -func (l *ListTransactionsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &l, "", false, false); err != nil { - return err - } - return nil -} - -func (o *ListTransactionsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *ListTransactionsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *ListTransactionsRequest) GetAfter() *string { - if o == nil { - return nil - } - return o.After -} - -func (o *ListTransactionsRequest) GetReference() *string { - if o == nil { - return nil - } - return o.Reference -} - -func (o *ListTransactionsRequest) GetAccount() *string { - if o == nil { - return nil - } - return o.Account -} - -func (o *ListTransactionsRequest) GetSource() *string { - if o == nil { - return nil - } - return o.Source -} - -func (o *ListTransactionsRequest) GetDestination() *string { - if o == nil { - return nil - } - return o.Destination -} - -func (o *ListTransactionsRequest) GetStartTime() *time.Time { - if o == nil { - return nil - } - return o.StartTime -} - -func (o *ListTransactionsRequest) GetEndTime() *time.Time { - if o == nil { - return nil - } - return o.EndTime -} - -func (o *ListTransactionsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *ListTransactionsRequest) GetMetadata() map[string]any { - if o == nil { - return nil - } - return o.Metadata -} - -type ListTransactionsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - TransactionsCursorResponse *components.TransactionsCursorResponse -} - -func (o *ListTransactionsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *ListTransactionsResponse) GetTransactionsCursorResponse() *components.TransactionsCursorResponse { - if o == nil { - return nil - } - return o.TransactionsCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/options.go b/components/ledger/pkg/client/models/operations/options.go deleted file mode 100644 index fedc0031f6..0000000000 --- a/components/ledger/pkg/client/models/operations/options.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "errors" - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/retry" - "time" -) - -var ErrUnsupportedOption = errors.New("unsupported option") - -const ( - SupportedOptionServerURL = "serverURL" - SupportedOptionRetries = "retries" - SupportedOptionTimeout = "timeout" - SupportedOptionAcceptHeaderOverride = "acceptHeaderOverride" - SupportedOptionURLOverride = "urlOverride" -) - -type AcceptHeaderEnum string - -const ( - AcceptHeaderEnumApplicationJson AcceptHeaderEnum = "application/json" - AcceptHeaderEnumWildcardWildcard AcceptHeaderEnum = "*/*" -) - -func (e AcceptHeaderEnum) ToPointer() *AcceptHeaderEnum { - return &e -} - -type Options struct { - ServerURL *string - Retries *retry.Config - Timeout *time.Duration - AcceptHeaderOverride *AcceptHeaderEnum - URLOverride *string -} - -type Option func(*Options, ...string) error - -// WithServerURL allows providing an alternative server URL. -func WithServerURL(serverURL string) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionServerURL) { - return ErrUnsupportedOption - } - - opts.ServerURL = &serverURL - return nil - } -} - -// WithTemplatedServerURL allows providing an alternative server URL with templated parameters. -func WithTemplatedServerURL(serverURL string, params map[string]string) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionServerURL) { - return ErrUnsupportedOption - } - - if params != nil { - serverURL = utils.ReplaceParameters(serverURL, params) - } - - opts.ServerURL = &serverURL - return nil - } -} - -// WithRetries allows customizing the default retry configuration. -func WithRetries(config retry.Config) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionRetries) { - return ErrUnsupportedOption - } - - opts.Retries = &config - return nil - } -} - -// WithOperationTimeout allows setting the request timeout applied for an operation. -func WithOperationTimeout(timeout time.Duration) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionRetries) { - return ErrUnsupportedOption - } - - opts.Timeout = &timeout - return nil - } -} - -func WithAcceptHeaderOverride(acceptHeaderOverride AcceptHeaderEnum) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionAcceptHeaderOverride) { - return ErrUnsupportedOption - } - - opts.AcceptHeaderOverride = &acceptHeaderOverride - return nil - } -} - -// WithURLOverride allows overriding the URL. -func WithURLOverride(urlOverride string) Option { - return func(opts *Options, supportedOptions ...string) error { - if !utils.Contains(supportedOptions, SupportedOptionURLOverride) { - return ErrUnsupportedOption - } - - opts.URLOverride = &urlOverride - return nil - } -} diff --git a/components/ledger/pkg/client/models/operations/readstats.go b/components/ledger/pkg/client/models/operations/readstats.go deleted file mode 100644 index 5a95cadffa..0000000000 --- a/components/ledger/pkg/client/models/operations/readstats.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type ReadStatsRequest struct { - // name of the ledger - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *ReadStatsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type ReadStatsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - StatsResponse *components.StatsResponse -} - -func (o *ReadStatsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *ReadStatsResponse) GetStatsResponse() *components.StatsResponse { - if o == nil { - return nil - } - return o.StatsResponse -} diff --git a/components/ledger/pkg/client/models/operations/reverttransaction.go b/components/ledger/pkg/client/models/operations/reverttransaction.go deleted file mode 100644 index af8d63c2be..0000000000 --- a/components/ledger/pkg/client/models/operations/reverttransaction.go +++ /dev/null @@ -1,70 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type RevertTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - Txid *big.Int `pathParam:"style=simple,explode=false,name=txid"` - // Allow to disable balances checks - DisableChecks *bool `queryParam:"style=form,explode=true,name=disableChecks"` -} - -func (r RevertTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(r, "", false) -} - -func (r *RevertTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &r, "", false, false); err != nil { - return err - } - return nil -} - -func (o *RevertTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *RevertTransactionRequest) GetTxid() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.Txid -} - -func (o *RevertTransactionRequest) GetDisableChecks() *bool { - if o == nil { - return nil - } - return o.DisableChecks -} - -type RevertTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - TransactionResponse *components.TransactionResponse -} - -func (o *RevertTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *RevertTransactionResponse) GetTransactionResponse() *components.TransactionResponse { - if o == nil { - return nil - } - return o.TransactionResponse -} diff --git a/components/ledger/pkg/client/models/operations/runscript.go b/components/ledger/pkg/client/models/operations/runscript.go deleted file mode 100644 index 0555f3893f..0000000000 --- a/components/ledger/pkg/client/models/operations/runscript.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type RunScriptRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Set the preview mode. Preview mode doesn't add the logs to the database or publish a message to the message broker. - Preview *bool `queryParam:"style=form,explode=true,name=preview"` - Script components.Script `request:"mediaType=application/json"` -} - -func (o *RunScriptRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *RunScriptRequest) GetPreview() *bool { - if o == nil { - return nil - } - return o.Preview -} - -func (o *RunScriptRequest) GetScript() components.Script { - if o == nil { - return components.Script{} - } - return o.Script -} - -type RunScriptResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // On success, it will return a 200 status code, and the resulting transaction under the `transaction` field. - // - // On failure, it will also return a 200 status code, and the following fields: - // - `details`: contains a URL. When there is an error parsing Numscript, the result can be difficult to read—the provided URL will render the error in an easy-to-read format. - // - `errorCode` and `error_code` (deprecated): contains the string code of the error - // - `errorMessage` and `error_message` (deprecated): contains a human-readable indication of what went wrong, for example that an account had insufficient funds, or that there was an error in the provided Numscript. - // - ScriptResponse *components.ScriptResponse -} - -func (o *RunScriptResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *RunScriptResponse) GetScriptResponse() *components.ScriptResponse { - if o == nil { - return nil - } - return o.ScriptResponse -} diff --git a/components/ledger/pkg/client/models/operations/updatemapping.go b/components/ledger/pkg/client/models/operations/updatemapping.go deleted file mode 100644 index ee84332f3f..0000000000 --- a/components/ledger/pkg/client/models/operations/updatemapping.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type UpdateMappingRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - Mapping *components.Mapping `request:"mediaType=application/json"` -} - -func (o *UpdateMappingRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *UpdateMappingRequest) GetMapping() *components.Mapping { - if o == nil { - return nil - } - return o.Mapping -} - -type UpdateMappingResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - MappingResponse *components.MappingResponse -} - -func (o *UpdateMappingResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *UpdateMappingResponse) GetMappingResponse() *components.MappingResponse { - if o == nil { - return nil - } - return o.MappingResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2addmetadataontransaction.go b/components/ledger/pkg/client/models/operations/v2addmetadataontransaction.go deleted file mode 100644 index 52e5c5c9d1..0000000000 --- a/components/ledger/pkg/client/models/operations/v2addmetadataontransaction.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type V2AddMetadataOnTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - ID *big.Int `pathParam:"style=simple,explode=false,name=id"` - // Set the dryRun mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. - DryRun *bool `queryParam:"style=form,explode=true,name=dryRun"` - // Use an idempotency key - IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"` - // metadata - RequestBody map[string]string `request:"mediaType=application/json"` -} - -func (v V2AddMetadataOnTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2AddMetadataOnTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2AddMetadataOnTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2AddMetadataOnTransactionRequest) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2AddMetadataOnTransactionRequest) GetDryRun() *bool { - if o == nil { - return nil - } - return o.DryRun -} - -func (o *V2AddMetadataOnTransactionRequest) GetIdempotencyKey() *string { - if o == nil { - return nil - } - return o.IdempotencyKey -} - -func (o *V2AddMetadataOnTransactionRequest) GetRequestBody() map[string]string { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2AddMetadataOnTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2AddMetadataOnTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2addmetadatatoaccount.go b/components/ledger/pkg/client/models/operations/v2addmetadatatoaccount.go deleted file mode 100644 index 95fd332b5a..0000000000 --- a/components/ledger/pkg/client/models/operations/v2addmetadatatoaccount.go +++ /dev/null @@ -1,70 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2AddMetadataToAccountRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Exact address of the account. It must match the following regular expressions pattern: - // ``` - // ^\w+(:\w+)*$ - // ``` - // - Address string `pathParam:"style=simple,explode=false,name=address"` - // Set the dry run mode. Dry run mode doesn't add the logs to the database or publish a message to the message broker. - DryRun *bool `queryParam:"style=form,explode=true,name=dryRun"` - // Use an idempotency key - IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"` - // metadata - RequestBody map[string]string `request:"mediaType=application/json"` -} - -func (o *V2AddMetadataToAccountRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2AddMetadataToAccountRequest) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *V2AddMetadataToAccountRequest) GetDryRun() *bool { - if o == nil { - return nil - } - return o.DryRun -} - -func (o *V2AddMetadataToAccountRequest) GetIdempotencyKey() *string { - if o == nil { - return nil - } - return o.IdempotencyKey -} - -func (o *V2AddMetadataToAccountRequest) GetRequestBody() map[string]string { - if o == nil { - return map[string]string{} - } - return o.RequestBody -} - -type V2AddMetadataToAccountResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2AddMetadataToAccountResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2countaccounts.go b/components/ledger/pkg/client/models/operations/v2countaccounts.go deleted file mode 100644 index 33239ba922..0000000000 --- a/components/ledger/pkg/client/models/operations/v2countaccounts.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2CountAccountsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2CountAccountsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2CountAccountsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2CountAccountsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2CountAccountsRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2CountAccountsRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2CountAccountsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - Headers map[string][]string -} - -func (o *V2CountAccountsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2CountAccountsResponse) GetHeaders() map[string][]string { - if o == nil { - return map[string][]string{} - } - return o.Headers -} diff --git a/components/ledger/pkg/client/models/operations/v2counttransactions.go b/components/ledger/pkg/client/models/operations/v2counttransactions.go deleted file mode 100644 index acc7bbe0de..0000000000 --- a/components/ledger/pkg/client/models/operations/v2counttransactions.go +++ /dev/null @@ -1,67 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2CountTransactionsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2CountTransactionsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2CountTransactionsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2CountTransactionsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2CountTransactionsRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2CountTransactionsRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2CountTransactionsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - Headers map[string][]string -} - -func (o *V2CountTransactionsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2CountTransactionsResponse) GetHeaders() map[string][]string { - if o == nil { - return map[string][]string{} - } - return o.Headers -} diff --git a/components/ledger/pkg/client/models/operations/v2createbulk.go b/components/ledger/pkg/client/models/operations/v2createbulk.go deleted file mode 100644 index 50ab926040..0000000000 --- a/components/ledger/pkg/client/models/operations/v2createbulk.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2CreateBulkRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - RequestBody []components.V2BulkElement `request:"mediaType=application/json"` -} - -func (o *V2CreateBulkRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2CreateBulkRequest) GetRequestBody() []components.V2BulkElement { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2CreateBulkResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2BulkResponse *components.V2BulkResponse -} - -func (o *V2CreateBulkResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2CreateBulkResponse) GetV2BulkResponse() *components.V2BulkResponse { - if o == nil { - return nil - } - return o.V2BulkResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2createledger.go b/components/ledger/pkg/client/models/operations/v2createledger.go deleted file mode 100644 index 322ff14e84..0000000000 --- a/components/ledger/pkg/client/models/operations/v2createledger.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2CreateLedgerRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - V2CreateLedgerRequest *components.V2CreateLedgerRequest `request:"mediaType=application/json"` -} - -func (o *V2CreateLedgerRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2CreateLedgerRequest) GetV2CreateLedgerRequest() *components.V2CreateLedgerRequest { - if o == nil { - return nil - } - return o.V2CreateLedgerRequest -} - -type V2CreateLedgerResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2CreateLedgerResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2createtransaction.go b/components/ledger/pkg/client/models/operations/v2createtransaction.go deleted file mode 100644 index aeb743cd1d..0000000000 --- a/components/ledger/pkg/client/models/operations/v2createtransaction.go +++ /dev/null @@ -1,69 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2CreateTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Set the dryRun mode. dry run mode doesn't add the logs to the database or publish a message to the message broker. - DryRun *bool `queryParam:"style=form,explode=true,name=dryRun"` - // Use an idempotency key - IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"` - // The request body must contain at least one of the following objects: - // - `postings`: suitable for simple transactions - // - `script`: enabling more complex transactions with Numscript - // - V2PostTransaction components.V2PostTransaction `request:"mediaType=application/json"` -} - -func (o *V2CreateTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2CreateTransactionRequest) GetDryRun() *bool { - if o == nil { - return nil - } - return o.DryRun -} - -func (o *V2CreateTransactionRequest) GetIdempotencyKey() *string { - if o == nil { - return nil - } - return o.IdempotencyKey -} - -func (o *V2CreateTransactionRequest) GetV2PostTransaction() components.V2PostTransaction { - if o == nil { - return components.V2PostTransaction{} - } - return o.V2PostTransaction -} - -type V2CreateTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2CreateTransactionResponse *components.V2CreateTransactionResponse -} - -func (o *V2CreateTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2CreateTransactionResponse) GetV2CreateTransactionResponse() *components.V2CreateTransactionResponse { - if o == nil { - return nil - } - return o.V2CreateTransactionResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2deleteaccountmetadata.go b/components/ledger/pkg/client/models/operations/v2deleteaccountmetadata.go deleted file mode 100644 index 851f8048db..0000000000 --- a/components/ledger/pkg/client/models/operations/v2deleteaccountmetadata.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2DeleteAccountMetadataRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Account address - Address string `pathParam:"style=simple,explode=false,name=address"` - // The key to remove. - Key string `pathParam:"style=simple,explode=false,name=key"` -} - -func (o *V2DeleteAccountMetadataRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2DeleteAccountMetadataRequest) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *V2DeleteAccountMetadataRequest) GetKey() string { - if o == nil { - return "" - } - return o.Key -} - -type V2DeleteAccountMetadataResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2DeleteAccountMetadataResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2deleteledgermetadata.go b/components/ledger/pkg/client/models/operations/v2deleteledgermetadata.go deleted file mode 100644 index 82cbbd7894..0000000000 --- a/components/ledger/pkg/client/models/operations/v2deleteledgermetadata.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2DeleteLedgerMetadataRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Key to remove. - Key string `pathParam:"style=simple,explode=false,name=key"` -} - -func (o *V2DeleteLedgerMetadataRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2DeleteLedgerMetadataRequest) GetKey() string { - if o == nil { - return "" - } - return o.Key -} - -type V2DeleteLedgerMetadataResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2DeleteLedgerMetadataResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2deletetransactionmetadata.go b/components/ledger/pkg/client/models/operations/v2deletetransactionmetadata.go deleted file mode 100644 index 33613091c0..0000000000 --- a/components/ledger/pkg/client/models/operations/v2deletetransactionmetadata.go +++ /dev/null @@ -1,61 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type V2DeleteTransactionMetadataRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - ID *big.Int `pathParam:"style=simple,explode=false,name=id"` - // The key to remove. - Key string `pathParam:"style=simple,explode=false,name=key"` -} - -func (v V2DeleteTransactionMetadataRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2DeleteTransactionMetadataRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2DeleteTransactionMetadataRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2DeleteTransactionMetadataRequest) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2DeleteTransactionMetadataRequest) GetKey() string { - if o == nil { - return "" - } - return o.Key -} - -type V2DeleteTransactionMetadataResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2DeleteTransactionMetadataResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2exportlogs.go b/components/ledger/pkg/client/models/operations/v2exportlogs.go deleted file mode 100644 index 42c0e4557b..0000000000 --- a/components/ledger/pkg/client/models/operations/v2exportlogs.go +++ /dev/null @@ -1,30 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2ExportLogsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *V2ExportLogsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type V2ExportLogsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2ExportLogsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2getaccount.go b/components/ledger/pkg/client/models/operations/v2getaccount.go deleted file mode 100644 index b54f77eeb2..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getaccount.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2GetAccountRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Exact address of the account. It must match the following regular expressions pattern: - // ``` - // ^\w+(:\w+)*$ - // ``` - // - Address string `pathParam:"style=simple,explode=false,name=address"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` -} - -func (v V2GetAccountRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2GetAccountRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2GetAccountRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2GetAccountRequest) GetAddress() string { - if o == nil { - return "" - } - return o.Address -} - -func (o *V2GetAccountRequest) GetExpand() *string { - if o == nil { - return nil - } - return o.Expand -} - -func (o *V2GetAccountRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -type V2GetAccountResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2AccountResponse *components.V2AccountResponse -} - -func (o *V2GetAccountResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetAccountResponse) GetV2AccountResponse() *components.V2AccountResponse { - if o == nil { - return nil - } - return o.V2AccountResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2getbalancesaggregated.go b/components/ledger/pkg/client/models/operations/v2getbalancesaggregated.go deleted file mode 100644 index e482bbbbf9..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getbalancesaggregated.go +++ /dev/null @@ -1,77 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2GetBalancesAggregatedRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - // Use insertion date instead of effective date - UseInsertionDate *bool `queryParam:"style=form,explode=true,name=useInsertionDate"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2GetBalancesAggregatedRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2GetBalancesAggregatedRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2GetBalancesAggregatedRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2GetBalancesAggregatedRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2GetBalancesAggregatedRequest) GetUseInsertionDate() *bool { - if o == nil { - return nil - } - return o.UseInsertionDate -} - -func (o *V2GetBalancesAggregatedRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2GetBalancesAggregatedResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2AggregateBalancesResponse *components.V2AggregateBalancesResponse -} - -func (o *V2GetBalancesAggregatedResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetBalancesAggregatedResponse) GetV2AggregateBalancesResponse() *components.V2AggregateBalancesResponse { - if o == nil { - return nil - } - return o.V2AggregateBalancesResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2getinfo.go b/components/ledger/pkg/client/models/operations/v2getinfo.go deleted file mode 100644 index 136491d884..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getinfo.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" -) - -type V2GetInfoResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2ConfigInfoResponse *components.V2ConfigInfoResponse - // Error - V2ErrorResponse *sdkerrors.V2ErrorResponse -} - -func (o *V2GetInfoResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetInfoResponse) GetV2ConfigInfoResponse() *components.V2ConfigInfoResponse { - if o == nil { - return nil - } - return o.V2ConfigInfoResponse -} - -func (o *V2GetInfoResponse) GetV2ErrorResponse() *sdkerrors.V2ErrorResponse { - if o == nil { - return nil - } - return o.V2ErrorResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2getledger.go b/components/ledger/pkg/client/models/operations/v2getledger.go deleted file mode 100644 index 6a239e206d..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getledger.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2GetLedgerRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *V2GetLedgerRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type V2GetLedgerResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2GetLedgerResponse *components.V2GetLedgerResponse -} - -func (o *V2GetLedgerResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetLedgerResponse) GetV2GetLedgerResponse() *components.V2GetLedgerResponse { - if o == nil { - return nil - } - return o.V2GetLedgerResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2getledgerinfo.go b/components/ledger/pkg/client/models/operations/v2getledgerinfo.go deleted file mode 100644 index 5bf4982d37..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getledgerinfo.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2GetLedgerInfoRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *V2GetLedgerInfoRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type V2GetLedgerInfoResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2LedgerInfoResponse *components.V2LedgerInfoResponse -} - -func (o *V2GetLedgerInfoResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetLedgerInfoResponse) GetV2LedgerInfoResponse() *components.V2LedgerInfoResponse { - if o == nil { - return nil - } - return o.V2LedgerInfoResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2gettransaction.go b/components/ledger/pkg/client/models/operations/v2gettransaction.go deleted file mode 100644 index a1f2641071..0000000000 --- a/components/ledger/pkg/client/models/operations/v2gettransaction.go +++ /dev/null @@ -1,78 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" - "time" -) - -type V2GetTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - ID *big.Int `pathParam:"style=simple,explode=false,name=id"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` -} - -func (v V2GetTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2GetTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2GetTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2GetTransactionRequest) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2GetTransactionRequest) GetExpand() *string { - if o == nil { - return nil - } - return o.Expand -} - -func (o *V2GetTransactionRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -type V2GetTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2GetTransactionResponse *components.V2GetTransactionResponse -} - -func (o *V2GetTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetTransactionResponse) GetV2GetTransactionResponse() *components.V2GetTransactionResponse { - if o == nil { - return nil - } - return o.V2GetTransactionResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2getvolumeswithbalances.go b/components/ledger/pkg/client/models/operations/v2getvolumeswithbalances.go deleted file mode 100644 index 82a036286e..0000000000 --- a/components/ledger/pkg/client/models/operations/v2getvolumeswithbalances.go +++ /dev/null @@ -1,117 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2GetVolumesWithBalancesRequest struct { - // The maximum number of results to return per page. - // - PageSize *int64 `queryParam:"style=form,explode=true,name=pageSize"` - // Parameter used in pagination requests. Maximum page size is set to 15. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - EndTime *time.Time `queryParam:"style=form,explode=true,name=endTime"` - StartTime *time.Time `queryParam:"style=form,explode=true,name=startTime"` - // Use insertion date instead of effective date - InsertionDate *bool `queryParam:"style=form,explode=true,name=insertionDate"` - // Group volumes and balance by the level of the segment of the address - GroupBy *int64 `queryParam:"style=form,explode=true,name=groupBy"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2GetVolumesWithBalancesRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2GetVolumesWithBalancesRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2GetVolumesWithBalancesRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *V2GetVolumesWithBalancesRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *V2GetVolumesWithBalancesRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2GetVolumesWithBalancesRequest) GetEndTime() *time.Time { - if o == nil { - return nil - } - return o.EndTime -} - -func (o *V2GetVolumesWithBalancesRequest) GetStartTime() *time.Time { - if o == nil { - return nil - } - return o.StartTime -} - -func (o *V2GetVolumesWithBalancesRequest) GetInsertionDate() *bool { - if o == nil { - return nil - } - return o.InsertionDate -} - -func (o *V2GetVolumesWithBalancesRequest) GetGroupBy() *int64 { - if o == nil { - return nil - } - return o.GroupBy -} - -func (o *V2GetVolumesWithBalancesRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2GetVolumesWithBalancesResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2VolumesWithBalanceCursorResponse *components.V2VolumesWithBalanceCursorResponse -} - -func (o *V2GetVolumesWithBalancesResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2GetVolumesWithBalancesResponse) GetV2VolumesWithBalanceCursorResponse() *components.V2VolumesWithBalanceCursorResponse { - if o == nil { - return nil - } - return o.V2VolumesWithBalanceCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2importlogs.go b/components/ledger/pkg/client/models/operations/v2importlogs.go deleted file mode 100644 index d5722de7ef..0000000000 --- a/components/ledger/pkg/client/models/operations/v2importlogs.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2ImportLogsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - RequestBody *string `request:"mediaType=application/octet-stream"` -} - -func (o *V2ImportLogsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2ImportLogsRequest) GetRequestBody() *string { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2ImportLogsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` -} - -func (o *V2ImportLogsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} diff --git a/components/ledger/pkg/client/models/operations/v2listaccounts.go b/components/ledger/pkg/client/models/operations/v2listaccounts.go deleted file mode 100644 index 4f18a0fb5e..0000000000 --- a/components/ledger/pkg/client/models/operations/v2listaccounts.go +++ /dev/null @@ -1,99 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2ListAccountsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `queryParam:"style=form,explode=true,name=pageSize"` - // Parameter used in pagination requests. Maximum page size is set to 15. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2ListAccountsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2ListAccountsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2ListAccountsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2ListAccountsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *V2ListAccountsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *V2ListAccountsRequest) GetExpand() *string { - if o == nil { - return nil - } - return o.Expand -} - -func (o *V2ListAccountsRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2ListAccountsRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2ListAccountsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2AccountsCursorResponse *components.V2AccountsCursorResponse -} - -func (o *V2ListAccountsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2ListAccountsResponse) GetV2AccountsCursorResponse() *components.V2AccountsCursorResponse { - if o == nil { - return nil - } - return o.V2AccountsCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2listledgers.go b/components/ledger/pkg/client/models/operations/v2listledgers.go deleted file mode 100644 index 4c88234f46..0000000000 --- a/components/ledger/pkg/client/models/operations/v2listledgers.go +++ /dev/null @@ -1,53 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2ListLedgersRequest struct { - // The maximum number of results to return per page. - // - PageSize *int64 `queryParam:"style=form,explode=true,name=pageSize"` - // Parameter used in pagination requests. Maximum page size is set to 15. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` -} - -func (o *V2ListLedgersRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *V2ListLedgersRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -type V2ListLedgersResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2LedgerListResponse *components.V2LedgerListResponse -} - -func (o *V2ListLedgersResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2ListLedgersResponse) GetV2LedgerListResponse() *components.V2LedgerListResponse { - if o == nil { - return nil - } - return o.V2LedgerListResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2listlogs.go b/components/ledger/pkg/client/models/operations/v2listlogs.go deleted file mode 100644 index 1f792f6f02..0000000000 --- a/components/ledger/pkg/client/models/operations/v2listlogs.go +++ /dev/null @@ -1,91 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type V2ListLogsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `queryParam:"style=form,explode=true,name=pageSize"` - // Parameter used in pagination requests. Maximum page size is set to 15. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2ListLogsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2ListLogsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2ListLogsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2ListLogsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *V2ListLogsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *V2ListLogsRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2ListLogsRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2ListLogsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2LogsCursorResponse *components.V2LogsCursorResponse -} - -func (o *V2ListLogsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2ListLogsResponse) GetV2LogsCursorResponse() *components.V2LogsCursorResponse { - if o == nil { - return nil - } - return o.V2LogsCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2listtransactions.go b/components/ledger/pkg/client/models/operations/v2listtransactions.go deleted file mode 100644 index 8eaddb731b..0000000000 --- a/components/ledger/pkg/client/models/operations/v2listtransactions.go +++ /dev/null @@ -1,140 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "encoding/json" - "fmt" - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "time" -) - -type Order string - -const ( - OrderEffective Order = "effective" -) - -func (e Order) ToPointer() *Order { - return &e -} -func (e *Order) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "effective": - *e = Order(v) - return nil - default: - return fmt.Errorf("invalid value for Order: %v", v) - } -} - -type V2ListTransactionsRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // The maximum number of results to return per page. - // - PageSize *int64 `queryParam:"style=form,explode=true,name=pageSize"` - // Parameter used in pagination requests. Maximum page size is set to 15. - // Set to the value of next for the next page of results. - // Set to the value of previous for the previous page of results. - // No other parameters can be set when this parameter is set. - // - Cursor *string `queryParam:"style=form,explode=true,name=cursor"` - Expand *string `queryParam:"style=form,explode=true,name=expand"` - Pit *time.Time `queryParam:"style=form,explode=true,name=pit"` - Order *Order `queryParam:"style=form,explode=true,name=order"` - Reverse *bool `queryParam:"style=form,explode=true,name=reverse"` - RequestBody map[string]any `request:"mediaType=application/json"` -} - -func (v V2ListTransactionsRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2ListTransactionsRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2ListTransactionsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2ListTransactionsRequest) GetPageSize() *int64 { - if o == nil { - return nil - } - return o.PageSize -} - -func (o *V2ListTransactionsRequest) GetCursor() *string { - if o == nil { - return nil - } - return o.Cursor -} - -func (o *V2ListTransactionsRequest) GetExpand() *string { - if o == nil { - return nil - } - return o.Expand -} - -func (o *V2ListTransactionsRequest) GetPit() *time.Time { - if o == nil { - return nil - } - return o.Pit -} - -func (o *V2ListTransactionsRequest) GetOrder() *Order { - if o == nil { - return nil - } - return o.Order -} - -func (o *V2ListTransactionsRequest) GetReverse() *bool { - if o == nil { - return nil - } - return o.Reverse -} - -func (o *V2ListTransactionsRequest) GetRequestBody() map[string]any { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2ListTransactionsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2TransactionsCursorResponse *components.V2TransactionsCursorResponse -} - -func (o *V2ListTransactionsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2ListTransactionsResponse) GetV2TransactionsCursorResponse() *components.V2TransactionsCursorResponse { - if o == nil { - return nil - } - return o.V2TransactionsCursorResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2readstats.go b/components/ledger/pkg/client/models/operations/v2readstats.go deleted file mode 100644 index 74f7c84920..0000000000 --- a/components/ledger/pkg/client/models/operations/v2readstats.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" -) - -type V2ReadStatsRequest struct { - // name of the ledger - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` -} - -func (o *V2ReadStatsRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -type V2ReadStatsResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2StatsResponse *components.V2StatsResponse -} - -func (o *V2ReadStatsResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2ReadStatsResponse) GetV2StatsResponse() *components.V2StatsResponse { - if o == nil { - return nil - } - return o.V2StatsResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2reverttransaction.go b/components/ledger/pkg/client/models/operations/v2reverttransaction.go deleted file mode 100644 index 786e3a47d6..0000000000 --- a/components/ledger/pkg/client/models/operations/v2reverttransaction.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "math/big" -) - -type V2RevertTransactionRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - // Transaction ID. - ID *big.Int `pathParam:"style=simple,explode=false,name=id"` - // Force revert - Force *bool `queryParam:"style=form,explode=true,name=force"` - // Revert transaction at effective date of the original tx - AtEffectiveDate *bool `queryParam:"style=form,explode=true,name=atEffectiveDate"` -} - -func (v V2RevertTransactionRequest) MarshalJSON() ([]byte, error) { - return utils.MarshalJSON(v, "", false) -} - -func (v *V2RevertTransactionRequest) UnmarshalJSON(data []byte) error { - if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil { - return err - } - return nil -} - -func (o *V2RevertTransactionRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2RevertTransactionRequest) GetID() *big.Int { - if o == nil { - return big.NewInt(0) - } - return o.ID -} - -func (o *V2RevertTransactionRequest) GetForce() *bool { - if o == nil { - return nil - } - return o.Force -} - -func (o *V2RevertTransactionRequest) GetAtEffectiveDate() *bool { - if o == nil { - return nil - } - return o.AtEffectiveDate -} - -type V2RevertTransactionResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // OK - V2RevertTransactionResponse *components.V2RevertTransactionResponse -} - -func (o *V2RevertTransactionResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2RevertTransactionResponse) GetV2RevertTransactionResponse() *components.V2RevertTransactionResponse { - if o == nil { - return nil - } - return o.V2RevertTransactionResponse -} diff --git a/components/ledger/pkg/client/models/operations/v2updateledgermetadata.go b/components/ledger/pkg/client/models/operations/v2updateledgermetadata.go deleted file mode 100644 index e8d8e69dd6..0000000000 --- a/components/ledger/pkg/client/models/operations/v2updateledgermetadata.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package operations - -import ( - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" -) - -type V2UpdateLedgerMetadataRequest struct { - // Name of the ledger. - Ledger string `pathParam:"style=simple,explode=false,name=ledger"` - RequestBody map[string]string `request:"mediaType=application/json"` -} - -func (o *V2UpdateLedgerMetadataRequest) GetLedger() string { - if o == nil { - return "" - } - return o.Ledger -} - -func (o *V2UpdateLedgerMetadataRequest) GetRequestBody() map[string]string { - if o == nil { - return nil - } - return o.RequestBody -} - -type V2UpdateLedgerMetadataResponse struct { - HTTPMeta components.HTTPMetadata `json:"-"` - // Error - V2ErrorResponse *sdkerrors.V2ErrorResponse -} - -func (o *V2UpdateLedgerMetadataResponse) GetHTTPMeta() components.HTTPMetadata { - if o == nil { - return components.HTTPMetadata{} - } - return o.HTTPMeta -} - -func (o *V2UpdateLedgerMetadataResponse) GetV2ErrorResponse() *sdkerrors.V2ErrorResponse { - if o == nil { - return nil - } - return o.V2ErrorResponse -} diff --git a/components/ledger/pkg/client/models/sdkerrors/errorresponse.go b/components/ledger/pkg/client/models/sdkerrors/errorresponse.go deleted file mode 100644 index cde5b68251..0000000000 --- a/components/ledger/pkg/client/models/sdkerrors/errorresponse.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package sdkerrors - -import ( - "encoding/json" - "github.com/formancehq/stack/ledger/client/models/components" -) - -// ErrorResponse - Error -type ErrorResponse struct { - ErrorCode components.ErrorsEnum `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - Details *string `json:"details,omitempty"` -} - -var _ error = &ErrorResponse{} - -func (e *ErrorResponse) Error() string { - data, _ := json.Marshal(e) - return string(data) -} diff --git a/components/ledger/pkg/client/models/sdkerrors/sdkerror.go b/components/ledger/pkg/client/models/sdkerrors/sdkerror.go deleted file mode 100644 index 7d63b9813d..0000000000 --- a/components/ledger/pkg/client/models/sdkerrors/sdkerror.go +++ /dev/null @@ -1,35 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package sdkerrors - -import ( - "fmt" - "net/http" -) - -type SDKError struct { - Message string - StatusCode int - Body string - RawResponse *http.Response -} - -var _ error = &SDKError{} - -func NewSDKError(message string, statusCode int, body string, httpRes *http.Response) *SDKError { - return &SDKError{ - Message: message, - StatusCode: statusCode, - Body: body, - RawResponse: httpRes, - } -} - -func (e *SDKError) Error() string { - body := "" - if len(e.Body) > 0 { - body = fmt.Sprintf("\n%s", e.Body) - } - - return fmt.Sprintf("%s: Status %d%s", e.Message, e.StatusCode, body) -} diff --git a/components/ledger/pkg/client/models/sdkerrors/v2errorresponse.go b/components/ledger/pkg/client/models/sdkerrors/v2errorresponse.go deleted file mode 100644 index d696168625..0000000000 --- a/components/ledger/pkg/client/models/sdkerrors/v2errorresponse.go +++ /dev/null @@ -1,22 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package sdkerrors - -import ( - "encoding/json" - "github.com/formancehq/stack/ledger/client/models/components" -) - -// V2ErrorResponse - Error -type V2ErrorResponse struct { - ErrorCode components.V2ErrorsEnum `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - Details *string `json:"details,omitempty"` -} - -var _ error = &V2ErrorResponse{} - -func (e *V2ErrorResponse) Error() string { - data, _ := json.Marshal(e) - return string(data) -} diff --git a/components/ledger/pkg/client/retry/config.go b/components/ledger/pkg/client/retry/config.go deleted file mode 100644 index c051b0a46f..0000000000 --- a/components/ledger/pkg/client/retry/config.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package retry - -type BackoffStrategy struct { - InitialInterval int - MaxInterval int - Exponent float64 - MaxElapsedTime int -} - -type Config struct { - Strategy string - Backoff *BackoffStrategy - RetryConnectionErrors bool -} diff --git a/components/ledger/pkg/client/types/bigint.go b/components/ledger/pkg/client/types/bigint.go deleted file mode 100644 index 9c6a086d51..0000000000 --- a/components/ledger/pkg/client/types/bigint.go +++ /dev/null @@ -1,21 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package types - -import ( - "fmt" - "math/big" -) - -// MustNewBigIntFromString returns an instance of big.Int from a string -// The string is assumed to be base 10 and if it is not a valid big.Int -// then the function panics. -// Avoid using this function in production code. -func MustNewBigIntFromString(s string) *big.Int { - i, ok := new(big.Int).SetString(s, 10) - if !ok { - panic(fmt.Errorf("failed to parse string as big.Int")) - } - - return i -} diff --git a/components/ledger/pkg/client/types/date.go b/components/ledger/pkg/client/types/date.go deleted file mode 100644 index 5b2782f219..0000000000 --- a/components/ledger/pkg/client/types/date.go +++ /dev/null @@ -1,90 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package types - -import ( - "encoding/json" - "fmt" - "strings" - "time" -) - -// Date is a wrapper around time.Time that allows for JSON marshaling a date string formatted as "2006-01-02". -type Date struct { - time.Time -} - -var ( - _ json.Marshaler = &Date{} - _ json.Unmarshaler = &Date{} - _ fmt.Stringer = &Date{} -) - -// NewDate returns an instance of Date from a time.Time. -func NewDate(t time.Time) *Date { - d := DateFromTime(t) - return &d -} - -// DateFromTime returns a Date from a time.Time. -func DateFromTime(t time.Time) Date { - return Date{t} -} - -// NewDateFromString returns an instance of Date from a string formatted as "2006-01-02". -func NewDateFromString(str string) (*Date, error) { - d, err := DateFromString(str) - if err != nil { - return nil, err - } - - return &d, nil -} - -// DateFromString returns a Date from a string formatted as "2006-01-02". -func DateFromString(str string) (Date, error) { - var d Date - var err error - - d.Time, err = time.Parse("2006-01-02", str) - return d, err -} - -// MustNewDateFromString returns an instance of Date from a string formatted as "2006-01-02" or panics. -// Avoid using this function in production code. -func MustNewDateFromString(str string) *Date { - d := MustDateFromString(str) - return &d -} - -// MustDateFromString returns a Date from a string formatted as "2006-01-02" or panics. -// Avoid using this function in production code. -func MustDateFromString(str string) Date { - d, err := DateFromString(str) - if err != nil { - panic(err) - } - return d -} - -func (d Date) GetTime() time.Time { - return d.Time -} - -func (d Date) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"%s"`, d.Time.Format("2006-01-02"))), nil -} - -func (d *Date) UnmarshalJSON(data []byte) error { - var err error - - str := string(data) - str = strings.Trim(str, `"`) - - d.Time, err = time.Parse("2006-01-02", str) - return err -} - -func (d Date) String() string { - return d.Time.Format("2006-01-02") -} diff --git a/components/ledger/pkg/client/types/datetime.go b/components/ledger/pkg/client/types/datetime.go deleted file mode 100644 index 3eff332daa..0000000000 --- a/components/ledger/pkg/client/types/datetime.go +++ /dev/null @@ -1,23 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package types - -import "time" - -// MustTimeFromString returns a time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. -// Avoid using this function in production code. -func MustTimeFromString(str string) time.Time { - t, err := time.Parse(time.RFC3339, str) - if err != nil { - panic(err) - } - - return t -} - -// MustNewTimeFromString returns an instance of time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. -// Avoid using this function in production code. -func MustNewTimeFromString(str string) *time.Time { - t := MustTimeFromString(str) - return &t -} diff --git a/components/ledger/pkg/client/types/decimal.go b/components/ledger/pkg/client/types/decimal.go deleted file mode 100644 index d8429bc6bd..0000000000 --- a/components/ledger/pkg/client/types/decimal.go +++ /dev/null @@ -1,20 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package types - -import ( - "fmt" - - "github.com/ericlagergren/decimal" -) - -// MustNewDecimalFromString returns an instance of Decimal from a string -// Avoid using this function in production code. -func MustNewDecimalFromString(s string) *decimal.Big { - d, ok := new(decimal.Big).SetString(s) - if !ok { - panic(fmt.Errorf("failed to parse string as decimal.Big")) - } - - return d -} diff --git a/components/ledger/pkg/client/types/pointers.go b/components/ledger/pkg/client/types/pointers.go deleted file mode 100644 index 950d6a3587..0000000000 --- a/components/ledger/pkg/client/types/pointers.go +++ /dev/null @@ -1,10 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package types - -func String(s string) *string { return &s } -func Bool(b bool) *bool { return &b } -func Int(i int) *int { return &i } -func Int64(i int64) *int64 { return &i } -func Float32(f float32) *float32 { return &f } -func Float64(f float64) *float64 { return &f } diff --git a/components/ledger/pkg/client/v1.go b/components/ledger/pkg/client/v1.go deleted file mode 100644 index 64127606d7..0000000000 --- a/components/ledger/pkg/client/v1.go +++ /dev/null @@ -1,3728 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package client - -import ( - "bytes" - "context" - "fmt" - "github.com/cenkalti/backoff/v4" - "github.com/formancehq/stack/ledger/client/internal/hooks" - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/operations" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" - "io" - "math/big" - "net/http" - "net/url" -) - -type V1 struct { - sdkConfiguration sdkConfiguration -} - -func newV1(sdkConfig sdkConfiguration) *V1 { - return &V1{ - sdkConfiguration: sdkConfig, - } -} - -// GetInfo - Show server information -func (s *V1) GetInfo(ctx context.Context, opts ...operations.Option) (*operations.GetInfoResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getInfo", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := url.JoinPath(baseURL, "/_info") - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetInfoResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.ConfigInfoResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.ConfigInfoResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetLedgerInfo - Get information about a ledger -func (s *V1) GetLedgerInfo(ctx context.Context, ledger string, opts ...operations.Option) (*operations.GetLedgerInfoResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getLedgerInfo", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.GetLedgerInfoRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/_info", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetLedgerInfoResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.LedgerInfoResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.LedgerInfoResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CountAccounts - Count the accounts from a ledger -func (s *V1) CountAccounts(ctx context.Context, ledger string, address *string, metadata map[string]any, opts ...operations.Option) (*operations.CountAccountsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "countAccounts", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.CountAccountsRequest{ - Ledger: ledger, - Address: address, - Metadata: metadata, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/accounts", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.CountAccountsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - res.Headers = httpRes.Header - - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListAccounts - List accounts from a ledger -// List accounts from a ledger, sorted by address in descending order. -func (s *V1) ListAccounts(ctx context.Context, request operations.ListAccountsRequest, opts ...operations.Option) (*operations.ListAccountsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "listAccounts", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/accounts", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.ListAccountsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.AccountsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.AccountsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - case httpRes.StatusCode == 404: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.ErrorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetAccount - Get account by its address -func (s *V1) GetAccount(ctx context.Context, ledger string, address string, opts ...operations.Option) (*operations.GetAccountResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getAccount", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.GetAccountRequest{ - Ledger: ledger, - Address: address, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/accounts/{address}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetAccountResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.AccountResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.AccountResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// AddMetadataToAccount - Add metadata to an account -func (s *V1) AddMetadataToAccount(ctx context.Context, ledger string, address string, requestBody map[string]any, opts ...operations.Option) (*operations.AddMetadataToAccountResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "addMetadataToAccount", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.AddMetadataToAccountRequest{ - Ledger: ledger, - Address: address, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/accounts/{address}/metadata", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, true, false, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.AddMetadataToAccountResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetMapping - Get the mapping of a ledger -func (s *V1) GetMapping(ctx context.Context, ledger string, opts ...operations.Option) (*operations.GetMappingResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getMapping", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.GetMappingRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/mapping", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetMappingResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.MappingResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.MappingResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// UpdateMapping - Update the mapping of a ledger -func (s *V1) UpdateMapping(ctx context.Context, ledger string, mapping *components.Mapping, opts ...operations.Option) (*operations.UpdateMappingResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "updateMapping", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.UpdateMappingRequest{ - Ledger: ledger, - Mapping: mapping, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/mapping", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, true, false, "Mapping", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "PUT", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.UpdateMappingResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.MappingResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.MappingResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// RunScript - Execute a Numscript -// This route is deprecated, and has been merged into `POST /{ledger}/transactions`. -// -// Deprecated method: This will be removed in a future release, please migrate away from it as soon as possible. -func (s *V1) RunScript(ctx context.Context, ledger string, script components.Script, preview *bool, opts ...operations.Option) (*operations.RunScriptResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "runScript", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.RunScriptRequest{ - Ledger: ledger, - Preview: preview, - Script: script, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/script", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Script", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.RunScriptResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.ScriptResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.ScriptResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - return nil, sdkerrors.NewSDKError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) - } - - return res, nil - -} - -// ReadStats - Get statistics from a ledger -// Get statistics from a ledger. (aggregate metrics on accounts and transactions) -func (s *V1) ReadStats(ctx context.Context, ledger string, opts ...operations.Option) (*operations.ReadStatsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "readStats", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.ReadStatsRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/stats", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.ReadStatsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.StatsResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.StatsResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CountTransactions - Count the transactions from a ledger -func (s *V1) CountTransactions(ctx context.Context, request operations.CountTransactionsRequest, opts ...operations.Option) (*operations.CountTransactionsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "countTransactions", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.CountTransactionsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - res.Headers = httpRes.Header - - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListTransactions - List transactions from a ledger -// List transactions from a ledger, sorted by txid in descending order. -func (s *V1) ListTransactions(ctx context.Context, request operations.ListTransactionsRequest, opts ...operations.Option) (*operations.ListTransactionsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "listTransactions", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.ListTransactionsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.TransactionsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.TransactionsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CreateTransaction - Create a new transaction to a ledger -func (s *V1) CreateTransaction(ctx context.Context, ledger string, postTransaction components.PostTransaction, preview *bool, opts ...operations.Option) (*operations.CreateTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "createTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.CreateTransactionRequest{ - Ledger: ledger, - Preview: preview, - PostTransaction: postTransaction, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "PostTransaction", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.CreateTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.TransactionsResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.TransactionsResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetTransaction - Get transaction from a ledger by its ID -func (s *V1) GetTransaction(ctx context.Context, ledger string, txid *big.Int, opts ...operations.Option) (*operations.GetTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.GetTransactionRequest{ - Ledger: ledger, - Txid: txid, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions/{txid}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.TransactionResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.TransactionResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// AddMetadataOnTransaction - Set the metadata of a transaction by its ID -func (s *V1) AddMetadataOnTransaction(ctx context.Context, ledger string, txid *big.Int, requestBody map[string]any, opts ...operations.Option) (*operations.AddMetadataOnTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "addMetadataOnTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.AddMetadataOnTransactionRequest{ - Ledger: ledger, - Txid: txid, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions/{txid}/metadata", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, true, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.AddMetadataOnTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// RevertTransaction - Revert a ledger transaction by its ID -func (s *V1) RevertTransaction(ctx context.Context, ledger string, txid *big.Int, disableChecks *bool, opts ...operations.Option) (*operations.RevertTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "revertTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.RevertTransactionRequest{ - Ledger: ledger, - Txid: txid, - DisableChecks: disableChecks, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions/{txid}/revert", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.RevertTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 201: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.TransactionResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.TransactionResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CreateTransactions - Create a new batch of transactions to a ledger -func (s *V1) CreateTransactions(ctx context.Context, ledger string, transactions components.Transactions, opts ...operations.Option) (*operations.CreateTransactionsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "CreateTransactions", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.CreateTransactionsRequest{ - Ledger: ledger, - Transactions: transactions, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/transactions/batch", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "Transactions", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.CreateTransactionsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.TransactionsResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.TransactionsResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetBalances - Get the balances from a ledger's account -func (s *V1) GetBalances(ctx context.Context, request operations.GetBalancesRequest, opts ...operations.Option) (*operations.GetBalancesResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getBalances", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/balances", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetBalancesResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.BalancesCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.BalancesCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetBalancesAggregated - Get the aggregated balances from selected accounts -func (s *V1) GetBalancesAggregated(ctx context.Context, ledger string, address *string, useInsertionDate *bool, opts ...operations.Option) (*operations.GetBalancesAggregatedResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "getBalancesAggregated", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.GetBalancesAggregatedRequest{ - Ledger: ledger, - Address: address, - UseInsertionDate: useInsertionDate, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/aggregate/balances", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.GetBalancesAggregatedResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.AggregateBalancesResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.AggregateBalancesResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListLogs - List the logs from a ledger -// List the logs from a ledger, sorted by ID in descending order. -func (s *V1) ListLogs(ctx context.Context, request operations.ListLogsRequest, opts ...operations.Option) (*operations.ListLogsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "listLogs", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/{ledger}/logs", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.ListLogsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.LogsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.LogsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} diff --git a/components/ledger/pkg/client/v2.go b/components/ledger/pkg/client/v2.go deleted file mode 100644 index 1b6fad03db..0000000000 --- a/components/ledger/pkg/client/v2.go +++ /dev/null @@ -1,4839 +0,0 @@ -// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. - -package client - -import ( - "bytes" - "context" - "fmt" - "github.com/cenkalti/backoff/v4" - "github.com/formancehq/stack/ledger/client/internal/hooks" - "github.com/formancehq/stack/ledger/client/internal/utils" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/formancehq/stack/ledger/client/models/operations" - "github.com/formancehq/stack/ledger/client/models/sdkerrors" - "io" - "math/big" - "net/http" - "net/url" - "time" -) - -type V2 struct { - sdkConfiguration sdkConfiguration -} - -func newV2(sdkConfig sdkConfiguration) *V2 { - return &V2{ - sdkConfiguration: sdkConfig, - } -} - -// GetInfo - Show server information -func (s *V2) GetInfo(ctx context.Context, opts ...operations.Option) (*operations.V2GetInfoResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetInfo", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := url.JoinPath(baseURL, "/v2/_info") - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetInfoResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2ConfigInfoResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2ConfigInfoResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2ErrorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListLedgers - List ledgers -func (s *V2) ListLedgers(ctx context.Context, pageSize *int64, cursor *string, opts ...operations.Option) (*operations.V2ListLedgersResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ListLedgers", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2ListLedgersRequest{ - PageSize: pageSize, - Cursor: cursor, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := url.JoinPath(baseURL, "/v2") - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ListLedgersResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2LedgerListResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2LedgerListResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetLedger - Get a ledger -func (s *V2) GetLedger(ctx context.Context, ledger string, opts ...operations.Option) (*operations.V2GetLedgerResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetLedger", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2GetLedgerRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetLedgerResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2GetLedgerResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2GetLedgerResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CreateLedger - Create a ledger -func (s *V2) CreateLedger(ctx context.Context, ledger string, v2CreateLedgerRequest *components.V2CreateLedgerRequest, opts ...operations.Option) (*operations.V2CreateLedgerResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2CreateLedger", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2CreateLedgerRequest{ - Ledger: ledger, - V2CreateLedgerRequest: v2CreateLedgerRequest, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "V2CreateLedgerRequest", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2CreateLedgerResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// UpdateLedgerMetadata - Update ledger metadata -func (s *V2) UpdateLedgerMetadata(ctx context.Context, ledger string, requestBody map[string]string, opts ...operations.Option) (*operations.V2UpdateLedgerMetadataResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2UpdateLedgerMetadata", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2UpdateLedgerMetadataRequest{ - Ledger: ledger, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/metadata", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "PUT", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2UpdateLedgerMetadataResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - case httpRes.StatusCode >= 500 && httpRes.StatusCode < 600: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2ErrorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// DeleteLedgerMetadata - Delete ledger metadata by key -func (s *V2) DeleteLedgerMetadata(ctx context.Context, ledger string, key string, opts ...operations.Option) (*operations.V2DeleteLedgerMetadataResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2DeleteLedgerMetadata", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2DeleteLedgerMetadataRequest{ - Ledger: ledger, - Key: key, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/metadata/{key}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "DELETE", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2DeleteLedgerMetadataResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetLedgerInfo - Get information about a ledger -func (s *V2) GetLedgerInfo(ctx context.Context, ledger string, opts ...operations.Option) (*operations.V2GetLedgerInfoResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetLedgerInfo", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2GetLedgerInfoRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/_info", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetLedgerInfoResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2LedgerInfoResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2LedgerInfoResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CreateBulk - Bulk request -func (s *V2) CreateBulk(ctx context.Context, ledger string, requestBody []components.V2BulkElement, opts ...operations.Option) (*operations.V2CreateBulkResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2CreateBulk", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2CreateBulkRequest{ - Ledger: ledger, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/_bulk", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2CreateBulkResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - fallthrough - case httpRes.StatusCode == 400: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2BulkResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2BulkResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CountAccounts - Count the accounts from a ledger -func (s *V2) CountAccounts(ctx context.Context, ledger string, pit *time.Time, requestBody map[string]any, opts ...operations.Option) (*operations.V2CountAccountsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2CountAccounts", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2CountAccountsRequest{ - Ledger: ledger, - Pit: pit, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/accounts", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2CountAccountsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - res.Headers = httpRes.Header - - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListAccounts - List accounts from a ledger -// List accounts from a ledger, sorted by address in descending order. -func (s *V2) ListAccounts(ctx context.Context, request operations.V2ListAccountsRequest, opts ...operations.Option) (*operations.V2ListAccountsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ListAccounts", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/accounts", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ListAccountsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2AccountsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2AccountsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetAccount - Get account by its address -func (s *V2) GetAccount(ctx context.Context, ledger string, address string, expand *string, pit *time.Time, opts ...operations.Option) (*operations.V2GetAccountResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetAccount", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2GetAccountRequest{ - Ledger: ledger, - Address: address, - Expand: expand, - Pit: pit, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/accounts/{address}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetAccountResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2AccountResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2AccountResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// AddMetadataToAccount - Add metadata to an account -func (s *V2) AddMetadataToAccount(ctx context.Context, request operations.V2AddMetadataToAccountRequest, opts ...operations.Option) (*operations.V2AddMetadataToAccountResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2AddMetadataToAccount", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/accounts/{address}/metadata", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - utils.PopulateHeaders(ctx, req, request, nil) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2AddMetadataToAccountResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// DeleteAccountMetadata - Delete metadata by key -// Delete metadata by key -func (s *V2) DeleteAccountMetadata(ctx context.Context, ledger string, address string, key string, opts ...operations.Option) (*operations.V2DeleteAccountMetadataResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2DeleteAccountMetadata", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2DeleteAccountMetadataRequest{ - Ledger: ledger, - Address: address, - Key: key, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/accounts/{address}/metadata/{key}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "DELETE", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2DeleteAccountMetadataResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode >= 200 && httpRes.StatusCode < 300: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ReadStats - Get statistics from a ledger -// Get statistics from a ledger. (aggregate metrics on accounts and transactions) -func (s *V2) ReadStats(ctx context.Context, ledger string, opts ...operations.Option) (*operations.V2ReadStatsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ReadStats", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2ReadStatsRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/stats", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ReadStatsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2StatsResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2StatsResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CountTransactions - Count the transactions from a ledger -func (s *V2) CountTransactions(ctx context.Context, ledger string, pit *time.Time, requestBody map[string]any, opts ...operations.Option) (*operations.V2CountTransactionsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2CountTransactions", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2CountTransactionsRequest{ - Ledger: ledger, - Pit: pit, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "HEAD", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2CountTransactionsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - res.Headers = httpRes.Header - - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListTransactions - List transactions from a ledger -// List transactions from a ledger, sorted by id in descending order. -func (s *V2) ListTransactions(ctx context.Context, request operations.V2ListTransactionsRequest, opts ...operations.Option) (*operations.V2ListTransactionsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ListTransactions", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ListTransactionsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2TransactionsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2TransactionsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// CreateTransaction - Create a new transaction to a ledger -func (s *V2) CreateTransaction(ctx context.Context, ledger string, v2PostTransaction components.V2PostTransaction, dryRun *bool, idempotencyKey *string, opts ...operations.Option) (*operations.V2CreateTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2CreateTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2CreateTransactionRequest{ - Ledger: ledger, - DryRun: dryRun, - IdempotencyKey: idempotencyKey, - V2PostTransaction: v2PostTransaction, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, false, "V2PostTransaction", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - utils.PopulateHeaders(ctx, req, request, nil) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2CreateTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2CreateTransactionResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2CreateTransactionResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetTransaction - Get transaction from a ledger by its ID -func (s *V2) GetTransaction(ctx context.Context, ledger string, id *big.Int, expand *string, pit *time.Time, opts ...operations.Option) (*operations.V2GetTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2GetTransactionRequest{ - Ledger: ledger, - ID: id, - Expand: expand, - Pit: pit, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions/{id}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2GetTransactionResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2GetTransactionResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// AddMetadataOnTransaction - Set the metadata of a transaction by its ID -func (s *V2) AddMetadataOnTransaction(ctx context.Context, request operations.V2AddMetadataOnTransactionRequest, opts ...operations.Option) (*operations.V2AddMetadataOnTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2AddMetadataOnTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions/{id}/metadata", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - utils.PopulateHeaders(ctx, req, request, nil) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2AddMetadataOnTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// DeleteTransactionMetadata - Delete metadata by key -// Delete metadata by key -func (s *V2) DeleteTransactionMetadata(ctx context.Context, ledger string, id *big.Int, key string, opts ...operations.Option) (*operations.V2DeleteTransactionMetadataResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2DeleteTransactionMetadata", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2DeleteTransactionMetadataRequest{ - Ledger: ledger, - ID: id, - Key: key, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions/{id}/metadata/{key}", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "DELETE", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2DeleteTransactionMetadataResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode >= 200 && httpRes.StatusCode < 300: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// RevertTransaction - Revert a ledger transaction by its ID -func (s *V2) RevertTransaction(ctx context.Context, ledger string, id *big.Int, force *bool, atEffectiveDate *bool, opts ...operations.Option) (*operations.V2RevertTransactionResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2RevertTransaction", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2RevertTransactionRequest{ - Ledger: ledger, - ID: id, - Force: force, - AtEffectiveDate: atEffectiveDate, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/transactions/{id}/revert", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2RevertTransactionResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 201: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2RevertTransactionResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2RevertTransactionResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetBalancesAggregated - Get the aggregated balances from selected accounts -func (s *V2) GetBalancesAggregated(ctx context.Context, ledger string, pit *time.Time, useInsertionDate *bool, requestBody map[string]any, opts ...operations.Option) (*operations.V2GetBalancesAggregatedResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetBalancesAggregated", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2GetBalancesAggregatedRequest{ - Ledger: ledger, - Pit: pit, - UseInsertionDate: useInsertionDate, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/aggregate/balances", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetBalancesAggregatedResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2AggregateBalancesResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2AggregateBalancesResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// GetVolumesWithBalances - Get list of volumes with balances for (account/asset) -func (s *V2) GetVolumesWithBalances(ctx context.Context, request operations.V2GetVolumesWithBalancesRequest, opts ...operations.Option) (*operations.V2GetVolumesWithBalancesResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2GetVolumesWithBalances", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/volumes", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2GetVolumesWithBalancesResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2VolumesWithBalanceCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2VolumesWithBalanceCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ListLogs - List the logs from a ledger -// List the logs from a ledger, sorted by ID in descending order. -func (s *V2) ListLogs(ctx context.Context, request operations.V2ListLogsRequest, opts ...operations.Option) (*operations.V2ListLogsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ListLogs", - OAuth2Scopes: []string{"ledger:read", "ledger:read"}, - SecuritySource: s.sdkConfiguration.Security, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/logs", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "json", `request:"mediaType=application/json"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "GET", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { - return nil, fmt.Errorf("error populating query params: %w", err) - } - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ListLogsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out components.V2LogsCursorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - res.V2LogsCursorResponse = &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -func (s *V2) ImportLogs(ctx context.Context, ledger string, requestBody *string, opts ...operations.Option) (*operations.V2ImportLogsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ImportLogs", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2ImportLogsRequest{ - Ledger: ledger, - RequestBody: requestBody, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/logs/import", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "RequestBody", "string", `request:"mediaType=application/octet-stream"`) - if err != nil { - return nil, err - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, bodyReader) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - req.Header.Set("Content-Type", reqContentType) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ImportLogsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 204: - default: - switch { - case utils.MatchContentType(httpRes.Header.Get("Content-Type"), `application/json`): - var out sdkerrors.V2ErrorResponse - if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { - return nil, err - } - - return nil, &out - default: - return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", httpRes.Header.Get("Content-Type")), httpRes.StatusCode, string(rawBody), httpRes) - } - } - - return res, nil - -} - -// ExportLogs - Export logs -func (s *V2) ExportLogs(ctx context.Context, ledger string, opts ...operations.Option) (*operations.V2ExportLogsResponse, error) { - hookCtx := hooks.HookContext{ - Context: ctx, - OperationID: "v2ExportLogs", - OAuth2Scopes: []string{"ledger:read", "ledger:write"}, - SecuritySource: s.sdkConfiguration.Security, - } - - request := operations.V2ExportLogsRequest{ - Ledger: ledger, - } - - o := operations.Options{} - supportedOptions := []string{ - operations.SupportedOptionRetries, - operations.SupportedOptionTimeout, - } - - for _, opt := range opts { - if err := opt(&o, supportedOptions...); err != nil { - return nil, fmt.Errorf("error applying option: %w", err) - } - } - - baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) - opURL, err := utils.GenerateURL(ctx, baseURL, "/v2/{ledger}/logs/export", request, nil) - if err != nil { - return nil, fmt.Errorf("error generating URL: %w", err) - } - - timeout := o.Timeout - if timeout == nil { - timeout = s.sdkConfiguration.Timeout - } - - if timeout != nil { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) - defer cancel() - } - - req, err := http.NewRequestWithContext(ctx, "POST", opURL, nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %w", err) - } - req.Header.Set("Accept", "*/*") - req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent) - - if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil { - return nil, err - } - - globalRetryConfig := s.sdkConfiguration.RetryConfig - retryConfig := o.Retries - if retryConfig == nil { - if globalRetryConfig != nil { - retryConfig = globalRetryConfig - } - } - - var httpRes *http.Response - if retryConfig != nil { - httpRes, err = utils.Retry(ctx, utils.Retries{ - Config: retryConfig, - StatusCodes: []string{ - "429", - "500", - "502", - "503", - "504", - }, - }, func() (*http.Response, error) { - if req.Body != nil { - copyBody, err := req.GetBody() - if err != nil { - return nil, err - } - req.Body = copyBody - } - - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, backoff.Permanent(err) - } - - httpRes, err := s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - } - return httpRes, err - }) - - if err != nil { - return nil, err - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } else { - req, err = s.sdkConfiguration.Hooks.BeforeRequest(hooks.BeforeRequestContext{HookContext: hookCtx}, req) - if err != nil { - return nil, err - } - - httpRes, err = s.sdkConfiguration.Client.Do(req) - if err != nil || httpRes == nil { - if err != nil { - err = fmt.Errorf("error sending request: %w", err) - } else { - err = fmt.Errorf("error sending request: no response") - } - - _, err = s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, nil, err) - return nil, err - } else if utils.MatchStatusCodes([]string{"default"}, httpRes.StatusCode) { - _httpRes, err := s.sdkConfiguration.Hooks.AfterError(hooks.AfterErrorContext{HookContext: hookCtx}, httpRes, nil) - if err != nil { - return nil, err - } else if _httpRes != nil { - httpRes = _httpRes - } - } else { - httpRes, err = s.sdkConfiguration.Hooks.AfterSuccess(hooks.AfterSuccessContext{HookContext: hookCtx}, httpRes) - if err != nil { - return nil, err - } - } - } - - res := &operations.V2ExportLogsResponse{ - HTTPMeta: components.HTTPMetadata{ - Request: req, - Response: httpRes, - }, - } - - rawBody, err := io.ReadAll(httpRes.Body) - if err != nil { - return nil, fmt.Errorf("error reading response body: %w", err) - } - httpRes.Body.Close() - httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) - - switch { - case httpRes.StatusCode == 200: - default: - return nil, sdkerrors.NewSDKError("API error occurred", httpRes.StatusCode, string(rawBody), httpRes) - } - - return res, nil - -} diff --git a/components/ledger/pkg/core/accounts/account_test.go b/components/ledger/pkg/core/accounts/account_test.go deleted file mode 100644 index c9122275be..0000000000 --- a/components/ledger/pkg/core/accounts/account_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package accounts - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestValidateAddress(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - address string - shouldBeOk bool - } - - testsCases := []testCase{ - { - name: "nominal", - address: "foo:bar", - shouldBeOk: true, - }, - { - name: "short segment", - address: "a:b", - shouldBeOk: true, - }, - { - name: "only one segment", - address: "a", - shouldBeOk: true, - }, - { - name: "using underscore as first char", - address: "_a", - shouldBeOk: true, - }, - { - name: "using digits", - address: "_0123", - shouldBeOk: true, - }, - { - name: "using empty segment", - address: "a:", - shouldBeOk: false, - }, - { - name: "using single dash", - address: "-", - shouldBeOk: true, - }, - { - name: "using dash without alphanum before", - address: "-toto", - shouldBeOk: true, - }, - { - name: "using dash without alphanum after", - address: "toto-", - shouldBeOk: true, - }, - { - name: "using dash", - address: "toto-titi", - shouldBeOk: true, - }, - { - name: "using dash multi segment", - address: "toto-titi:tata-tutu", - shouldBeOk: true, - }, - { - name: "using multiple dashes", - address: "toto----titi", - shouldBeOk: true, - }, - { - name: "using multiple dashes 2", - address: "-toto----titi-", - shouldBeOk: true, - }, - } - - for _, testCase := range testsCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - require.Equal(t, testCase.shouldBeOk, ValidateAddress(testCase.address)) - }) - } -} diff --git a/components/ledger/pkg/core/accounts/accounts.go b/components/ledger/pkg/core/accounts/accounts.go deleted file mode 100644 index cb712c1cfb..0000000000 --- a/components/ledger/pkg/core/accounts/accounts.go +++ /dev/null @@ -1,12 +0,0 @@ -package accounts - -import "regexp" - -const SegmentRegex = "[a-zA-Z0-9_-]+" -const Pattern = "^" + SegmentRegex + "(:" + SegmentRegex + ")*$" - -var Regexp = regexp.MustCompile(Pattern) - -func ValidateAddress(addr string) bool { - return Regexp.Match([]byte(addr)) -} diff --git a/components/ledger/pkg/core/assets/asset.go b/components/ledger/pkg/core/assets/asset.go deleted file mode 100644 index 7559ea59de..0000000000 --- a/components/ledger/pkg/core/assets/asset.go +++ /dev/null @@ -1,13 +0,0 @@ -package assets - -import ( - "regexp" -) - -const Pattern = `[A-Z][A-Z0-9]{0,16}(\/\d{1,6})?` - -var Regexp = regexp.MustCompile("^" + Pattern + "$") - -func IsValid(v string) bool { - return Regexp.Match([]byte(v)) -} diff --git a/components/ledger/pkg/events/events.go b/components/ledger/pkg/events/events.go deleted file mode 100644 index f6d6f55c64..0000000000 --- a/components/ledger/pkg/events/events.go +++ /dev/null @@ -1,11 +0,0 @@ -package events - -const ( - EventVersion = "v2" - EventApp = "ledger" - - EventTypeCommittedTransactions = "COMMITTED_TRANSACTIONS" - EventTypeSavedMetadata = "SAVED_METADATA" - EventTypeRevertedTransaction = "REVERTED_TRANSACTION" - EventTypeDeletedMetadata = "DELETED_METADATA" -) diff --git a/components/ledger/pkg/testserver/helpers.go b/components/ledger/pkg/testserver/helpers.go deleted file mode 100644 index 154165d532..0000000000 --- a/components/ledger/pkg/testserver/helpers.go +++ /dev/null @@ -1,15 +0,0 @@ -package testserver - -import ( - . "github.com/formancehq/go-libs/testing/utils" - . "github.com/onsi/ginkgo/v2" -) - -func UseNewTestServer(configurationProvider func() Configuration) *Deferred[*Server] { - d := NewDeferred[*Server]() - BeforeEach(func() { - d.Reset() - d.SetValue(New(GinkgoT(), configurationProvider())) - }) - return d -} diff --git a/components/ledger/pkg/testserver/server.go b/components/ledger/pkg/testserver/server.go deleted file mode 100644 index 736f88c527..0000000000 --- a/components/ledger/pkg/testserver/server.go +++ /dev/null @@ -1,174 +0,0 @@ -package testserver - -import ( - "context" - "fmt" - "io" - "net/http" - "os" - "strings" - "time" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/httpclient" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/ledger/cmd" - ledgerclient "github.com/formancehq/stack/ledger/client" - "github.com/stretchr/testify/require" -) - -type T interface { - require.TestingT - TempDir() string - Cleanup(func()) - Helper() - Logf(format string, args ...any) -} - -type Configuration struct { - PostgresConfiguration bunconnect.ConnectionOptions - Output io.Writer - Debug bool -} - -type Server struct { - configuration Configuration - t T - httpClient *ledgerclient.Formance - cancel func() - ctx context.Context - errorChan chan error -} - -func (s *Server) Start() { - s.t.Helper() - - tmpDir := s.t.TempDir() - require.NoError(s.t, os.MkdirAll(tmpDir, 0700)) - s.t.Cleanup(func() { - _ = os.RemoveAll(tmpDir) - }) - - rootCmd := cmd.NewRootCommand() - args := []string{ - "serve", - "--" + cmd.BindFlag, ":0", - "--" + bunconnect.PostgresURIFlag, s.configuration.PostgresConfiguration.DatabaseSourceName, - "--" + bunconnect.PostgresMaxOpenConnsFlag, fmt.Sprint(s.configuration.PostgresConfiguration.MaxOpenConns), - "--" + bunconnect.PostgresConnMaxIdleTimeFlag, fmt.Sprint(s.configuration.PostgresConfiguration.ConnMaxIdleTime), - } - if s.configuration.PostgresConfiguration.MaxIdleConns != 0 { - args = append( - args, - "--"+bunconnect.PostgresMaxIdleConnsFlag, - fmt.Sprint(s.configuration.PostgresConfiguration.MaxIdleConns), - ) - } - if s.configuration.PostgresConfiguration.MaxOpenConns != 0 { - args = append( - args, - "--"+bunconnect.PostgresMaxOpenConnsFlag, - fmt.Sprint(s.configuration.PostgresConfiguration.MaxOpenConns), - ) - } - if s.configuration.PostgresConfiguration.ConnMaxIdleTime != 0 { - args = append( - args, - "--"+bunconnect.PostgresConnMaxIdleTimeFlag, - fmt.Sprint(s.configuration.PostgresConfiguration.ConnMaxIdleTime), - ) - } - if s.configuration.Debug { - args = append(args, "--"+service.DebugFlag) - } - - s.t.Logf("Starting application with flags: %s", strings.Join(args, " ")) - rootCmd.SetArgs(args) - rootCmd.SilenceErrors = true - output := s.configuration.Output - if output == nil { - output = io.Discard - } - rootCmd.SetOut(output) - rootCmd.SetErr(output) - - s.ctx = logging.TestingContext() - s.ctx, s.cancel = context.WithCancel(s.ctx) - s.ctx = service.ContextWithLifecycle(s.ctx) - s.ctx = httpserver.ContextWithServerInfo(s.ctx) - - s.errorChan = make(chan error, 1) - go func() { - s.errorChan <- rootCmd.ExecuteContext(s.ctx) - }() - - select { - case <-service.Ready(s.ctx): - case err := <-s.errorChan: - if err != nil { - require.NoError(s.t, err) - } else { - require.Fail(s.t, "unexpected service stop") - } - } - - s.httpClient = ledgerclient.New( - ledgerclient.WithServerURL(httpserver.URL(s.ctx)), - ledgerclient.WithClient(&http.Client{ - Transport: httpclient.NewDebugHTTPTransport(http.DefaultTransport), - }), - ) -} - -func (s *Server) Stop() { - s.t.Helper() - - if s.cancel == nil { - return - } - s.cancel() - s.cancel = nil - - // Wait app to be marked as stopped - select { - case <-service.Stopped(s.ctx): - case <-time.After(5 * time.Second): - require.Fail(s.t, "service should have been stopped") - } - - // Ensure the app has been properly shutdown - select { - case err := <-s.errorChan: - require.NoError(s.t, err) - case <-time.After(5 * time.Second): - require.Fail(s.t, "service should have been stopped without error") - } -} - -func (s *Server) Client() *ledgerclient.Formance { - return s.httpClient -} - -func (s *Server) Restart() { - s.t.Helper() - - s.Stop() - s.Start() -} - -func New(t T, configuration Configuration) *Server { - srv := &Server{ - t: t, - configuration: configuration, - } - t.Logf("Start testing server") - srv.Start() - t.Cleanup(func() { - t.Logf("Stop testing server") - srv.Stop() - }) - - return srv -} diff --git a/components/ledger/scratch.Dockerfile b/components/ledger/scratch.Dockerfile deleted file mode 100644 index e93ad9b95d..0000000000 --- a/components/ledger/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY ledger /usr/bin/ledger -ENV OTEL_SERVICE_NAME ledger -ENTRYPOINT ["/usr/bin/ledger"] -CMD ["serve"] diff --git a/components/ledger/sdkconfig.yaml b/components/ledger/sdkconfig.yaml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/ledger/test/integration/environment_test.go b/components/ledger/test/integration/environment_test.go deleted file mode 100644 index 8a0c463b96..0000000000 --- a/components/ledger/test/integration/environment_test.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build it - -package test_suite - -import ( - "encoding/json" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/docker" - . "github.com/formancehq/go-libs/testing/platform/pgtesting" - . "github.com/formancehq/go-libs/testing/utils" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "os" -) - -var ( - dockerPool = NewDeferred[*docker.Pool]() - pgServer = NewDeferred[*PostgresServer]() - debug = os.Getenv("DEBUG") == "true" - logger = logging.NewDefaultLogger(GinkgoWriter, debug, false) -) - -type ParallelExecutionContext struct { - PostgresServer *PostgresServer -} - -var _ = SynchronizedBeforeSuite(func() []byte { - By("Initializing docker pool") - dockerPool.SetValue(docker.NewPool(GinkgoT(), logger)) - - pgServer.LoadAsync(func() *PostgresServer { - By("Initializing postgres server") - return CreatePostgresServer(GinkgoT(), dockerPool.GetValue()) - }) - - By("Waiting services alive") - Wait(pgServer) - By("All services ready.") - - data, err := json.Marshal(ParallelExecutionContext{ - PostgresServer: pgServer.GetValue(), - }) - Expect(err).To(BeNil()) - - return data -}, func(data []byte) { - select { - case <-pgServer.Done(): - // Process #1, setup is terminated - return - default: - } - pec := ParallelExecutionContext{} - err := json.Unmarshal(data, &pec) - Expect(err).To(BeNil()) - - pgServer.SetValue(pec.PostgresServer) -}) diff --git a/components/ledger/test/integration/scenario_test.go b/components/ledger/test/integration/scenario_test.go deleted file mode 100644 index 4492c07812..0000000000 --- a/components/ledger/test/integration/scenario_test.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build it - -package test_suite - -import ( - "github.com/formancehq/go-libs/logging" - . "github.com/formancehq/go-libs/testing/platform/pgtesting" - . "github.com/formancehq/ledger/pkg/testserver" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Context("Ledger integration tests", func() { - var ( - db = UsePostgresDatabase(pgServer) - ctx = logging.TestingContext() - ) - - testServer := UseNewTestServer(func() Configuration { - return Configuration{ - PostgresConfiguration: db.GetValue().ConnectionOptions(), - Output: GinkgoWriter, - Debug: debug, - } - }) - When("Starting the ledger", func() { - It("Should be ok", func() { - info, err := testServer.GetValue().Client().Ledger.V2.GetInfo(ctx) - Expect(err).NotTo(HaveOccurred()) - Expect(info.V2ConfigInfoResponse.Version).To(Equal("develop")) - }) - }) -}) diff --git a/components/ledger/test/integration/suite_test.go b/components/ledger/test/integration/suite_test.go deleted file mode 100644 index 8f527c415d..0000000000 --- a/components/ledger/test/integration/suite_test.go +++ /dev/null @@ -1,14 +0,0 @@ -//go:build it - -package test_suite - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "testing" -) - -func Test(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Test Suite") -} diff --git a/components/ledger/test/performance/performance_test.go b/components/ledger/test/performance/performance_test.go deleted file mode 100644 index ad8aff9fe2..0000000000 --- a/components/ledger/test/performance/performance_test.go +++ /dev/null @@ -1,116 +0,0 @@ -//go:build it - -package benchmarks - -import ( - "bytes" - "fmt" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - "github.com/formancehq/go-libs/testing/utils" - "github.com/formancehq/go-libs/time" - "github.com/formancehq/ledger/pkg/testserver" - "github.com/formancehq/stack/ledger/client/models/components" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "math/big" - "runtime" - "sync" - "sync/atomic" - "testing" -) - -var ( - dockerPool *docker.Pool - srv *pgtesting.PostgresServer -) - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - dockerPool = docker.NewPool(t, logging.Testing()) - srv = pgtesting.CreatePostgresServer(t, dockerPool) - - return m.Run() - }) -} - -func BenchmarkWorstCase(b *testing.B) { - - db := srv.NewDatabase(b) - - ctx := logging.TestingContext() - - ledgerName := uuid.NewString() - testServer := testserver.New(b, testserver.Configuration{ - PostgresConfiguration: db.ConnectionOptions(), - Debug: testing.Verbose(), - }) - testServer.Start() - defer testServer.Stop() - - _, err := testServer.Client().Ledger.V2.CreateLedger(ctx, ledgerName, &components.V2CreateLedgerRequest{}) - require.NoError(b, err) - - totalDuration := atomic.Int64{} - b.SetParallelism(1000) - runtime.GC() - b.ResetTimer() - startOfBench := time.Now() - counter := atomic.Int64{} - longestTxLock := sync.Mutex{} - longestTransactionID := big.NewInt(0) - longestTransactionDuration := time.Duration(0) - b.RunParallel(func(pb *testing.PB) { - buf := bytes.NewBufferString("") - for pb.Next() { - buf.Reset() - id := counter.Add(1) - now := time.Now() - - // todo: check why the generated sdk does not have the same signature as the global sdk - transactionResponse, err := testServer.Client().Ledger.V2.CreateTransaction(ctx, ledgerName, components.V2PostTransaction{ - Timestamp: nil, - Postings: nil, - Script: &components.V2PostTransactionScript{ - Plain: `vars { - account $account -} - -send [USD/2 100] ( - source = @world - destination = $account -)`, - Vars: map[string]any{ - "account": fmt.Sprintf("accounts:%d", id), - }, - }, - - Reference: nil, - Metadata: nil, - }, pointer.For(false), nil) - if err != nil { - return - } - require.NoError(b, err) - - latency := time.Since(now).Milliseconds() - totalDuration.Add(latency) - - longestTxLock.Lock() - if time.Millisecond*time.Duration(latency) > longestTransactionDuration { - longestTransactionID = transactionResponse.V2CreateTransactionResponse.Data.ID - longestTransactionDuration = time.Duration(latency) * time.Millisecond - } - longestTxLock.Unlock() - } - }) - - b.StopTimer() - b.Logf("Longest transaction: %d (%s)", longestTransactionID, longestTransactionDuration.String()) - b.ReportMetric((float64(time.Duration(b.N))/float64(time.Since(startOfBench)))*float64(time.Second), "t/s") - b.ReportMetric(float64(totalDuration.Load()/int64(b.N)), "ms/transaction") - - runtime.GC() -} diff --git a/components/operator/Earthfile b/components/operator/Earthfile index 3d681e776d..da6a268686 100644 --- a/components/operator/Earthfile +++ b/components/operator/Earthfile @@ -9,7 +9,6 @@ FROM core+base-image sources: FROM core+builder-image WORKDIR /src - COPY (stack+sources/out --LOCATION=ee/search) ee/search WORKDIR /src/components/operator COPY --dir api internal pkg cmd . COPY go.* . diff --git a/components/operator/go.mod b/components/operator/go.mod index 5dd704e874..102768c807 100644 --- a/components/operator/go.mod +++ b/components/operator/go.mod @@ -82,4 +82,4 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/formancehq/search => ../../ee/search +replace github.com/formancehq/search => github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd diff --git a/components/operator/go.sum b/components/operator/go.sum index d80959b870..d95bc7e5a4 100644 --- a/components/operator/go.sum +++ b/components/operator/go.sum @@ -15,6 +15,8 @@ github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1 github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= +github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd h1:zsv//syEUoJWo2VswR9vXvrbDAAoxm8rLXkCxYl114s= +github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd/go.mod h1:vmUx4wanfOqGBZk0t2bhAXx+r+Pp9WaIA1UQQNc9Zds= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= diff --git a/components/payments/.gitignore b/components/payments/.gitignore deleted file mode 100644 index 0f1636bb23..0000000000 --- a/components/payments/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -.idea -vendor -.cloud/ressources/.terraform -.cloud/ressources/.terraform.lock.hcl -/.cloud/helm/charts/ -coverage.out -dist/ -.env -payments diff --git a/components/payments/.goreleaser.yml b/components/payments/.goreleaser.yml deleted file mode 100644 index d62326ad53..0000000000 --- a/components/payments/.goreleaser.yml +++ /dev/null @@ -1,38 +0,0 @@ -project_name: payments -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: payments - id: payments - ldflags: - - -X github.com/formancehq/payments/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/payments/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/payments/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - payments - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) \ No newline at end of file diff --git a/components/payments/Earthfile b/components/payments/Earthfile deleted file mode 100644 index 49ad37a620..0000000000 --- a/components/payments/Earthfile +++ /dev/null @@ -1,99 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT ../../releases AS releases -IMPORT .. AS components - -FROM core+base-image - -sources: - WORKDIR src - COPY --pass-args (releases+sdk-generate/go) /src/releases/sdks/go - WORKDIR /src/components/payments - COPY go.* . - COPY --dir pkg cmd internal . - COPY main.go . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/components/payments - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/payments"] - CMD ["serve"] - COPY (+compile/main) /bin/payments - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=payments --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/components/payments - WITH DOCKER --pull=postgres:15-alpine - DO --pass-args core+GO_TESTS - END - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"payments\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=payments - -lint: - FROM core+builder-image - COPY (+sources/*) /src - COPY --pass-args +tidy/go.* . - WORKDIR /src/components/payments - DO --pass-args stack+GO_LINT - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT internal AS LOCAL internal - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT main.go AS LOCAL main.go - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - -openapi: - COPY ./openapi.yaml . - SAVE ARTIFACT ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/components/payments - DO --pass-args stack+GO_TIDY - -generate-generic-connector-client: - FROM openapitools/openapi-generator-cli:v6.6.0 - WORKDIR /src - COPY cmd/connectors/internal/connectors/generic/client/generic-openapi.yaml . - RUN docker-entrypoint.sh generate \ - -i ./generic-openapi.yaml \ - -g go \ - -o ./generated \ - --git-user-id=formancehq \ - --git-repo-id=payments \ - -p packageVersion=latest \ - -p isGoSubmodule=true \ - -p packageName=genericclient - RUN rm -rf ./generated/test - SAVE ARTIFACT ./generated AS LOCAL ./cmd/connectors/internal/connectors/generic/client/generated - -release: - BUILD --pass-args stack+goreleaser --path=components/payments \ No newline at end of file diff --git a/components/payments/README.md b/components/payments/README.md deleted file mode 100644 index 1911448206..0000000000 --- a/components/payments/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Formance Payments [![test](https://github.com/formancehq/payments/actions/workflows/main.yml/badge.svg)](https://github.com/formancehq/payments/actions/workflows/main.yml) [![goreportcard](https://goreportcard.com/badge/github.com/formancehq/payments)](https://goreportcard.com/report/github.com/formancehq/payments) [![discord](https://img.shields.io/discord/846686859869814784?label=chat%20@%20discord)](https://discord.gg/xyHvcbzk4w) - -# Getting started - -Payments works as a standalone binary, the latest of which can be downloaded from the [releases page](https://github.com/formancehq/payments/releases). You can move the binary to any executable path, such as to `/usr/local/bin`. Installations using brew, apt, yum or docker are also [available](https://docs.formance.com/oss/payments/get-started/installation). - -```SHELL -payments -``` - -# What is it? - -Basically, a framework. - -A framework to ingest payin and payout coming from different payment providers (PSP). - -The framework contains connectors. Each connector is basically a translator for a PSP. -Translator, because the main role of a connector is to translate specific PSP payin/payout formats to a generalized format used at Formance. - -Because it is a framework, it is extensible. Please follow the guide below if you want to add your connector. - -# Contribute - -Please follow [this guide](./docs/development.md) if you want to contribute. - -![Frame 1 (2)](https://user-images.githubusercontent.com/1770991/134163361-d86c5728-6075-4510-8de7-06df1f6ed740.png) diff --git a/components/payments/build.Dockerfile b/components/payments/build.Dockerfile deleted file mode 100644 index 28730968fc..0000000000 --- a/components/payments/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY payments /usr/bin/payments -ENV OTEL_SERVICE_NAME payments -ENTRYPOINT ["/usr/bin/payments"] -CMD ["server"] diff --git a/components/payments/cmd/api/internal/api/accounts.go b/components/payments/cmd/api/internal/api/accounts.go deleted file mode 100644 index b2e9468ab3..0000000000 --- a/components/payments/cmd/api/internal/api/accounts.go +++ /dev/null @@ -1,245 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" -) - -type accountResponse struct { - ID string `json:"id"` - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` - DefaultCurrency string `json:"defaultCurrency"` // Deprecated: should be removed soon - DefaultAsset string `json:"defaultAsset"` - AccountName string `json:"accountName"` - Type string `json:"type"` - Metadata map[string]string `json:"metadata"` - Pools []uuid.UUID `json:"pools"` - Raw interface{} `json:"raw"` -} - -func createAccountHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "createAccountHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - var req service.CreateAccountRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes( - attribute.String("request.reference", req.Reference), - attribute.String("request.type", req.Type), - attribute.String("request.connectorID", req.ConnectorID), - attribute.String("request.createdAt", req.CreatedAt.String()), - attribute.String("request.accountName", req.AccountName), - attribute.String("request.defaultAsset", req.DefaultAsset), - ) - - if err := req.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - account, err := b.GetService().CreateAccount(ctx, &req) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := &accountResponse{ - ID: account.ID.String(), - Reference: account.Reference, - CreatedAt: account.CreatedAt, - ConnectorID: account.ConnectorID.String(), - Provider: account.ConnectorID.Provider.String(), - DefaultCurrency: account.DefaultAsset.String(), - DefaultAsset: account.DefaultAsset.String(), - AccountName: account.AccountName, - Type: account.Type.String(), - Raw: account.RawData, - Pools: make([]uuid.UUID, 0), - } - - if account.Metadata != nil { - metadata := make(map[string]string) - for k, v := range account.Metadata { - metadata[k] = v - } - data.Metadata = metadata - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[accountResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func listAccountsHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listAccountsHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - query, err := bunpaginate.Extract[storage.ListAccountsQuery](r, func() (*storage.ListAccountsQuery, error) { - options, err := getPagination(r, storage.AccountQuery{}) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListAccountsQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := b.GetService().ListAccounts(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*accountResponse, len(ret)) - - for i := range ret { - accountType := ret[i].Type - if accountType == models.AccountTypeExternalFormance { - accountType = models.AccountTypeExternal - } - - data[i] = &accountResponse{ - ID: ret[i].ID.String(), - Reference: ret[i].Reference, - CreatedAt: ret[i].CreatedAt, - ConnectorID: ret[i].ConnectorID.String(), - Provider: ret[i].ConnectorID.Provider.String(), - DefaultCurrency: ret[i].DefaultAsset.String(), - DefaultAsset: ret[i].DefaultAsset.String(), - AccountName: ret[i].AccountName, - Type: accountType.String(), - Raw: ret[i].RawData, - } - - if ret[i].Metadata != nil { - metadata := make(map[string]string) - for k, v := range ret[i].Metadata { - metadata[k] = v - } - data[i].Metadata = metadata - } - - data[i].Pools = make([]uuid.UUID, len(ret[i].PoolAccounts)) - for j := range ret[i].PoolAccounts { - data[i].Pools[j] = ret[i].PoolAccounts[j].PoolID - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*accountResponse]{ - Cursor: &bunpaginate.Cursor[*accountResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func readAccountHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readAccountHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - accountID := mux.Vars(r)["accountID"] - - span.SetAttributes(attribute.String("request.accountID", accountID)) - - account, err := b.GetService().GetAccount(ctx, accountID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - accountType := account.Type - if accountType == models.AccountTypeExternalFormance { - accountType = models.AccountTypeExternal - } - - data := &accountResponse{ - ID: account.ID.String(), - Reference: account.Reference, - CreatedAt: account.CreatedAt, - ConnectorID: account.ConnectorID.String(), - Provider: account.ConnectorID.Provider.String(), - DefaultCurrency: account.DefaultAsset.String(), - DefaultAsset: account.DefaultAsset.String(), - AccountName: account.AccountName, - Type: accountType.String(), - Raw: account.RawData, - } - - if account.Metadata != nil { - metadata := make(map[string]string) - for k, v := range account.Metadata { - metadata[k] = v - } - data.Metadata = metadata - } - - data.Pools = make([]uuid.UUID, len(account.PoolAccounts)) - for j := range account.PoolAccounts { - data.Pools[j] = account.PoolAccounts[j].PoolID - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[accountResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - } -} diff --git a/components/payments/cmd/api/internal/api/accounts_test.go b/components/payments/cmd/api/internal/api/accounts_test.go deleted file mode 100644 index ed1ccf4089..0000000000 --- a/components/payments/cmd/api/internal/api/accounts_test.go +++ /dev/null @@ -1,749 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestCreateAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.CreateAccountRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - { - name: "nomimal", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "no default asset, but should still pass", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "missing reference", - req: &service.CreateAccountRequest{ - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing connectorID", - req: &service.CreateAccountRequest{ - Reference: "test", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing createdAt", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "createdAt zero", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Time{}, - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing accountName", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing type", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid type", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "unknown", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - req: &service.CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - createAccountResponse := &models.Account{ - ID: models.AccountID{ - Reference: testCase.req.Reference, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: testCase.req.CreatedAt, - Reference: testCase.req.Reference, - DefaultAsset: models.Asset(testCase.req.DefaultAsset), - AccountName: testCase.req.AccountName, - Type: models.AccountType(testCase.req.Type), - Metadata: map[string]string{ - "foo": "bar", - }, - PoolAccounts: make([]*models.PoolAccounts, 0), - } - - expectedCreateAccountResponse := &accountResponse{ - ID: createAccountResponse.ID.String(), - Reference: createAccountResponse.Reference, - CreatedAt: createAccountResponse.CreatedAt, - ConnectorID: createAccountResponse.ConnectorID.String(), - Provider: createAccountResponse.ConnectorID.Provider.String(), - DefaultCurrency: createAccountResponse.DefaultAsset.String(), - DefaultAsset: createAccountResponse.DefaultAsset.String(), - AccountName: createAccountResponse.AccountName, - Type: createAccountResponse.Type.String(), - Metadata: map[string]string{ - "foo": "bar", - }, - Pools: make([]uuid.UUID, 0), - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - CreateAccount(gomock.Any(), testCase.req). - Return(createAccountResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - CreateAccount(gomock.Any(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, "/accounts", bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[accountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedCreateAccountResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestListAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - pageSize int - expectedQuery storage.ListAccountsQuery - expectedStatusCode int - serviceError error - expectedErrorCode string - } - - testCases := []testCase{ - { - name: "nomimal", - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(100), - ), - pageSize: 100, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"source_account_id\": \"acc1\"}}"}, - }, - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15). - WithQueryBuilder(query.Match("source_account_id", "acc1")), - ), - pageSize: 15, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:asc"}, - }, - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15). - WithSorter(storage.Sorter{}.Add("source_account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - expectedQuery: storage.NewListAccountsQuery( - storage.NewPaginatedQueryOptions(storage.AccountQuery{}). - WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - accounts := []models.Account{ - { - ID: models.AccountID{Reference: "acc1", ConnectorID: models.ConnectorID{Reference: uuid.New(), Provider: models.ConnectorProviderDummyPay}}, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "acc1", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - }, - }, - { - ID: models.AccountID{Reference: "acc2", ConnectorID: models.ConnectorID{Reference: uuid.New(), Provider: models.ConnectorProviderDummyPay}}, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "acc2", - Type: models.AccountTypeExternalFormance, - }, - } - - listAccountsResponse := &bunpaginate.Cursor[models.Account]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: accounts, - } - - expectedAccountsResponse := []*accountResponse{ - { - ID: accounts[0].ID.String(), - Reference: accounts[0].Reference, - CreatedAt: accounts[0].CreatedAt, - ConnectorID: accounts[0].ConnectorID.String(), - Provider: accounts[0].ConnectorID.Provider.String(), - DefaultCurrency: accounts[0].DefaultAsset.String(), - DefaultAsset: accounts[0].DefaultAsset.String(), - AccountName: accounts[0].AccountName, - Type: accounts[0].Type.String(), - Pools: []uuid.UUID{}, - Metadata: accounts[0].Metadata, - }, - { - ID: accounts[1].ID.String(), - Reference: accounts[1].Reference, - CreatedAt: accounts[1].CreatedAt, - ConnectorID: accounts[1].ConnectorID.String(), - Provider: accounts[1].ConnectorID.Provider.String(), - DefaultCurrency: accounts[1].DefaultAsset.String(), - DefaultAsset: accounts[1].DefaultAsset.String(), - AccountName: accounts[1].AccountName, - Pools: []uuid.UUID{}, - // Type is converted to external when it is external formance - Type: string(models.AccountTypeExternal), - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListAccounts(gomock.Any(), testCase.expectedQuery). - Return(listAccountsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListAccounts(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, "/accounts", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*accountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedAccountsResponse, resp.Cursor.Data) - require.Equal(t, listAccountsResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listAccountsResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listAccountsResponse.Next, resp.Cursor.Next) - require.Equal(t, listAccountsResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetAccount(t *testing.T) { - t.Parallel() - - accountID1 := models.AccountID{ - Reference: "acc1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - accountID2 := models.AccountID{ - Reference: "acc2", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - type testCase struct { - name string - accountID string - serviceError error - expectedAccountID models.AccountID - expectedStatusCode int - expectedErrorCode string - } - - testCases := []testCase{ - { - name: "nomimal acc1", - accountID: accountID1.String(), - expectedAccountID: accountID1, - }, - { - name: "nomimal acc2", - accountID: accountID2.String(), - expectedAccountID: accountID2, - }, - { - name: "err validation from backend", - accountID: accountID1.String(), - expectedAccountID: accountID1, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - accountID: accountID1.String(), - expectedAccountID: accountID1, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - accountID: accountID1.String(), - expectedAccountID: accountID1, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - accountID: accountID1.String(), - expectedAccountID: accountID1, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - var getAccountResponse *models.Account - var expectedAccountsResponse *accountResponse - if testCase.expectedAccountID == accountID1 { - getAccountResponse = &models.Account{ - ID: models.AccountID{Reference: "acc1", ConnectorID: models.ConnectorID{Reference: uuid.New(), Provider: models.ConnectorProviderDummyPay}}, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "acc1", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - }, - } - - expectedAccountsResponse = &accountResponse{ - ID: getAccountResponse.ID.String(), - Reference: getAccountResponse.Reference, - CreatedAt: getAccountResponse.CreatedAt, - ConnectorID: getAccountResponse.ConnectorID.String(), - Provider: getAccountResponse.ConnectorID.Provider.String(), - DefaultCurrency: getAccountResponse.DefaultAsset.String(), - DefaultAsset: getAccountResponse.DefaultAsset.String(), - AccountName: getAccountResponse.AccountName, - Metadata: getAccountResponse.Metadata, - Pools: []uuid.UUID{}, - Type: getAccountResponse.Type.String(), - } - } else { - getAccountResponse = &models.Account{ - ID: models.AccountID{Reference: "acc2", ConnectorID: models.ConnectorID{Reference: uuid.New(), Provider: models.ConnectorProviderDummyPay}}, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "acc2", - Type: models.AccountTypeExternalFormance, - } - expectedAccountsResponse = &accountResponse{ - ID: getAccountResponse.ID.String(), - Reference: getAccountResponse.Reference, - CreatedAt: getAccountResponse.CreatedAt, - ConnectorID: getAccountResponse.ConnectorID.String(), - Provider: getAccountResponse.ConnectorID.Provider.String(), - DefaultCurrency: getAccountResponse.DefaultAsset.String(), - DefaultAsset: getAccountResponse.DefaultAsset.String(), - AccountName: getAccountResponse.AccountName, - Pools: []uuid.UUID{}, - // Type is converted to external when it is external formance - Type: models.AccountTypeExternal.String(), - } - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - GetAccount(gomock.Any(), testCase.expectedAccountID.String()). - Return(getAccountResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - GetAccount(gomock.Any(), testCase.expectedAccountID.String()). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/accounts/%s", testCase.accountID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[accountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedAccountsResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/api_utils_test.go b/components/payments/cmd/api/internal/api/api_utils_test.go deleted file mode 100644 index 149478e4f1..0000000000 --- a/components/payments/cmd/api/internal/api/api_utils_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package api - -import ( - "testing" - - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/golang/mock/gomock" -) - -func newTestingBackend(t *testing.T) (*backend.MockBackend, *backend.MockService) { - ctrl := gomock.NewController(t) - mockService := backend.NewMockService(ctrl) - backend := backend.NewMockBackend(ctrl) - backend. - EXPECT(). - GetService(). - MinTimes(0). - Return(mockService) - t.Cleanup(func() { - ctrl.Finish() - }) - return backend, mockService -} diff --git a/components/payments/cmd/api/internal/api/backend/backend.go b/components/payments/cmd/api/internal/api/backend/backend.go deleted file mode 100644 index 72429a2518..0000000000 --- a/components/payments/cmd/api/internal/api/backend/backend.go +++ /dev/null @@ -1,54 +0,0 @@ -package backend - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -//go:generate mockgen -source backend.go -destination backend_generated.go -package backend . Service -type Service interface { - Ping() error - CreateAccount(ctx context.Context, req *service.CreateAccountRequest) (*models.Account, error) - ListAccounts(ctx context.Context, q storage.ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) - GetAccount(ctx context.Context, id string) (*models.Account, error) - ListBalances(ctx context.Context, q storage.ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) - ListBankAccounts(ctx context.Context, a storage.ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) - GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) - ListTransferInitiations(ctx context.Context, q storage.ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) - ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) - CreatePayment(ctx context.Context, req *service.CreatePaymentRequest) (*models.Payment, error) - ListPayments(ctx context.Context, q storage.ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) - GetPayment(ctx context.Context, id string) (*models.Payment, error) - UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error - CreatePool(ctx context.Context, req *service.CreatePoolRequest) (*models.Pool, error) - AddAccountToPool(ctx context.Context, poolID string, req *service.AddAccountToPoolRequest) error - RemoveAccountFromPool(ctx context.Context, poolID string, accountID string) error - ListPools(ctx context.Context, q storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) - GetPool(ctx context.Context, poolID string) (*models.Pool, error) - GetPoolBalance(ctx context.Context, poolID string, atTime string) (*service.GetPoolBalanceResponse, error) - DeletePool(ctx context.Context, poolID string) error -} - -type Backend interface { - GetService() Service -} - -type DefaultBackend struct { - service Service -} - -func (d DefaultBackend) GetService() Service { - return d.service -} - -func NewDefaultBackend(service Service) Backend { - return &DefaultBackend{ - service: service, - } -} diff --git a/components/payments/cmd/api/internal/api/backend/backend_generated.go b/components/payments/cmd/api/internal/api/backend/backend_generated.go deleted file mode 100644 index cc78a24b3e..0000000000 --- a/components/payments/cmd/api/internal/api/backend/backend_generated.go +++ /dev/null @@ -1,372 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: backend.go - -// Package backend is a generated GoMock package. -package backend - -import ( - context "context" - reflect "reflect" - - service "github.com/formancehq/payments/cmd/api/internal/api/service" - storage "github.com/formancehq/payments/cmd/api/internal/storage" - models "github.com/formancehq/payments/internal/models" - bunpaginate "github.com/formancehq/go-libs/bun/bunpaginate" - gomock "github.com/golang/mock/gomock" - uuid "github.com/google/uuid" -) - -// MockService is a mock of Service interface. -type MockService struct { - ctrl *gomock.Controller - recorder *MockServiceMockRecorder -} - -// MockServiceMockRecorder is the mock recorder for MockService. -type MockServiceMockRecorder struct { - mock *MockService -} - -// NewMockService creates a new mock instance. -func NewMockService(ctrl *gomock.Controller) *MockService { - mock := &MockService{ctrl: ctrl} - mock.recorder = &MockServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockService) EXPECT() *MockServiceMockRecorder { - return m.recorder -} - -// AddAccountToPool mocks base method. -func (m *MockService) AddAccountToPool(ctx context.Context, poolID string, req *service.AddAccountToPoolRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddAccountToPool", ctx, poolID, req) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddAccountToPool indicates an expected call of AddAccountToPool. -func (mr *MockServiceMockRecorder) AddAccountToPool(ctx, poolID, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAccountToPool", reflect.TypeOf((*MockService)(nil).AddAccountToPool), ctx, poolID, req) -} - -// CreateAccount mocks base method. -func (m *MockService) CreateAccount(ctx context.Context, req *service.CreateAccountRequest) (*models.Account, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateAccount", ctx, req) - ret0, _ := ret[0].(*models.Account) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateAccount indicates an expected call of CreateAccount. -func (mr *MockServiceMockRecorder) CreateAccount(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAccount", reflect.TypeOf((*MockService)(nil).CreateAccount), ctx, req) -} - -// CreatePayment mocks base method. -func (m *MockService) CreatePayment(ctx context.Context, req *service.CreatePaymentRequest) (*models.Payment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreatePayment", ctx, req) - ret0, _ := ret[0].(*models.Payment) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreatePayment indicates an expected call of CreatePayment. -func (mr *MockServiceMockRecorder) CreatePayment(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePayment", reflect.TypeOf((*MockService)(nil).CreatePayment), ctx, req) -} - -// CreatePool mocks base method. -func (m *MockService) CreatePool(ctx context.Context, req *service.CreatePoolRequest) (*models.Pool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreatePool", ctx, req) - ret0, _ := ret[0].(*models.Pool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreatePool indicates an expected call of CreatePool. -func (mr *MockServiceMockRecorder) CreatePool(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePool", reflect.TypeOf((*MockService)(nil).CreatePool), ctx, req) -} - -// DeletePool mocks base method. -func (m *MockService) DeletePool(ctx context.Context, poolID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeletePool", ctx, poolID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeletePool indicates an expected call of DeletePool. -func (mr *MockServiceMockRecorder) DeletePool(ctx, poolID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePool", reflect.TypeOf((*MockService)(nil).DeletePool), ctx, poolID) -} - -// GetAccount mocks base method. -func (m *MockService) GetAccount(ctx context.Context, id string) (*models.Account, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccount", ctx, id) - ret0, _ := ret[0].(*models.Account) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAccount indicates an expected call of GetAccount. -func (mr *MockServiceMockRecorder) GetAccount(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockService)(nil).GetAccount), ctx, id) -} - -// GetBankAccount mocks base method. -func (m *MockService) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBankAccount", ctx, id, expand) - ret0, _ := ret[0].(*models.BankAccount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBankAccount indicates an expected call of GetBankAccount. -func (mr *MockServiceMockRecorder) GetBankAccount(ctx, id, expand interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBankAccount", reflect.TypeOf((*MockService)(nil).GetBankAccount), ctx, id, expand) -} - -// GetPayment mocks base method. -func (m *MockService) GetPayment(ctx context.Context, id string) (*models.Payment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPayment", ctx, id) - ret0, _ := ret[0].(*models.Payment) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPayment indicates an expected call of GetPayment. -func (mr *MockServiceMockRecorder) GetPayment(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPayment", reflect.TypeOf((*MockService)(nil).GetPayment), ctx, id) -} - -// GetPool mocks base method. -func (m *MockService) GetPool(ctx context.Context, poolID string) (*models.Pool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPool", ctx, poolID) - ret0, _ := ret[0].(*models.Pool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPool indicates an expected call of GetPool. -func (mr *MockServiceMockRecorder) GetPool(ctx, poolID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPool", reflect.TypeOf((*MockService)(nil).GetPool), ctx, poolID) -} - -// GetPoolBalance mocks base method. -func (m *MockService) GetPoolBalance(ctx context.Context, poolID, atTime string) (*service.GetPoolBalanceResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPoolBalance", ctx, poolID, atTime) - ret0, _ := ret[0].(*service.GetPoolBalanceResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPoolBalance indicates an expected call of GetPoolBalance. -func (mr *MockServiceMockRecorder) GetPoolBalance(ctx, poolID, atTime interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPoolBalance", reflect.TypeOf((*MockService)(nil).GetPoolBalance), ctx, poolID, atTime) -} - -// ListAccounts mocks base method. -func (m *MockService) ListAccounts(ctx context.Context, q storage.ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListAccounts", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.Account]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListAccounts indicates an expected call of ListAccounts. -func (mr *MockServiceMockRecorder) ListAccounts(ctx, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccounts", reflect.TypeOf((*MockService)(nil).ListAccounts), ctx, q) -} - -// ListBalances mocks base method. -func (m *MockService) ListBalances(ctx context.Context, q storage.ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListBalances", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.Balance]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListBalances indicates an expected call of ListBalances. -func (mr *MockServiceMockRecorder) ListBalances(ctx, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBalances", reflect.TypeOf((*MockService)(nil).ListBalances), ctx, q) -} - -// ListBankAccounts mocks base method. -func (m *MockService) ListBankAccounts(ctx context.Context, a storage.ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListBankAccounts", ctx, a) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.BankAccount]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListBankAccounts indicates an expected call of ListBankAccounts. -func (mr *MockServiceMockRecorder) ListBankAccounts(ctx, a interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBankAccounts", reflect.TypeOf((*MockService)(nil).ListBankAccounts), ctx, a) -} - -// ListPayments mocks base method. -func (m *MockService) ListPayments(ctx context.Context, q storage.ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListPayments", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.Payment]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListPayments indicates an expected call of ListPayments. -func (mr *MockServiceMockRecorder) ListPayments(ctx, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPayments", reflect.TypeOf((*MockService)(nil).ListPayments), ctx, q) -} - -// ListPools mocks base method. -func (m *MockService) ListPools(ctx context.Context, q storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListPools", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.Pool]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListPools indicates an expected call of ListPools. -func (mr *MockServiceMockRecorder) ListPools(ctx, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPools", reflect.TypeOf((*MockService)(nil).ListPools), ctx, q) -} - -// ListTransferInitiations mocks base method. -func (m *MockService) ListTransferInitiations(ctx context.Context, q storage.ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListTransferInitiations", ctx, q) - ret0, _ := ret[0].(*bunpaginate.Cursor[models.TransferInitiation]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListTransferInitiations indicates an expected call of ListTransferInitiations. -func (mr *MockServiceMockRecorder) ListTransferInitiations(ctx, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTransferInitiations", reflect.TypeOf((*MockService)(nil).ListTransferInitiations), ctx, q) -} - -// Ping mocks base method. -func (m *MockService) Ping() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Ping") - ret0, _ := ret[0].(error) - return ret0 -} - -// Ping indicates an expected call of Ping. -func (mr *MockServiceMockRecorder) Ping() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockService)(nil).Ping)) -} - -// ReadTransferInitiation mocks base method. -func (m *MockService) ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadTransferInitiation", ctx, id) - ret0, _ := ret[0].(*models.TransferInitiation) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReadTransferInitiation indicates an expected call of ReadTransferInitiation. -func (mr *MockServiceMockRecorder) ReadTransferInitiation(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadTransferInitiation", reflect.TypeOf((*MockService)(nil).ReadTransferInitiation), ctx, id) -} - -// RemoveAccountFromPool mocks base method. -func (m *MockService) RemoveAccountFromPool(ctx context.Context, poolID, accountID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveAccountFromPool", ctx, poolID, accountID) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveAccountFromPool indicates an expected call of RemoveAccountFromPool. -func (mr *MockServiceMockRecorder) RemoveAccountFromPool(ctx, poolID, accountID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAccountFromPool", reflect.TypeOf((*MockService)(nil).RemoveAccountFromPool), ctx, poolID, accountID) -} - -// UpdatePaymentMetadata mocks base method. -func (m *MockService) UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdatePaymentMetadata", ctx, paymentID, metadata) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdatePaymentMetadata indicates an expected call of UpdatePaymentMetadata. -func (mr *MockServiceMockRecorder) UpdatePaymentMetadata(ctx, paymentID, metadata interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePaymentMetadata", reflect.TypeOf((*MockService)(nil).UpdatePaymentMetadata), ctx, paymentID, metadata) -} - -// MockBackend is a mock of Backend interface. -type MockBackend struct { - ctrl *gomock.Controller - recorder *MockBackendMockRecorder -} - -// MockBackendMockRecorder is the mock recorder for MockBackend. -type MockBackendMockRecorder struct { - mock *MockBackend -} - -// NewMockBackend creates a new mock instance. -func NewMockBackend(ctrl *gomock.Controller) *MockBackend { - mock := &MockBackend{ctrl: ctrl} - mock.recorder = &MockBackendMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBackend) EXPECT() *MockBackendMockRecorder { - return m.recorder -} - -// GetService mocks base method. -func (m *MockBackend) GetService() Service { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetService") - ret0, _ := ret[0].(Service) - return ret0 -} - -// GetService indicates an expected call of GetService. -func (mr *MockBackendMockRecorder) GetService() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockBackend)(nil).GetService)) -} diff --git a/components/payments/cmd/api/internal/api/balances.go b/components/payments/cmd/api/internal/api/balances.go deleted file mode 100644 index 52d8c734c6..0000000000 --- a/components/payments/cmd/api/internal/api/balances.go +++ /dev/null @@ -1,163 +0,0 @@ -package api - -import ( - "encoding/json" - "math/big" - "net/http" - "strconv" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" -) - -type balancesResponse struct { - AccountID string `json:"accountId"` - CreatedAt time.Time `json:"createdAt"` - LastUpdatedAt time.Time `json:"lastUpdatedAt"` - Currency string `json:"currency"` // Deprecated: should be removed soon - Asset string `json:"asset"` - Balance *big.Int `json:"balance"` -} - -func listBalancesForAccount(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listBalancesForAccount") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - balanceQuery, err := populateBalanceQueryFromRequest(r) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - span.SetAttributes( - attribute.String("request.accountID", balanceQuery.AccountID.String()), - attribute.String("request.currency", balanceQuery.Currency), - attribute.String("request.from", balanceQuery.From.String()), - attribute.String("request.to", balanceQuery.To.String()), - ) - - query, err := bunpaginate.Extract[storage.ListBalancesQuery](r, func() (*storage.ListBalancesQuery, error) { - options, err := getPagination(r, balanceQuery) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListBalancesQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - // In order to support the legacy API, we need to check if the limit query parameter is set - // and if so, we need to override the pageSize pagination option - if r.URL.Query().Get("limit") != "" { - limit, err := strconv.ParseInt(r.URL.Query().Get("limit"), 10, 64) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - if limit > 0 { - query.PageSize = uint64(limit) - query.Options.PageSize = uint64(limit) - } - } - - cursor, err := b.GetService().ListBalances(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*balancesResponse, len(ret)) - - for i := range ret { - data[i] = &balancesResponse{ - AccountID: ret[i].AccountID.String(), - CreatedAt: ret[i].CreatedAt, - Currency: ret[i].Asset.String(), - Asset: ret[i].Asset.String(), - Balance: ret[i].Balance, - LastUpdatedAt: ret[i].LastUpdatedAt, - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*balancesResponse]{ - Cursor: &bunpaginate.Cursor[*balancesResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func populateBalanceQueryFromRequest(r *http.Request) (storage.BalanceQuery, error) { - var balanceQuery storage.BalanceQuery - - balanceQuery = balanceQuery.WithCurrency(r.URL.Query().Get("asset")) - - accountID, err := models.AccountIDFromString(mux.Vars(r)["accountID"]) - if err != nil { - return balanceQuery, err - } - balanceQuery = balanceQuery.WithAccountID(accountID) - - var startTimeParsed, endTimeParsed time.Time - - from, to := r.URL.Query().Get("from"), r.URL.Query().Get("to") - if from != "" { - startTimeParsed, err = time.Parse(time.RFC3339Nano, from) - if err != nil { - return balanceQuery, err - } - } - if to != "" { - endTimeParsed, err = time.Parse(time.RFC3339Nano, to) - if err != nil { - return balanceQuery, err - } - } - - switch { - case startTimeParsed.IsZero() && endTimeParsed.IsZero(): - balanceQuery = balanceQuery. - WithTo(time.Now()) - case !startTimeParsed.IsZero() && endTimeParsed.IsZero(): - balanceQuery = balanceQuery. - WithFrom(startTimeParsed). - WithTo(time.Now()) - case startTimeParsed.IsZero() && !endTimeParsed.IsZero(): - balanceQuery = balanceQuery. - WithTo(endTimeParsed) - default: - balanceQuery = balanceQuery. - WithFrom(startTimeParsed). - WithTo(endTimeParsed) - } - - return balanceQuery, nil -} diff --git a/components/payments/cmd/api/internal/api/balances_test.go b/components/payments/cmd/api/internal/api/balances_test.go deleted file mode 100644 index ec351dfdf9..0000000000 --- a/components/payments/cmd/api/internal/api/balances_test.go +++ /dev/null @@ -1,377 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestGetBalances(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - accountID string - queryParams url.Values - pageSize int - expectedQuery storage.ListBalancesQuery - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - accountIDString := accountID.String() - testCases := []testCase{ - { - name: "nomimal", - queryParams: url.Values{ - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - pageSize: 15, - accountID: accountIDString, - }, - { - name: "with invalid accountID", - accountID: "invalid", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "with valid limit", - queryParams: url.Values{ - "limit": {"10"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(10), - ), - pageSize: 15, - accountID: accountIDString, - }, - { - name: "with invalid limit", - queryParams: url.Values{ - "limit": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "with from and to", - queryParams: url.Values{ - "from": []string{time.Date(2023, 11, 20, 6, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithFrom(time.Date(2023, 11, 20, 6, 0, 0, 0, time.UTC)). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - accountID: accountIDString, - }, - { - name: "with invalid from", - queryParams: url.Values{ - "from": []string{"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "with invalid to", - queryParams: url.Values{ - "to": []string{"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "page size too low, should use the default value", - queryParams: url.Values{ - "pageSize": {"0"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - pageSize: 15, - accountID: accountIDString, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(100), - ), - pageSize: 100, - accountID: accountIDString, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"account_id\": \"acc1\"}}"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15). - WithQueryBuilder(query.Match("account_id", "acc1")), - ), - pageSize: 15, - accountID: accountIDString, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"account_id:asc"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15). - WithSorter(storage.Sorter{}.Add("account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - accountID: accountIDString, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"account_id:invalid"}, - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "err validation from backend", - queryParams: url.Values{ - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - accountID: accountIDString, - }, - { - name: "ErrNotFound from storage", - queryParams: url.Values{ - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - accountID: accountIDString, - }, - { - name: "ErrDuplicateKeyValue from storage", - queryParams: url.Values{ - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - accountID: accountIDString, - }, - { - name: "other storage errors from storage", - queryParams: url.Values{ - "to": []string{time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC).Format(time.RFC3339Nano)}, - }, - expectedQuery: storage.NewListBalancesQuery( - storage.NewPaginatedQueryOptions( - storage.NewBalanceQuery(). - WithAccountID(&accountID). - WithTo(time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC)), - ).WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - accountID: accountIDString, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - balances := []models.Balance{ - { - AccountID: accountID, - Asset: "EUR/2", - Balance: big.NewInt(100), - CreatedAt: time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC), - LastUpdatedAt: time.Date(2023, 11, 23, 9, 0, 0, 0, time.UTC), - ConnectorID: connectorID, - }, - } - - listBalancesResponse := &bunpaginate.Cursor[models.Balance]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: balances, - } - - if limit, ok := testCase.queryParams["limit"]; ok { - testCase.pageSize, _ = strconv.Atoi(limit[0]) - } - - expectedBalancessResponse := []*balancesResponse{ - { - AccountID: balances[0].AccountID.String(), - CreatedAt: balances[0].CreatedAt, - LastUpdatedAt: balances[0].LastUpdatedAt, - Currency: balances[0].Asset.String(), - Asset: balances[0].Asset.String(), - Balance: balances[0].Balance, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListBalances(gomock.Any(), testCase.expectedQuery). - Return(listBalancesResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListBalances(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/accounts/%s/balances", testCase.accountID), nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*balancesResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedBalancessResponse, resp.Cursor.Data) - require.Equal(t, listBalancesResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listBalancesResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listBalancesResponse.Next, resp.Cursor.Next) - require.Equal(t, listBalancesResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/bank_accounts.go b/components/payments/cmd/api/internal/api/bank_accounts.go deleted file mode 100644 index 526e59db4d..0000000000 --- a/components/payments/cmd/api/internal/api/bank_accounts.go +++ /dev/null @@ -1,186 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" -) - -type bankAccountRelatedAccountsResponse struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - AccountID string `json:"accountID"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` -} - -type bankAccountResponse struct { - ID string `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - Country string `json:"country"` - Iban string `json:"iban,omitempty"` - AccountNumber string `json:"accountNumber,omitempty"` - SwiftBicCode string `json:"swiftBicCode,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - RelatedAccounts []*bankAccountRelatedAccountsResponse `json:"relatedAccounts,omitempty"` - - // Deprecated fields, but clients still use them - // They correspond to the first bank account adjustment now. - ConnectorID string `json:"connectorID"` - Provider string `json:"provider,omitempty"` - AccountID string `json:"accountID,omitempty"` -} - -func listBankAccountsHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listBankAccountsHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - query, err := bunpaginate.Extract[storage.ListBankAccountQuery](r, func() (*storage.ListBankAccountQuery, error) { - options, err := getPagination(r, storage.BankAccountQuery{}) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListBankAccountQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := b.GetService().ListBankAccounts(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*bankAccountResponse, len(ret)) - - for i := range ret { - data[i] = &bankAccountResponse{ - ID: ret[i].ID.String(), - Name: ret[i].Name, - CreatedAt: ret[i].CreatedAt, - Country: ret[i].Country, - Metadata: ret[i].Metadata, - } - - // Deprecated fields, but clients still use them - if len(ret[i].RelatedAccounts) > 0 { - data[i].ConnectorID = ret[i].RelatedAccounts[0].ConnectorID.String() - data[i].AccountID = ret[i].RelatedAccounts[0].AccountID.String() - data[i].Provider = ret[i].RelatedAccounts[0].ConnectorID.Provider.String() - } - - for _, adjustment := range ret[i].RelatedAccounts { - data[i].RelatedAccounts = append(data[i].RelatedAccounts, &bankAccountRelatedAccountsResponse{ - ID: adjustment.ID.String(), - CreatedAt: adjustment.CreatedAt, - AccountID: adjustment.AccountID.String(), - ConnectorID: adjustment.ConnectorID.String(), - Provider: adjustment.ConnectorID.Provider.String(), - }) - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*bankAccountResponse]{ - Cursor: &bunpaginate.Cursor[*bankAccountResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func readBankAccountHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readBankAccountHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - bankAccountID, err := uuid.Parse(mux.Vars(r)["bankAccountID"]) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.bankAccountID", bankAccountID.String())) - - account, err := b.GetService().GetBankAccount(ctx, bankAccountID, true) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - if err := account.Offuscate(); err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - data := &bankAccountResponse{ - ID: account.ID.String(), - Name: account.Name, - CreatedAt: account.CreatedAt, - Country: account.Country, - Iban: account.IBAN, - AccountNumber: account.AccountNumber, - SwiftBicCode: account.SwiftBicCode, - Metadata: account.Metadata, - } - - // Deprecated fields, but clients still use them - if len(account.RelatedAccounts) > 0 { - data.ConnectorID = account.RelatedAccounts[0].ConnectorID.String() - data.AccountID = account.RelatedAccounts[0].AccountID.String() - data.Provider = account.RelatedAccounts[0].ConnectorID.Provider.String() - } - - for _, adjustment := range account.RelatedAccounts { - data.RelatedAccounts = append(data.RelatedAccounts, &bankAccountRelatedAccountsResponse{ - ID: adjustment.ID.String(), - CreatedAt: adjustment.CreatedAt, - AccountID: adjustment.AccountID.String(), - ConnectorID: adjustment.ConnectorID.String(), - Provider: adjustment.ConnectorID.Provider.String(), - }) - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[bankAccountResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - } -} diff --git a/components/payments/cmd/api/internal/api/bank_accounts_test.go b/components/payments/cmd/api/internal/api/bank_accounts_test.go deleted file mode 100644 index e1387555db..0000000000 --- a/components/payments/cmd/api/internal/api/bank_accounts_test.go +++ /dev/null @@ -1,451 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestListBankAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - pageSize int - expectedQuery storage.ListBankAccountQuery - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nomimal", - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(100), - ), - pageSize: 100, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"source_account_id\": \"acc1\"}}"}, - }, - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15). - WithQueryBuilder(query.Match("source_account_id", "acc1")), - ), - pageSize: 15, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:asc"}, - }, - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15). - WithSorter(storage.Sorter{}.Add("source_account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - expectedQuery: storage.NewListBankAccountQuery( - storage.NewPaginatedQueryOptions(storage.BankAccountQuery{}). - WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - b1ID := uuid.New() - b2ID := uuid.New() - - bankAccounts := []models.BankAccount{ - { - ID: b1ID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "ba1", - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - RelatedAccounts: []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - BankAccountID: b1ID, - ConnectorID: connectorID, - AccountID: models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - }, - }, - }, - { - ID: b2ID, - CreatedAt: time.Date(2023, 11, 23, 8, 0, 0, 0, time.UTC), - Name: "ba2", - AccountNumber: "0112345679", - IBAN: "FR7630006000011234567890188", - SwiftBicCode: "ABCDGB4B", - Country: "DE", - RelatedAccounts: []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - BankAccountID: b2ID, - ConnectorID: connectorID, - AccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - }, - }, - }, - }, - } - listBankAccountsResponse := &bunpaginate.Cursor[models.BankAccount]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: bankAccounts, - } - - expectedBankAccountsResponse := []*bankAccountResponse{ - { - ID: bankAccounts[0].ID.String(), - Name: bankAccounts[0].Name, - CreatedAt: bankAccounts[0].CreatedAt, - Country: bankAccounts[0].Country, - ConnectorID: bankAccounts[0].RelatedAccounts[0].ConnectorID.String(), - AccountID: bankAccounts[0].RelatedAccounts[0].AccountID.String(), - Provider: bankAccounts[0].RelatedAccounts[0].ConnectorID.Provider.String(), - RelatedAccounts: []*bankAccountRelatedAccountsResponse{ - { - ID: bankAccounts[0].RelatedAccounts[0].ID.String(), - AccountID: bankAccounts[0].RelatedAccounts[0].AccountID.String(), - ConnectorID: bankAccounts[0].RelatedAccounts[0].ConnectorID.String(), - Provider: bankAccounts[0].RelatedAccounts[0].ConnectorID.Provider.String(), - }, - }, - }, - { - ID: bankAccounts[1].ID.String(), - Name: bankAccounts[1].Name, - CreatedAt: bankAccounts[1].CreatedAt, - Country: bankAccounts[1].Country, - ConnectorID: bankAccounts[1].RelatedAccounts[0].ConnectorID.String(), - AccountID: bankAccounts[1].RelatedAccounts[0].AccountID.String(), - Provider: bankAccounts[1].RelatedAccounts[0].ConnectorID.Provider.String(), - RelatedAccounts: []*bankAccountRelatedAccountsResponse{ - { - ID: bankAccounts[1].RelatedAccounts[0].ID.String(), - AccountID: bankAccounts[1].RelatedAccounts[0].AccountID.String(), - ConnectorID: bankAccounts[1].RelatedAccounts[0].ConnectorID.String(), - Provider: bankAccounts[1].RelatedAccounts[0].ConnectorID.Provider.String(), - }, - }, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListBankAccounts(gomock.Any(), testCase.expectedQuery). - Return(listBankAccountsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListBankAccounts(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, "/bank-accounts", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*bankAccountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedBankAccountsResponse, resp.Cursor.Data) - require.Equal(t, listBankAccountsResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listBankAccountsResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listBankAccountsResponse.Next, resp.Cursor.Next) - require.Equal(t, listBankAccountsResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetBankAccount(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - bankAccountUUID string - expectedBankAccountUUID uuid.UUID - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - uuid1 := uuid.New() - testCases := []testCase{ - { - name: "nomimal", - bankAccountUUID: uuid1.String(), - expectedBankAccountUUID: uuid1, - }, - { - name: "invalid uuid", - bankAccountUUID: "invalid", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "err validation from backend", - bankAccountUUID: uuid1.String(), - expectedBankAccountUUID: uuid1, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - bankAccountUUID: uuid1.String(), - expectedBankAccountUUID: uuid1, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - bankAccountUUID: uuid1.String(), - expectedBankAccountUUID: uuid1, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - bankAccountUUID: uuid1.String(), - expectedBankAccountUUID: uuid1, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - getBankAccountResponse := &models.BankAccount{ - ID: uuid1, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "ba1", - AccountNumber: "13719713158835300", - IBAN: "FR7630006000011234567890188", - SwiftBicCode: "ABCDGB4B", - Country: "FR", - RelatedAccounts: []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - BankAccountID: uuid1, - ConnectorID: connectorID, - AccountID: models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - }, - }, - } - - expectedBankAccountResponse := &bankAccountResponse{ - ID: getBankAccountResponse.ID.String(), - Name: getBankAccountResponse.Name, - CreatedAt: getBankAccountResponse.CreatedAt, - Country: getBankAccountResponse.Country, - ConnectorID: getBankAccountResponse.RelatedAccounts[0].ConnectorID.String(), - Provider: getBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String(), - AccountID: getBankAccountResponse.RelatedAccounts[0].AccountID.String(), - Iban: "FR76*******************0188", - AccountNumber: "13************300", - SwiftBicCode: "ABCDGB4B", - RelatedAccounts: []*bankAccountRelatedAccountsResponse{ - { - ID: getBankAccountResponse.RelatedAccounts[0].ID.String(), - AccountID: getBankAccountResponse.RelatedAccounts[0].AccountID.String(), - ConnectorID: getBankAccountResponse.RelatedAccounts[0].ConnectorID.String(), - Provider: getBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String(), - }, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - GetBankAccount(gomock.Any(), testCase.expectedBankAccountUUID, true). - Return(getBankAccountResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - GetBankAccount(gomock.Any(), testCase.expectedBankAccountUUID, true). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/bank-accounts/%s", testCase.bankAccountUUID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[bankAccountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedBankAccountResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/health.go b/components/payments/cmd/api/internal/api/health.go deleted file mode 100644 index 3ba6427d8a..0000000000 --- a/components/payments/cmd/api/internal/api/health.go +++ /dev/null @@ -1,26 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/api/internal/api/backend" -) - -func healthHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := b.GetService().Ping(); err != nil { - api.InternalServerError(w, r, err) - - return - } - - w.WriteHeader(http.StatusOK) - } -} - -func liveHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - } -} diff --git a/components/payments/cmd/api/internal/api/metadata.go b/components/payments/cmd/api/internal/api/metadata.go deleted file mode 100644 index 9067a65b2e..0000000000 --- a/components/payments/cmd/api/internal/api/metadata.go +++ /dev/null @@ -1,60 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - - "github.com/gorilla/mux" -) - -func updateMetadataHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "updateMetadataHandler") - defer span.End() - - paymentID, err := models.PaymentIDFromString(mux.Vars(r)["paymentID"]) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.paymentID", paymentID.String())) - - var metadata service.UpdateMetadataRequest - if r.ContentLength == 0 { - var err = errors.New("body is required") - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - err = json.NewDecoder(r.Body).Decode(&metadata) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - for k, v := range metadata { - span.SetAttributes(attribute.String("request.metadata."+k, v)) - } - - err = b.GetService().UpdatePaymentMetadata(ctx, *paymentID, metadata) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} diff --git a/components/payments/cmd/api/internal/api/metadata_test.go b/components/payments/cmd/api/internal/api/metadata_test.go deleted file mode 100644 index df8dfbd9a7..0000000000 --- a/components/payments/cmd/api/internal/api/metadata_test.go +++ /dev/null @@ -1,149 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - paymentID string - body string - expectedPaymentID models.PaymentID - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - paymentID: paymentID.String(), - body: "{\"foo\":\"bar\"}", - expectedPaymentID: paymentID, - }, - { - name: "missing body", - paymentID: paymentID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "invalid body", - paymentID: paymentID.String(), - body: "invalid", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "invalid paymentID", - paymentID: "invalid", - body: "{\"foo\":\"bar\"}", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "nominal", - paymentID: paymentID.String(), - body: "{\"foo\":\"bar\"}", - expectedPaymentID: paymentID, - serviceError: service.ErrValidation, - expectedErrorCode: ErrValidation, - expectedStatusCode: http.StatusBadRequest, - }, - { - name: "nominal", - paymentID: paymentID.String(), - body: "{\"foo\":\"bar\"}", - expectedPaymentID: paymentID, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "nominal", - paymentID: paymentID.String(), - body: "{\"foo\":\"bar\"}", - expectedPaymentID: paymentID, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "nominal", - paymentID: paymentID.String(), - body: "{\"foo\":\"bar\"}", - expectedPaymentID: paymentID, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - UpdatePaymentMetadata(gomock.Any(), testCase.expectedPaymentID, map[string]string{"foo": "bar"}). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - UpdatePaymentMetadata(gomock.Any(), testCase.expectedPaymentID, map[string]string{"foo": "bar"}). - Return(testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/payments/%s/metadata", testCase.paymentID), strings.NewReader(testCase.body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/module.go b/components/payments/cmd/api/internal/api/module.go deleted file mode 100644 index 3cd1e54bc8..0000000000 --- a/components/payments/cmd/api/internal/api/module.go +++ /dev/null @@ -1,98 +0,0 @@ -package api - -import ( - "context" - "errors" - "net/http" - "runtime/debug" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/otlp" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/messages" - "github.com/gorilla/mux" - "github.com/rs/cors" - "github.com/sirupsen/logrus" - "go.uber.org/fx" -) - -const ( - otelTracesFlag = "otel-traces" - serviceName = "Payments" - - ErrUniqueReference = "CONFLICT" - ErrNotFound = "NOT_FOUND" - ErrInvalidID = "INVALID_ID" - ErrMissingOrInvalidBody = "MISSING_OR_INVALID_BODY" - ErrValidation = "VALIDATION" -) - -func HTTPModule(serviceInfo api.ServiceInfo, bind string, stackURL string, otelTraces bool) fx.Option { - return fx.Options( - fx.Invoke(func(m *mux.Router, lc fx.Lifecycle) { - lc.Append(httpserver.NewHook(m, httpserver.WithAddress(bind))) - }), - fx.Provide(func(store *storage.Storage) service.Store { - return store - }), - fx.Provide(func() *messages.Messages { - return messages.NewMessages(stackURL) - }), - fx.Provide(fx.Annotate(service.New, fx.As(new(backend.Service)))), - fx.Provide(backend.NewDefaultBackend), - fx.Supply(serviceInfo), - fx.Provide(func(b backend.Backend, - logger logging.Logger, - serviceInfo api.ServiceInfo, - a auth.Authenticator) *mux.Router { - return httpRouter(b, logger, serviceInfo, a, otelTraces) - }), - ) -} - -func httpRecoveryFunc(otelTraces bool) func(context.Context, interface{}) { - return func(ctx context.Context, e interface{}) { - if otelTraces { - otlp.RecordAsError(ctx, e) - } else { - logrus.Errorln(e) - debug.PrintStack() - } - } -} - -func httpCorsHandler() func(http.Handler) http.Handler { - return cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut}, - AllowCredentials: true, - }).Handler -} - -func httpServeFunc(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - handler.ServeHTTP(w, r) - }) -} - -func handleServiceErrors(w http.ResponseWriter, r *http.Request, err error) { - switch { - case errors.Is(err, storage.ErrDuplicateKeyValue): - api.BadRequest(w, ErrUniqueReference, err) - case errors.Is(err, storage.ErrNotFound): - api.NotFound(w, err) - case errors.Is(err, storage.ErrValidation): - api.BadRequest(w, ErrValidation, err) - case errors.Is(err, service.ErrValidation): - api.BadRequest(w, ErrValidation, err) - default: - api.InternalServerError(w, r, err) - } -} diff --git a/components/payments/cmd/api/internal/api/payments.go b/components/payments/cmd/api/internal/api/payments.go deleted file mode 100644 index e353edbbc8..0000000000 --- a/components/payments/cmd/api/internal/api/payments.go +++ /dev/null @@ -1,291 +0,0 @@ -package api - -import ( - "encoding/json" - "math/big" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" -) - -type paymentResponse struct { - ID string `json:"id"` - Reference string `json:"reference"` - SourceAccountID string `json:"sourceAccountID"` - DestinationAccountID string `json:"destinationAccountID"` - Type string `json:"type"` - Provider models.ConnectorProvider `json:"provider"` - ConnectorID string `json:"connectorID"` - Status models.PaymentStatus `json:"status"` - Amount *big.Int `json:"amount"` - InitialAmount *big.Int `json:"initialAmount"` - Scheme models.PaymentScheme `json:"scheme"` - Asset string `json:"asset"` - CreatedAt time.Time `json:"createdAt"` - Raw interface{} `json:"raw"` - Adjustments []paymentAdjustment `json:"adjustments"` - Metadata map[string]string `json:"metadata"` -} - -type paymentAdjustment struct { - Reference string `json:"reference" bson:"reference"` - CreatedAt time.Time `json:"createdAt" bson:"createdAt"` - Status models.PaymentStatus `json:"status" bson:"status"` - Amount *big.Int `json:"amount" bson:"amount"` - Raw interface{} `json:"raw" bson:"raw"` -} - -func createPaymentHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "createPaymentHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - var req service.CreatePaymentRequest - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes( - attribute.String("request.reference", req.Reference), - attribute.String("request.sourceAccountID", req.SourceAccountID), - attribute.String("request.destinationAccountID", req.DestinationAccountID), - attribute.String("request.type", req.Type), - attribute.String("request.connectorID", req.ConnectorID), - attribute.String("request.scheme", req.Scheme), - attribute.String("request.status", req.Status), - attribute.String("request.asset", req.Asset), - attribute.String("request.amount", req.Amount.String()), - attribute.String("request.createdAt", req.CreatedAt.String()), - ) - - if err := req.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - payment, err := b.GetService().CreatePayment(ctx, &req) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := paymentResponse{ - ID: payment.ID.String(), - Reference: payment.Reference, - Type: payment.Type.String(), - ConnectorID: payment.ConnectorID.String(), - Provider: payment.ConnectorID.Provider, - Status: payment.Status, - Amount: payment.Amount, - InitialAmount: payment.InitialAmount, - Scheme: payment.Scheme, - Asset: payment.Asset.String(), - CreatedAt: payment.CreatedAt, - Raw: payment.RawData, - Adjustments: make([]paymentAdjustment, len(payment.Adjustments)), - } - - if payment.SourceAccountID != nil { - data.SourceAccountID = payment.SourceAccountID.String() - } - - if payment.DestinationAccountID != nil { - data.DestinationAccountID = payment.DestinationAccountID.String() - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[paymentResponse]{ - Data: &data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func listPaymentsHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listPaymentsHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - query, err := bunpaginate.Extract[storage.ListPaymentsQuery](r, func() (*storage.ListPaymentsQuery, error) { - options, err := getPagination(r, storage.PaymentQuery{}) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListPaymentsQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := b.GetService().ListPayments(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*paymentResponse, len(ret)) - - for i := range ret { - data[i] = &paymentResponse{ - ID: ret[i].ID.String(), - Reference: ret[i].Reference, - Type: ret[i].Type.String(), - ConnectorID: ret[i].ConnectorID.String(), - Provider: ret[i].Connector.Provider, - Status: ret[i].Status, - Amount: ret[i].Amount, - InitialAmount: ret[i].InitialAmount, - Scheme: ret[i].Scheme, - Asset: ret[i].Asset.String(), - CreatedAt: ret[i].CreatedAt, - Raw: ret[i].RawData, - Adjustments: make([]paymentAdjustment, len(ret[i].Adjustments)), - } - - if ret[i].Connector != nil { - data[i].Provider = ret[i].Connector.Provider - } - - if ret[i].SourceAccountID != nil { - data[i].SourceAccountID = ret[i].SourceAccountID.String() - } - - if ret[i].DestinationAccountID != nil { - data[i].DestinationAccountID = ret[i].DestinationAccountID.String() - } - - for adjustmentIdx := range ret[i].Adjustments { - data[i].Adjustments[adjustmentIdx] = paymentAdjustment{ - Reference: ret[i].Adjustments[adjustmentIdx].Reference, - Status: ret[i].Adjustments[adjustmentIdx].Status, - Amount: ret[i].Adjustments[adjustmentIdx].Amount, - CreatedAt: ret[i].Adjustments[adjustmentIdx].CreatedAt, - Raw: ret[i].Adjustments[adjustmentIdx].RawData, - } - } - - if ret[i].Metadata != nil { - data[i].Metadata = make(map[string]string) - - for metadataIDx := range ret[i].Metadata { - data[i].Metadata[ret[i].Metadata[metadataIDx].Key] = ret[i].Metadata[metadataIDx].Value - } - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*paymentResponse]{ - Cursor: &bunpaginate.Cursor[*paymentResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func readPaymentHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readPaymentHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - paymentID := mux.Vars(r)["paymentID"] - - span.SetAttributes(attribute.String("request.paymentID", paymentID)) - - payment, err := b.GetService().GetPayment(ctx, paymentID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := paymentResponse{ - ID: payment.ID.String(), - Reference: payment.Reference, - Type: payment.Type.String(), - ConnectorID: payment.ConnectorID.String(), - Status: payment.Status, - Amount: payment.Amount, - InitialAmount: payment.InitialAmount, - Scheme: payment.Scheme, - Asset: payment.Asset.String(), - CreatedAt: payment.CreatedAt, - Raw: payment.RawData, - Adjustments: make([]paymentAdjustment, len(payment.Adjustments)), - } - - if payment.SourceAccountID != nil { - data.SourceAccountID = payment.SourceAccountID.String() - } - - if payment.DestinationAccountID != nil { - data.DestinationAccountID = payment.DestinationAccountID.String() - } - - if payment.Connector != nil { - data.Provider = payment.Connector.Provider - } - - for i := range payment.Adjustments { - data.Adjustments[i] = paymentAdjustment{ - Reference: payment.Adjustments[i].Reference, - Status: payment.Adjustments[i].Status, - Amount: payment.Adjustments[i].Amount, - CreatedAt: payment.Adjustments[i].CreatedAt, - Raw: payment.Adjustments[i].RawData, - } - } - - if payment.Metadata != nil { - data.Metadata = make(map[string]string) - - for metadataIDx := range payment.Metadata { - data.Metadata[payment.Metadata[metadataIDx].Key] = payment.Metadata[metadataIDx].Value - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[paymentResponse]{ - Data: &data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} diff --git a/components/payments/cmd/api/internal/api/payments_test.go b/components/payments/cmd/api/internal/api/payments_test.go deleted file mode 100644 index fc4c513b0c..0000000000 --- a/components/payments/cmd/api/internal/api/payments_test.go +++ /dev/null @@ -1,971 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestCreatePayments(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.CreatePaymentRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - sourceAccountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - destinationAccountID := models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - } - - testCases := []testCase{ - { - name: "nomimal", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - }, - { - name: "no source account id, but should still pass", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - DestinationAccountID: destinationAccountID.String(), - }, - }, - { - name: "no destination account id, but should still pass", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - }, - }, - { - name: "missing reference", - req: &service.CreatePaymentRequest{ - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing createdAt", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "created at to zero", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Time{}, - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing connectorID", - req: &service.CreatePaymentRequest{ - Reference: "test", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing amount", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing type", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid type", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: "invalid", - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing status", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid status", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: "invalid", - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing scheme", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid scheme", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: "invalid", - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing asset", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid asset", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "invalid", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - req: &service.CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - createPaymentResponse := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: testCase.req.Reference, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: testCase.req.CreatedAt, - Reference: testCase.req.Reference, - Amount: testCase.req.Amount, - InitialAmount: testCase.req.Amount, - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeOther, - Asset: models.Asset("EUR/2"), - SourceAccountID: &sourceAccountID, - DestinationAccountID: &destinationAccountID, - } - - expectedCreatePaymentResponse := &paymentResponse{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "test", - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }.String(), - Reference: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - Type: string(createPaymentResponse.Type), - Provider: createPaymentResponse.ConnectorID.Provider, - ConnectorID: createPaymentResponse.ConnectorID.String(), - Status: createPaymentResponse.Status, - InitialAmount: createPaymentResponse.Amount, - Amount: createPaymentResponse.Amount, - Scheme: createPaymentResponse.Scheme, - Asset: createPaymentResponse.Asset.String(), - CreatedAt: createPaymentResponse.CreatedAt, - Adjustments: make([]paymentAdjustment, len(createPaymentResponse.Adjustments)), - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - CreatePayment(gomock.Any(), testCase.req). - Return(createPaymentResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - CreatePayment(gomock.Any(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, "/payments", bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[paymentResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedCreatePaymentResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestPayments(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - pageSize int - expectedQuery storage.ListPaymentsQuery - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nomimal", - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(100), - ), - pageSize: 100, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"source_account_id\": \"acc1\"}}"}, - }, - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15). - WithQueryBuilder(query.Match("source_account_id", "acc1")), - ), - pageSize: 15, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:asc"}, - }, - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15). - WithSorter(storage.Sorter{}.Add("source_account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - expectedQuery: storage.NewListPaymentsQuery( - storage.NewPaginatedQueryOptions(storage.PaymentQuery{}). - WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - payments := []models.Payment{ - { - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "p1", - Amount: big.NewInt(100), - InitialAmount: big.NewInt(1000), - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusPending, - Scheme: models.PaymentSchemeCardMasterCard, - Asset: models.Asset("USD/2"), - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - Connector: &models.Connector{ - Provider: models.ConnectorProviderDummyPay, - }, - }, - { - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p2", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - Reference: "p2", - Amount: big.NewInt(1000), - InitialAmount: big.NewInt(10000), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeCardVisa, - Asset: models.Asset("EUR/2"), - DestinationAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - Connector: &models.Connector{ - Provider: models.ConnectorProviderDummyPay, - }, - }, - } - listPaymentsResponse := &bunpaginate.Cursor[models.Payment]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: payments, - } - - expectedPaymentsResponse := []*paymentResponse{ - { - ID: payments[0].ID.String(), - Reference: payments[0].Reference, - SourceAccountID: payments[0].SourceAccountID.String(), - DestinationAccountID: payments[0].DestinationAccountID.String(), - Type: payments[0].Type.String(), - Provider: payments[0].Connector.Provider, - ConnectorID: payments[0].ConnectorID.String(), - Status: payments[0].Status, - InitialAmount: payments[0].InitialAmount, - Amount: payments[0].Amount, - Scheme: payments[0].Scheme, - Asset: payments[0].Asset.String(), - CreatedAt: payments[0].CreatedAt, - Adjustments: make([]paymentAdjustment, len(payments[0].Adjustments)), - }, - { - ID: payments[1].ID.String(), - Reference: payments[1].Reference, - SourceAccountID: payments[1].SourceAccountID.String(), - DestinationAccountID: payments[1].DestinationAccountID.String(), - Type: payments[1].Type.String(), - Provider: payments[1].Connector.Provider, - ConnectorID: payments[1].ConnectorID.String(), - Status: payments[1].Status, - InitialAmount: payments[1].InitialAmount, - Amount: payments[1].Amount, - Scheme: payments[1].Scheme, - Asset: payments[1].Asset.String(), - CreatedAt: payments[1].CreatedAt, - Adjustments: make([]paymentAdjustment, len(payments[0].Adjustments)), - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListPayments(gomock.Any(), testCase.expectedQuery). - Return(listPaymentsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListPayments(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, "/payments", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*paymentResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedPaymentsResponse, resp.Cursor.Data) - require.Equal(t, listPaymentsResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listPaymentsResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listPaymentsResponse.Next, resp.Cursor.Next) - require.Equal(t, listPaymentsResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetPayment(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - paymentID1 := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - paymentID2 := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p2", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - - type testCase struct { - name string - paymentID string - expectedPaymentID models.PaymentID - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nomimal p1", - paymentID: paymentID1.String(), - expectedPaymentID: paymentID1, - }, - { - name: "nomimal p2", - paymentID: paymentID2.String(), - expectedPaymentID: paymentID2, - }, - { - name: "err validation from backend", - paymentID: paymentID1.String(), - expectedPaymentID: paymentID1, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - paymentID: paymentID1.String(), - expectedPaymentID: paymentID1, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - paymentID: paymentID1.String(), - expectedPaymentID: paymentID1, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - paymentID: paymentID1.String(), - expectedPaymentID: paymentID1, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - var getPaymentResponse *models.Payment - var expectedPaymentResponse *paymentResponse - if testCase.expectedPaymentID == paymentID1 { - getPaymentResponse = &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Reference: "p1", - Amount: big.NewInt(100), - InitialAmount: big.NewInt(1000), - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusPending, - Scheme: models.PaymentSchemeCardMasterCard, - Asset: models.Asset("USD/2"), - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - Connector: &models.Connector{ - Provider: models.ConnectorProviderDummyPay, - }, - } - - expectedPaymentResponse = &paymentResponse{ - ID: getPaymentResponse.ID.String(), - Reference: getPaymentResponse.Reference, - SourceAccountID: getPaymentResponse.SourceAccountID.String(), - DestinationAccountID: getPaymentResponse.DestinationAccountID.String(), - Type: getPaymentResponse.Type.String(), - Provider: getPaymentResponse.Connector.Provider, - ConnectorID: getPaymentResponse.ConnectorID.String(), - Status: getPaymentResponse.Status, - InitialAmount: getPaymentResponse.InitialAmount, - Amount: getPaymentResponse.Amount, - Scheme: getPaymentResponse.Scheme, - Asset: getPaymentResponse.Asset.String(), - CreatedAt: getPaymentResponse.CreatedAt, - Adjustments: make([]paymentAdjustment, len(getPaymentResponse.Adjustments)), - } - } else { - getPaymentResponse = &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p2", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - Reference: "p2", - Amount: big.NewInt(1000), - InitialAmount: big.NewInt(10000), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeCardVisa, - Asset: models.Asset("EUR/2"), - DestinationAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - Connector: &models.Connector{ - Provider: models.ConnectorProviderDummyPay, - }, - } - expectedPaymentResponse = &paymentResponse{ - ID: getPaymentResponse.ID.String(), - Reference: getPaymentResponse.Reference, - SourceAccountID: getPaymentResponse.SourceAccountID.String(), - DestinationAccountID: getPaymentResponse.DestinationAccountID.String(), - Type: getPaymentResponse.Type.String(), - Provider: getPaymentResponse.Connector.Provider, - ConnectorID: getPaymentResponse.ConnectorID.String(), - Status: getPaymentResponse.Status, - InitialAmount: getPaymentResponse.InitialAmount, - Amount: getPaymentResponse.Amount, - Scheme: getPaymentResponse.Scheme, - Asset: getPaymentResponse.Asset.String(), - CreatedAt: getPaymentResponse.CreatedAt, - Adjustments: make([]paymentAdjustment, len(getPaymentResponse.Adjustments)), - } - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - GetPayment(gomock.Any(), testCase.expectedPaymentID.String()). - Return(getPaymentResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - GetPayment(gomock.Any(), testCase.expectedPaymentID.String()). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/payments/%s", testCase.paymentID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[paymentResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedPaymentResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/pools.go b/components/payments/cmd/api/internal/api/pools.go deleted file mode 100644 index 52858deb3e..0000000000 --- a/components/payments/cmd/api/internal/api/pools.go +++ /dev/null @@ -1,353 +0,0 @@ -package api - -import ( - "encoding/json" - "math/big" - "net/http" - "strings" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type poolResponse struct { - ID string `json:"id"` - Name string `json:"name"` - Accounts []string `json:"accounts"` -} - -func createPoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "createPoolHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - var createPoolRequest service.CreatePoolRequest - err := json.NewDecoder(r.Body).Decode(&createPoolRequest) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes( - attribute.String("request.name", createPoolRequest.Name), - attribute.String("request.accounts", strings.Join(createPoolRequest.AccountIDs, ",")), - ) - - if err := createPoolRequest.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - pool, err := b.GetService().CreatePool(ctx, &createPoolRequest) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - accounts := make([]string, len(pool.PoolAccounts)) - for i := range pool.PoolAccounts { - accounts[i] = pool.PoolAccounts[i].AccountID.String() - } - - data := &poolResponse{ - ID: pool.ID.String(), - Name: pool.Name, - Accounts: accounts, - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[poolResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func addAccountToPoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "addAccountToPoolHandler") - defer span.End() - - poolID, ok := mux.Vars(r)["poolID"] - if !ok { - var err = errors.New("missing poolID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.poolID", poolID)) - - var addAccountToPoolRequest service.AddAccountToPoolRequest - err := json.NewDecoder(r.Body).Decode(&addAccountToPoolRequest) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes( - attribute.String("request.accountID", addAccountToPoolRequest.AccountID), - ) - - if err := addAccountToPoolRequest.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - err = b.GetService().AddAccountToPool(ctx, poolID, &addAccountToPoolRequest) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func removeAccountFromPoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "removeAccountFromPoolHandler") - defer span.End() - - poolID, ok := mux.Vars(r)["poolID"] - if !ok { - var err = errors.New("missing poolID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.poolID", poolID)) - - accountID, ok := mux.Vars(r)["accountID"] - if !ok { - var err = errors.New("missing accountID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.accountID", accountID)) - - err := b.GetService().RemoveAccountFromPool(ctx, poolID, accountID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func listPoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listPoolHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - query, err := bunpaginate.Extract[storage.ListPoolsQuery](r, func() (*storage.ListPoolsQuery, error) { - options, err := getPagination(r, storage.PoolQuery{}) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListPoolsQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := b.GetService().ListPools(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*poolResponse, len(ret)) - - for i := range ret { - accounts := make([]string, len(ret[i].PoolAccounts)) - for j := range ret[i].PoolAccounts { - accounts[j] = ret[i].PoolAccounts[j].AccountID.String() - } - - data[i] = &poolResponse{ - ID: ret[i].ID.String(), - Name: ret[i].Name, - Accounts: accounts, - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*poolResponse]{ - Cursor: &bunpaginate.Cursor[*poolResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func getPoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "getPoolHandler") - defer span.End() - - poolID, ok := mux.Vars(r)["poolID"] - if !ok { - err := errors.New("missing poolID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.poolID", poolID)) - - pool, err := b.GetService().GetPool(ctx, poolID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - accounts := make([]string, len(pool.PoolAccounts)) - for i := range pool.PoolAccounts { - accounts[i] = pool.PoolAccounts[i].AccountID.String() - } - - data := &poolResponse{ - ID: pool.ID.String(), - Name: pool.Name, - Accounts: accounts, - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[poolResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -type poolBalancesResponse struct { - Balances []*poolBalanceResponse `json:"balances"` -} - -type poolBalanceResponse struct { - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` -} - -func getPoolBalances(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "getPoolBalances") - defer span.End() - - poolID, ok := mux.Vars(r)["poolID"] - if !ok { - var err = errors.New("missing poolID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.poolID", poolID)) - - atTime := r.URL.Query().Get("at") - if atTime == "" { - var err = errors.New("missing atTime") - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - span.SetAttributes(attribute.String("request.atTime", atTime)) - - balance, err := b.GetService().GetPoolBalance(ctx, poolID, atTime) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := &poolBalancesResponse{ - Balances: make([]*poolBalanceResponse, len(balance.Balances)), - } - - for i := range balance.Balances { - data.Balances[i] = &poolBalanceResponse{ - Amount: balance.Balances[i].Amount, - Asset: balance.Balances[i].Asset, - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[poolBalancesResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func deletePoolHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "deletePoolHandler") - defer span.End() - - poolID, ok := mux.Vars(r)["poolID"] - if !ok { - var err = errors.New("missing poolID") - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.poolID", poolID)) - - err := b.GetService().DeletePool(ctx, poolID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} diff --git a/components/payments/cmd/api/internal/api/pools_test.go b/components/payments/cmd/api/internal/api/pools_test.go deleted file mode 100644 index db1db5d20f..0000000000 --- a/components/payments/cmd/api/internal/api/pools_test.go +++ /dev/null @@ -1,1043 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestCreatePool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.CreatePoolRequest - expectedStatusCode int - serviceError error - expectedErrorCode string - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{accountID.String()}, - }, - }, - { - name: "no accounts", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing name", - req: &service.CreatePoolRequest{ - Name: "", - AccountIDs: []string{accountID.String()}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{accountID.String()}, - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{accountID.String()}, - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{accountID.String()}, - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - req: &service.CreatePoolRequest{ - Name: "test", - AccountIDs: []string{accountID.String()}, - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - createPoolResponse := &models.Pool{ - ID: uuid1, - Name: testCase.req.Name, - PoolAccounts: []*models.PoolAccounts{ - { - PoolID: uuid1, - AccountID: accountID, - }, - }, - } - - accounts := make([]string, len(createPoolResponse.PoolAccounts)) - for i := range createPoolResponse.PoolAccounts { - accounts[i] = createPoolResponse.PoolAccounts[i].AccountID.String() - } - expectedCreatePoolResponse := &poolResponse{ - ID: createPoolResponse.ID.String(), - Name: createPoolResponse.Name, - Accounts: accounts, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - CreatePool(gomock.Any(), testCase.req). - Return(createPoolResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - CreatePool(gomock.Any(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, "/pools", bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[poolResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedCreatePoolResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestAddAccountToPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.AddAccountToPoolRequest - poolID string - expectedStatusCode int - serviceError error - expectedErrorCode string - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - req: &service.AddAccountToPoolRequest{ - AccountID: accountID.String(), - }, - poolID: uuid1.String(), - }, - { - name: "missing accountID", - req: &service.AddAccountToPoolRequest{ - AccountID: "", - }, - poolID: uuid1.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing body", - poolID: uuid1.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "err validation from backend", - poolID: uuid1.String(), - req: &service.AddAccountToPoolRequest{ - AccountID: accountID.String(), - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - poolID: uuid1.String(), - req: &service.AddAccountToPoolRequest{ - AccountID: accountID.String(), - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - poolID: uuid1.String(), - req: &service.AddAccountToPoolRequest{ - AccountID: accountID.String(), - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - poolID: uuid1.String(), - req: &service.AddAccountToPoolRequest{ - AccountID: accountID.String(), - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - AddAccountToPool(gomock.Any(), testCase.poolID, testCase.req). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - AddAccountToPool(gomock.Any(), testCase.poolID, testCase.req). - Return(testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/pools/%s/accounts", testCase.poolID), bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } - -} - -func TestRemoveAccountFromPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - accountID string - serviceError error - expectedStatusCode int - expectedErrorCode string - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - accountID: accountID.String(), - }, - { - name: "err validation from backend", - poolID: uuid1.String(), - accountID: accountID.String(), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - poolID: uuid1.String(), - accountID: accountID.String(), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - poolID: uuid1.String(), - accountID: accountID.String(), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - poolID: uuid1.String(), - accountID: accountID.String(), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - RemoveAccountFromPool(gomock.Any(), testCase.poolID, testCase.accountID). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - RemoveAccountFromPool(gomock.Any(), testCase.poolID, testCase.accountID). - Return(testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/pools/%s/accounts/%s", testCase.poolID, testCase.accountID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestListPools(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - pageSize int - expectedQuery storage.ListPoolsQuery - expectedStatusCode int - serviceError error - expectedErrorCode string - } - - testCases := []testCase{ - { - name: "nomimal", - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(100), - ), - pageSize: 100, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"source_account_id\": \"acc1\"}}"}, - }, - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15). - WithQueryBuilder(query.Match("source_account_id", "acc1")), - ), - pageSize: 15, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:asc"}, - }, - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15). - WithSorter(storage.Sorter{}.Add("source_account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - - { - name: "err validation from backend", - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - expectedQuery: storage.NewListPoolsQuery( - storage.NewPaginatedQueryOptions(storage.PoolQuery{}). - WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - poolID1 := uuid.New() - poolID2 := uuid.New() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - accountID1 := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - accountID2 := models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - pools := []models.Pool{ - { - ID: poolID1, - Name: "test1", - PoolAccounts: []*models.PoolAccounts{ - { - PoolID: poolID1, - AccountID: accountID1, - }, - }, - }, - { - ID: poolID2, - Name: "test2", - PoolAccounts: []*models.PoolAccounts{ - { - PoolID: poolID2, - AccountID: accountID1, - }, - { - PoolID: poolID2, - AccountID: accountID2, - }, - }, - }, - } - listPoolsResponse := &bunpaginate.Cursor[models.Pool]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: pools, - } - - accounts1 := make([]string, len(pools[0].PoolAccounts)) - for i := range pools[0].PoolAccounts { - accounts1[i] = pools[0].PoolAccounts[i].AccountID.String() - } - - accounts2 := make([]string, len(pools[1].PoolAccounts)) - for i := range pools[1].PoolAccounts { - accounts2[i] = pools[1].PoolAccounts[i].AccountID.String() - } - expectedListPoolsResponse := []*poolResponse{ - { - ID: pools[0].ID.String(), - Name: pools[0].Name, - Accounts: accounts1, - }, - { - ID: pools[1].ID.String(), - Name: pools[1].Name, - Accounts: accounts2, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListPools(gomock.Any(), testCase.expectedQuery). - Return(listPoolsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListPools(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, "/pools", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*poolResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedListPoolsResponse, resp.Cursor.Data) - require.Equal(t, listPoolsResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listPoolsResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listPoolsResponse.Next, resp.Cursor.Next) - require.Equal(t, listPoolsResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - serviceError error - expectedPoolID uuid.UUID - expectedStatusCode int - expectedErrorCode string - } - - uuid1 := uuid.New() - testCases := []testCase{ - { - name: "nomimal", - poolID: uuid1.String(), - expectedPoolID: uuid1, - }, - { - name: "err validation from backend", - poolID: uuid1.String(), - expectedPoolID: uuid1, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - poolID: uuid1.String(), - expectedPoolID: uuid1, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - poolID: uuid1.String(), - expectedPoolID: uuid1, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - poolID: uuid1.String(), - expectedPoolID: uuid1, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - accountID1 := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - getPoolResponse := &models.Pool{ - ID: uuid1, - Name: "test1", - PoolAccounts: []*models.PoolAccounts{ - { - PoolID: uuid1, - AccountID: accountID1, - }, - }, - } - - accounts := make([]string, len(getPoolResponse.PoolAccounts)) - for i := range getPoolResponse.PoolAccounts { - accounts[i] = getPoolResponse.PoolAccounts[i].AccountID.String() - } - expectedPoolResponse := &poolResponse{ - ID: uuid1.String(), - Name: getPoolResponse.Name, - Accounts: accounts, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - GetPool(gomock.Any(), testCase.poolID). - Return(getPoolResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - GetPool(gomock.Any(), testCase.poolID). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/pools/%s", testCase.poolID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[poolResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedPoolResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetPoolBalance(t *testing.T) { - t.Parallel() - - uuid1 := uuid.New() - type testCase struct { - name string - queryParams url.Values - poolID string - serviceError error - expectedStatusCode int - expectedErrorCode string - } - - atTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).UTC() - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - queryParams: url.Values{ - "at": {atTime.Format(time.RFC3339)}, - }, - }, - { - name: "missing at", - poolID: uuid1.String(), - - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - poolID: uuid1.String(), - queryParams: url.Values{ - "at": {atTime.Format(time.RFC3339)}, - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - poolID: uuid1.String(), - queryParams: url.Values{ - "at": {atTime.Format(time.RFC3339)}, - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - poolID: uuid1.String(), - queryParams: url.Values{ - "at": {atTime.Format(time.RFC3339)}, - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - poolID: uuid1.String(), - queryParams: url.Values{ - "at": {atTime.Format(time.RFC3339)}, - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - getPoolBalanceResponse := &service.GetPoolBalanceResponse{ - Balances: []*service.Balance{ - { - Amount: big.NewInt(100), - Asset: "EUR/2", - }, - { - Amount: big.NewInt(12000), - Asset: "USD/2", - }, - }, - } - - expectedPoolBalancesResponse := &poolBalancesResponse{ - Balances: []*poolBalanceResponse{ - { - Amount: getPoolBalanceResponse.Balances[0].Amount, - Asset: getPoolBalanceResponse.Balances[0].Asset, - }, - { - Amount: getPoolBalanceResponse.Balances[1].Amount, - Asset: getPoolBalanceResponse.Balances[1].Asset, - }, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - GetPoolBalance(gomock.Any(), testCase.poolID, atTime.Format(time.RFC3339)). - Return(getPoolBalanceResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - GetPoolBalance(gomock.Any(), testCase.poolID, atTime.Format(time.RFC3339)). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/pools/%s/balances", testCase.poolID), nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[poolBalancesResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedPoolBalancesResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } - -} - -func TestDeletePool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - serviceError error - expectedStatusCode int - expectedErrorCode string - } - - uuid1 := uuid.New() - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - }, - { - name: "err validation from backend", - poolID: uuid1.String(), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - poolID: uuid1.String(), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - poolID: uuid1.String(), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - poolID: uuid1.String(), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - DeletePool(gomock.Any(), testCase.poolID). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - DeletePool(gomock.Any(), testCase.poolID). - Return(testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/pools/%s", testCase.poolID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/recovery.go b/components/payments/cmd/api/internal/api/recovery.go deleted file mode 100644 index c856fd0f0d..0000000000 --- a/components/payments/cmd/api/internal/api/recovery.go +++ /dev/null @@ -1,23 +0,0 @@ -package api - -import ( - "context" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/pkg/errors" -) - -func recoveryHandler(reporter func(ctx context.Context, e interface{})) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if e := recover(); e != nil { - api.InternalServerError(w, r, errors.New("Internal Server Error")) - reporter(r.Context(), e) - } - }() - h.ServeHTTP(w, r) - }) - } -} diff --git a/components/payments/cmd/api/internal/api/router.go b/components/payments/cmd/api/internal/api/router.go deleted file mode 100644 index b55c237b15..0000000000 --- a/components/payments/cmd/api/internal/api/router.go +++ /dev/null @@ -1,74 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/gorilla/mux" - "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" -) - -func httpRouter( - b backend.Backend, - logger logging.Logger, - serviceInfo api.ServiceInfo, - a auth.Authenticator, - otelTraces bool, -) *mux.Router { - rootMux := mux.NewRouter() - - // We have to keep this recovery handler here to ensure that the health - // endpoint is not panicking - rootMux.Use(recoveryHandler(httpRecoveryFunc(otelTraces))) - rootMux.Use(httpCorsHandler()) - rootMux.Use(httpServeFunc) - rootMux.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r.WithContext(logging.ContextWithLogger(r.Context(), logger))) - }) - }) - - rootMux.Path("/_health").Handler(healthHandler(b)) - - subRouter := rootMux.NewRoute().Subrouter() - if otelTraces { - subRouter.Use(otelmux.Middleware(serviceName)) - // Add a second recovery handler to ensure that the otel middleware - // is catching the error in the trace - rootMux.Use(recoveryHandler(httpRecoveryFunc(otelTraces))) - } - subRouter.Path("/_live").Handler(liveHandler()) - subRouter.Path("/_info").Handler(api.InfoHandler(serviceInfo)) - - authGroup := subRouter.Name("authenticated").Subrouter() - authGroup.Use(auth.Middleware(a)) - - authGroup.Path("/payments").Methods(http.MethodPost).Handler(createPaymentHandler(b)) - authGroup.Path("/payments").Methods(http.MethodGet).Handler(listPaymentsHandler(b)) - authGroup.Path("/payments/{paymentID}").Methods(http.MethodGet).Handler(readPaymentHandler(b)) - authGroup.Path("/payments/{paymentID}/metadata").Methods(http.MethodPatch).Handler(updateMetadataHandler(b)) - - authGroup.Path("/accounts").Methods(http.MethodPost).Handler(createAccountHandler(b)) - authGroup.Path("/accounts").Methods(http.MethodGet).Handler(listAccountsHandler(b)) - authGroup.Path("/accounts/{accountID}").Methods(http.MethodGet).Handler(readAccountHandler(b)) - authGroup.Path("/accounts/{accountID}/balances").Methods(http.MethodGet).Handler(listBalancesForAccount(b)) - - authGroup.Path("/bank-accounts").Methods(http.MethodGet).Handler(listBankAccountsHandler(b)) - authGroup.Path("/bank-accounts/{bankAccountID}").Methods(http.MethodGet).Handler(readBankAccountHandler(b)) - - authGroup.Path("/transfer-initiations").Methods(http.MethodGet).Handler(listTransferInitiationsHandler(b)) - authGroup.Path("/transfer-initiations/{transferID}").Methods(http.MethodGet).Handler(readTransferInitiationHandler(b)) - - authGroup.Path("/pools").Methods(http.MethodPost).Handler(createPoolHandler(b)) - authGroup.Path("/pools").Methods(http.MethodGet).Handler(listPoolHandler(b)) - authGroup.Path("/pools/{poolID}").Methods(http.MethodGet).Handler(getPoolHandler(b)) - authGroup.Path("/pools/{poolID}").Methods(http.MethodDelete).Handler(deletePoolHandler(b)) - authGroup.Path("/pools/{poolID}/accounts").Methods(http.MethodPost).Handler(addAccountToPoolHandler(b)) - authGroup.Path("/pools/{poolID}/accounts/{accountID}").Methods(http.MethodDelete).Handler(removeAccountFromPoolHandler(b)) - authGroup.Path("/pools/{poolID}/balances").Methods(http.MethodGet).Handler(getPoolBalances(b)) - - return rootMux -} diff --git a/components/payments/cmd/api/internal/api/service/accounts.go b/components/payments/cmd/api/internal/api/service/accounts.go deleted file mode 100644 index ee061a9eeb..0000000000 --- a/components/payments/cmd/api/internal/api/service/accounts.go +++ /dev/null @@ -1,126 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/pkg/errors" -) - -type CreateAccountRequest struct { - Reference string `json:"reference"` - ConnectorID string `json:"connectorID"` - CreatedAt time.Time `json:"createdAt"` - DefaultAsset string `json:"defaultAsset"` - AccountName string `json:"accountName"` - Type string `json:"type"` - Metadata map[string]string `json:"metadata"` -} - -func (r *CreateAccountRequest) Validate() error { - if r.Reference == "" { - return errors.New("reference is required") - } - - if r.ConnectorID == "" { - return errors.New("connectorID is required") - } - - if r.CreatedAt.IsZero() || r.CreatedAt.After(time.Now()) { - return errors.New("createdAt is empty or in the future") - } - - if r.AccountName == "" { - return errors.New("accountName is required") - } - - if r.Type == "" { - return errors.New("type is required") - } - - _, err := models.AccountTypeFromString(r.Type) - if err != nil { - return err - } - - return nil -} - -func (s *Service) CreateAccount(ctx context.Context, req *CreateAccountRequest) (*models.Account, error) { - connectorID, err := models.ConnectorIDFromString(req.ConnectorID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - isInstalled, err := s.store.IsConnectorInstalledByConnectorID(ctx, connectorID) - if err != nil { - return nil, newStorageError(err, "checking if connector is installed") - } - - if !isInstalled { - return nil, errors.Wrap(ErrValidation, "connector is not installed") - } - - accountType, err := models.AccountTypeFromString(req.Type) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - raw, err := json.Marshal(req) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - account := &models.Account{ - ID: models.AccountID{ - Reference: req.Reference, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: req.CreatedAt, - Reference: req.Reference, - DefaultAsset: models.Asset(req.DefaultAsset), - AccountName: req.AccountName, - Type: accountType, - Metadata: req.Metadata, - RawData: raw, - } - - err = s.store.UpsertAccounts(ctx, []*models.Account{account}) - if err != nil { - return nil, newStorageError(err, "creating account") - } - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventSavedAccounts(connectorID.Provider, account))) - if err != nil { - return nil, errors.Wrap(err, "publishing message") - } - - return account, nil -} - -func (s *Service) ListAccounts(ctx context.Context, q storage.ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) { - cursor, err := s.store.ListAccounts(ctx, q) - return cursor, newStorageError(err, "listing accounts") -} - -func (s *Service) GetAccount( - ctx context.Context, - accountID string, -) (*models.Account, error) { - _, err := models.AccountIDFromString(accountID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - account, err := s.store.GetAccount(ctx, accountID) - return account, newStorageError(err, "getting account") -} diff --git a/components/payments/cmd/api/internal/api/service/accounts_test.go b/components/payments/cmd/api/internal/api/service/accounts_test.go deleted file mode 100644 index 8ee2de2e66..0000000000 --- a/components/payments/cmd/api/internal/api/service/accounts_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package service - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestCreateAccout(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - request *CreateAccountRequest - isConnectorInstalled bool - expectedError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - { - name: "nominal", - request: &CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - isConnectorInstalled: true, - }, - { - name: "nominal without default asset", - request: &CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - isConnectorInstalled: true, - }, - { - name: "connector not installed", - request: &CreateAccountRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - isConnectorInstalled: false, - expectedError: ErrValidation, - }, - { - name: "invalid connectorID", - request: &CreateAccountRequest{ - Reference: "test", - ConnectorID: "invalid", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - DefaultAsset: "USD/2", - AccountName: "test", - Type: "INTERNAL", - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedError: ErrValidation, - isConnectorInstalled: true, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - store := &MockStore{} - service := New(store.WithIsConnectorInstalled(tc.isConnectorInstalled), &MockPublisher{}, messages.NewMessages("")) - p, err := service.CreateAccount(context.Background(), tc.request) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - require.NotNil(t, p) - } - }) - } -} - -func TestGetAccount(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - accountID string - expectedError error - } - - accountID := models.AccountID{ - Reference: "a1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - accountID: accountID.String(), - expectedError: nil, - }, - { - name: "invalid accountID", - accountID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - _, err := service.GetAccount(context.Background(), tc.accountID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/service/balance.go b/components/payments/cmd/api/internal/api/service/balance.go deleted file mode 100644 index 759703469a..0000000000 --- a/components/payments/cmd/api/internal/api/service/balance.go +++ /dev/null @@ -1,15 +0,0 @@ -package service - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" -) - -func (s *Service) ListBalances(ctx context.Context, q storage.ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) { - cursor, err := s.store.ListBalances(ctx, q) - return cursor, newStorageError(err, "listing balances") -} diff --git a/components/payments/cmd/api/internal/api/service/bank_accounts.go b/components/payments/cmd/api/internal/api/service/bank_accounts.go deleted file mode 100644 index c23612dc91..0000000000 --- a/components/payments/cmd/api/internal/api/service/bank_accounts.go +++ /dev/null @@ -1,21 +0,0 @@ -package service - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -func (s *Service) ListBankAccounts(ctx context.Context, q storage.ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) { - cursor, err := s.store.ListBankAccounts(ctx, q) - return cursor, newStorageError(err, "listing bank accounts") -} - -func (s *Service) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - account, err := s.store.GetBankAccount(ctx, id, expand) - return account, newStorageError(err, "getting bank account") -} diff --git a/components/payments/cmd/api/internal/api/service/errors.go b/components/payments/cmd/api/internal/api/service/errors.go deleted file mode 100644 index 7c1fe16104..0000000000 --- a/components/payments/cmd/api/internal/api/service/errors.go +++ /dev/null @@ -1,38 +0,0 @@ -package service - -import ( - "errors" - "fmt" -) - -var ( - ErrValidation = errors.New("validation error") -) - -type storageError struct { - err error - msg string -} - -func (e *storageError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.err) -} - -func (e *storageError) Is(err error) bool { - _, ok := err.(*storageError) - return ok -} - -func (e *storageError) Unwrap() error { - return e.err -} - -func newStorageError(err error, msg string) error { - if err == nil { - return nil - } - return &storageError{ - err: err, - msg: msg, - } -} diff --git a/components/payments/cmd/api/internal/api/service/payments.go b/components/payments/cmd/api/internal/api/service/payments.go deleted file mode 100644 index 64d9b19931..0000000000 --- a/components/payments/cmd/api/internal/api/service/payments.go +++ /dev/null @@ -1,176 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "math/big" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/pkg/errors" -) - -type CreatePaymentRequest struct { - Reference string `json:"reference"` - ConnectorID string `json:"connectorID"` - CreatedAt time.Time `json:"createdAt"` - Amount *big.Int `json:"amount"` - Type string `json:"type"` - Status string `json:"status"` - Scheme string `json:"scheme"` - Asset string `json:"asset"` - SourceAccountID string `json:"sourceAccountID"` - DestinationAccountID string `json:"destinationAccountID"` -} - -func (r *CreatePaymentRequest) Validate() error { - if r.Reference == "" { - return errors.New("reference is required") - } - - if r.ConnectorID == "" { - return errors.New("connectorID is required") - } - - if r.CreatedAt.IsZero() || r.CreatedAt.After(time.Now()) { - return errors.New("createdAt is empty or in the future") - } - - if r.Amount == nil { - return errors.New("amount is required") - } - - if r.Type == "" { - return errors.New("type is required") - } - - if _, err := models.PaymentTypeFromString(r.Type); err != nil { - return errors.Wrap(err, "invalid type") - } - - if r.Status == "" { - return errors.New("status is required") - } - - if _, err := models.PaymentStatusFromString(r.Status); err != nil { - return errors.Wrap(err, "invalid status") - } - - if r.Scheme == "" { - return errors.New("scheme is required") - } - - if _, err := models.PaymentSchemeFromString(r.Scheme); err != nil { - return errors.Wrap(err, "invalid scheme") - } - - if r.Asset == "" { - return errors.New("asset is required") - } - - _, _, err := models.GetCurrencyAndPrecisionFromAsset(models.Asset(r.Asset)) - if err != nil { - return errors.Wrap(err, "invalid asset") - } - - return nil -} - -func (s *Service) CreatePayment(ctx context.Context, req *CreatePaymentRequest) (*models.Payment, error) { - connectorID, err := models.ConnectorIDFromString(req.ConnectorID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - isInstalled, err := s.store.IsConnectorInstalledByConnectorID(ctx, connectorID) - if err != nil { - return nil, newStorageError(err, "checking if connector is installed") - } - - if !isInstalled { - return nil, errors.Wrap(ErrValidation, "connector is not installed") - } - - var sourceAccountID *models.AccountID - if req.SourceAccountID != "" { - sourceAccountID, err = models.AccountIDFromString(req.SourceAccountID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - } - - var destinationAccountID *models.AccountID - if req.DestinationAccountID != "" { - destinationAccountID, err = models.AccountIDFromString(req.DestinationAccountID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - } - - raw, err := json.Marshal(req) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: req.Reference, - Type: models.PaymentType(req.Type), - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: req.CreatedAt, - Reference: req.Reference, - Amount: req.Amount, - InitialAmount: req.Amount, - Type: models.PaymentType(req.Type), - Status: models.PaymentStatus(req.Status), - Scheme: models.PaymentScheme(req.Scheme), - Asset: models.Asset(req.Asset), - SourceAccountID: sourceAccountID, - DestinationAccountID: destinationAccountID, - RawData: raw, - } - - err = s.store.UpsertPayments(ctx, []*models.Payment{payment}) - if err != nil { - return nil, newStorageError(err, "creating payment") - } - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventSavedPayments(connectorID.Provider, payment))) - if err != nil { - return nil, errors.Wrap(err, "publishing message") - } - - return payment, nil -} - -func (s *Service) ListPayments(ctx context.Context, q storage.ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) { - cursor, err := s.store.ListPayments(ctx, q) - return cursor, newStorageError(err, "listing payments") -} - -func (s *Service) GetPayment(ctx context.Context, id string) (*models.Payment, error) { - _, err := models.PaymentIDFromString(id) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - payment, err := s.store.GetPayment(ctx, id) - return payment, newStorageError(err, "getting payment") -} - -type UpdateMetadataRequest map[string]string - -func (s *Service) UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error { - err := s.store.UpdatePaymentMetadata(ctx, paymentID, metadata) - return newStorageError(err, "updating payment metadata") -} diff --git a/components/payments/cmd/api/internal/api/service/payments_test.go b/components/payments/cmd/api/internal/api/service/payments_test.go deleted file mode 100644 index df51f87d13..0000000000 --- a/components/payments/cmd/api/internal/api/service/payments_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package service - -import ( - "context" - "errors" - "math/big" - "testing" - "time" - - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestCreatePayment(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - request *CreatePaymentRequest - isConnectorInstalled bool - expectedError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - sourceAccountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - destinationAccountID := models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - } - - testCases := []testCase{ - { - name: "nominal", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - isConnectorInstalled: true, - }, - { - name: "connector not installed", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - isConnectorInstalled: false, - expectedError: ErrValidation, - }, - { - name: "nominal without source or destination account ids", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - }, - isConnectorInstalled: true, - }, - { - name: "invalid connectorID", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: "invalid", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - }, - expectedError: ErrValidation, - isConnectorInstalled: true, - }, - { - name: "invalid source account id", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: "invalid", - DestinationAccountID: destinationAccountID.String(), - }, - expectedError: ErrValidation, - isConnectorInstalled: true, - }, - { - name: "invalid destination account id", - request: &CreatePaymentRequest{ - Reference: "test", - ConnectorID: connectorID.String(), - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Amount: big.NewInt(100), - Type: string(models.PaymentTypeTransfer), - Status: string(models.PaymentStatusSucceeded), - Scheme: string(models.PaymentSchemeOther), - Asset: "EUR/2", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: "invalid", - }, - expectedError: ErrValidation, - isConnectorInstalled: true, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - store := &MockStore{} - service := New(store.WithIsConnectorInstalled(tc.isConnectorInstalled), &MockPublisher{}, messages.NewMessages("")) - p, err := service.CreatePayment(context.Background(), tc.request) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - require.NotNil(t, p) - } - }) - } -} - -func TestGetPayment(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - paymentID string - expectedError error - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - paymentID: paymentID.String(), - expectedError: nil, - }, - { - name: "invalid paymentID", - paymentID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - _, err := service.GetPayment(context.Background(), tc.paymentID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/service/ping.go b/components/payments/cmd/api/internal/api/service/ping.go deleted file mode 100644 index e33dd7ba06..0000000000 --- a/components/payments/cmd/api/internal/api/service/ping.go +++ /dev/null @@ -1,5 +0,0 @@ -package service - -func (s *Service) Ping() error { - return s.store.Ping() -} diff --git a/components/payments/cmd/api/internal/api/service/pools.go b/components/payments/cmd/api/internal/api/service/pools.go deleted file mode 100644 index a0e82a7af4..0000000000 --- a/components/payments/cmd/api/internal/api/service/pools.go +++ /dev/null @@ -1,259 +0,0 @@ -package service - -import ( - "context" - "math/big" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -type CreatePoolRequest struct { - Name string `json:"name"` - AccountIDs []string `json:"accountIDs"` -} - -func (c *CreatePoolRequest) Validate() error { - if c.Name == "" { - return errors.New("name is required") - } - - if len(c.AccountIDs) == 0 { - return errors.New("accountIDs is required") - } - - return nil -} - -func (s *Service) CreatePool( - ctx context.Context, - req *CreatePoolRequest, -) (*models.Pool, error) { - pool := &models.Pool{ - Name: req.Name, - CreatedAt: time.Now().UTC(), - } - - err := s.store.CreatePool(ctx, pool) - if err != nil { - return nil, newStorageError(err, "creating pool") - } - - poolAccounts := make([]*models.PoolAccounts, len(req.AccountIDs)) - for i, accountID := range req.AccountIDs { - aID, err := models.AccountIDFromString(accountID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - poolAccounts[i] = &models.PoolAccounts{ - PoolID: pool.ID, - AccountID: *aID, - } - } - - err = s.store.AddAccountsToPool(ctx, poolAccounts) - if err != nil { - return nil, newStorageError(err, "adding accounts to pool") - } - pool.PoolAccounts = poolAccounts - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventSavedPool(pool))) - if err != nil { - return nil, errors.Wrap(err, "publishing message") - } - - return pool, nil -} - -type AddAccountToPoolRequest struct { - AccountID string `json:"accountID"` -} - -func (c *AddAccountToPoolRequest) Validate() error { - if c.AccountID == "" { - return errors.New("accountID is required") - } - - return nil -} - -func (s *Service) AddAccountToPool( - ctx context.Context, - poolID string, - req *AddAccountToPoolRequest, -) error { - id, err := uuid.Parse(poolID) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - aID, err := models.AccountIDFromString(req.AccountID) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - if err := s.store.AddAccountToPool(ctx, &models.PoolAccounts{ - PoolID: id, - AccountID: *aID, - }); err != nil { - return newStorageError(err, "adding account to pool") - } - - pool, err := s.store.GetPool(ctx, id) - if err != nil { - return newStorageError(err, "getting pool") - } - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventSavedPool(pool))) - if err != nil { - return errors.Wrap(err, "publishing message") - } - - return nil -} - -func (s *Service) RemoveAccountFromPool( - ctx context.Context, - poolID string, - accountID string, -) error { - id, err := uuid.Parse(poolID) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - aID, err := models.AccountIDFromString(accountID) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - if err := s.store.RemoveAccountFromPool(ctx, &models.PoolAccounts{ - PoolID: id, - AccountID: *aID, - }); err != nil { - return newStorageError(err, "removing account from pool") - } - - pool, err := s.store.GetPool(ctx, id) - if err != nil { - return newStorageError(err, "getting pool") - } - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventSavedPool(pool))) - if err != nil { - return errors.Wrap(err, "publishing message") - } - - return nil -} - -func (s *Service) ListPools(ctx context.Context, q storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) { - cursor, err := s.store.ListPools(ctx, q) - return cursor, newStorageError(err, "listing pools") -} - -func (s *Service) GetPool( - ctx context.Context, - poolID string, -) (*models.Pool, error) { - id, err := uuid.Parse(poolID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - pool, err := s.store.GetPool(ctx, id) - return pool, newStorageError(err, "getting pool") -} - -type GetPoolBalanceResponse struct { - Balances []*Balance -} - -type Balance struct { - Amount *big.Int - Asset string -} - -func (s *Service) GetPoolBalance( - ctx context.Context, - poolID string, - atTime string, -) (*GetPoolBalanceResponse, error) { - id, err := uuid.Parse(poolID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - at, err := time.Parse(time.RFC3339, atTime) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - pool, err := s.store.GetPool(ctx, id) - if err != nil { - return nil, newStorageError(err, "getting pool") - } - - res := make(map[string]*big.Int) - for _, poolAccount := range pool.PoolAccounts { - balances, err := s.store.GetBalancesAt(ctx, poolAccount.AccountID, at) - if err != nil { - return nil, newStorageError(err, "getting balances") - } - - for _, balance := range balances { - amount, ok := res[balance.Asset.String()] - if !ok { - amount = big.NewInt(0) - } - - amount.Add(amount, balance.Balance) - res[balance.Asset.String()] = amount - } - } - - balances := make([]*Balance, 0, len(res)) - for asset, amount := range res { - balances = append(balances, &Balance{ - Asset: asset, - Amount: amount, - }) - } - - return &GetPoolBalanceResponse{ - Balances: balances, - }, nil -} - -func (s *Service) DeletePool( - ctx context.Context, - poolID string, -) error { - id, err := uuid.Parse(poolID) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - if err := s.store.DeletePool(ctx, id); err != nil { - return newStorageError(err, "deleting pool") - } - - err = s.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, s.messages.NewEventDeletePool(id))) - if err != nil { - return errors.Wrap(err, "publishing message") - } - - return nil -} diff --git a/components/payments/cmd/api/internal/api/service/pools_test.go b/components/payments/cmd/api/internal/api/service/pools_test.go deleted file mode 100644 index e7b3a22874..0000000000 --- a/components/payments/cmd/api/internal/api/service/pools_test.go +++ /dev/null @@ -1,349 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "errors" - "math/big" - "testing" - "time" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestCreatePool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - request *CreatePoolRequest - expectedError error - } - - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - request: &CreatePoolRequest{ - Name: "pool1", - AccountIDs: []string{accountID.String()}, - }, - expectedError: nil, - }, - { - name: "invalid accountID", - request: &CreatePoolRequest{ - Name: "pool1", - AccountIDs: []string{"invalid"}, - }, - expectedError: ErrValidation, - }, - } - - m := &MockPublisher{} - messageChan := make(chan *message.Message, 1) - service := New(&MockStore{}, m.WithMessagesChan(messageChan), messages.NewMessages("")) - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - pool, err := service.CreatePool(context.Background(), tc.request) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - require.NotNil(t, pool) - - require.Eventually(t, func() bool { - select { - case msg := <-messageChan: - type poolPayload struct { - Payload struct { - ID string `json:"id"` - Name string `json:"name"` - AccountIDS []string `json:"accountIDs"` - } `json:"payload"` - } - - var p poolPayload - require.NoError(t, json.Unmarshal(msg.Payload, &p)) - require.Equal(t, pool.ID.String(), p.Payload.ID) - require.Equal(t, tc.request.Name, p.Payload.Name) - require.Equal(t, tc.request.AccountIDs, p.Payload.AccountIDS) - return true - } - }, 10*time.Second, 100*time.Millisecond) - } - }) - } -} - -func TestGetPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - expectedError error - } - - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - expectedError: nil, - }, - { - name: "invalid poolID", - poolID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - _, err := service.GetPool(context.Background(), tc.poolID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestAddAccountToPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - accountID string - expectedError error - } - - uuid1 := uuid.New() - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - accountID: accountID.String(), - expectedError: nil, - }, - { - name: "invalid poolID", - poolID: "invalid", - accountID: accountID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid accountID", - poolID: uuid1.String(), - accountID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := service.AddAccountToPool(context.Background(), tc.poolID, &AddAccountToPoolRequest{ - AccountID: tc.accountID, - }) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } - -} - -func TestRemoveAccountFromPool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - accountID string - expectedError error - } - - uuid1 := uuid.New() - accountID := models.AccountID{ - Reference: "acc1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - accountID: accountID.String(), - expectedError: nil, - }, - { - name: "invalid poolID", - poolID: "invalid", - accountID: accountID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid accountID", - poolID: uuid1.String(), - accountID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := service.RemoveAccountFromPool(context.Background(), tc.poolID, tc.accountID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestDeletePool(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - expectedError error - } - - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - expectedError: nil, - }, - { - name: "invalid poolID", - poolID: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := service.DeletePool(context.Background(), tc.poolID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } - -} - -func TestGetPoolBalance(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - poolID string - atTime string - expectedError error - } - - uuid1 := uuid.New() - - testCases := []testCase{ - { - name: "nominal", - poolID: uuid1.String(), - atTime: "2021-01-01T00:00:00Z", - }, - { - name: "invalid poolID", - poolID: "invalid", - atTime: "2021-01-01T00:00:00Z", - expectedError: ErrValidation, - }, - { - name: "invalid atTime", - poolID: uuid1.String(), - atTime: "invalid", - expectedError: ErrValidation, - }, - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages("")) - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - expectedResponseMap := map[string]*big.Int{ - "EUR/2": big.NewInt(200), - "USD/2": big.NewInt(300), - } - - balances, err := service.GetPoolBalance(context.Background(), tc.poolID, tc.atTime) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - - require.Equal(t, 2, len(balances.Balances)) - for _, balance := range balances.Balances { - expectedAmount, ok := expectedResponseMap[balance.Asset] - require.True(t, ok) - require.Equal(t, expectedAmount, balance.Amount) - } - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/service/service.go b/components/payments/cmd/api/internal/api/service/service.go deleted file mode 100644 index 387e594f0a..0000000000 --- a/components/payments/cmd/api/internal/api/service/service.go +++ /dev/null @@ -1,53 +0,0 @@ -package service - -import ( - "context" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -type Store interface { - Ping() error - IsConnectorInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) - UpsertAccounts(ctx context.Context, accounts []*models.Account) error - ListAccounts(ctx context.Context, q storage.ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) - GetAccount(ctx context.Context, id string) (*models.Account, error) - ListBalances(ctx context.Context, q storage.ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) - GetBalancesAt(ctx context.Context, accountID models.AccountID, at time.Time) ([]*models.Balance, error) - ListBankAccounts(ctx context.Context, q storage.ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) - GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) - UpsertPayments(ctx context.Context, payments []*models.Payment) error - ListPayments(ctx context.Context, q storage.ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) - GetPayment(ctx context.Context, id string) (*models.Payment, error) - UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error - ListTransferInitiations(ctx context.Context, q storage.ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) - GetTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) - CreatePool(ctx context.Context, pool *models.Pool) error - AddAccountToPool(ctx context.Context, poolAccount *models.PoolAccounts) error - AddAccountsToPool(ctx context.Context, poolAccounts []*models.PoolAccounts) error - RemoveAccountFromPool(ctx context.Context, poolAccount *models.PoolAccounts) error - ListPools(ctx context.Context, q storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) - GetPool(ctx context.Context, poolID uuid.UUID) (*models.Pool, error) - DeletePool(ctx context.Context, poolID uuid.UUID) error -} - -type Service struct { - store Store - publisher message.Publisher - messages *messages.Messages -} - -func New(store Store, publisher message.Publisher, messages *messages.Messages) *Service { - return &Service{ - store: store, - publisher: publisher, - messages: messages, - } -} diff --git a/components/payments/cmd/api/internal/api/service/service_test.go b/components/payments/cmd/api/internal/api/service/service_test.go deleted file mode 100644 index bf31a11393..0000000000 --- a/components/payments/cmd/api/internal/api/service/service_test.go +++ /dev/null @@ -1,180 +0,0 @@ -package service - -import ( - "context" - "math/big" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -type MockStore struct { - isConnectorInstalled bool -} - -func (m *MockStore) WithIsConnectorInstalled(isConnectorInstalled bool) *MockStore { - m.isConnectorInstalled = isConnectorInstalled - return m -} - -func (m *MockStore) Ping() error { - return nil -} - -func (m *MockStore) IsConnectorInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - return m.isConnectorInstalled, nil -} - -func (m *MockStore) ListBalances(ctx context.Context, q storage.ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) { - return nil, nil -} - -func (m *MockStore) GetBalancesAt(ctx context.Context, accountID models.AccountID, atTime time.Time) ([]*models.Balance, error) { - return []*models.Balance{ - { - AccountID: accountID, - Asset: "EUR/2", - Balance: big.NewInt(100), - }, - { - AccountID: accountID, - Asset: "USD/2", - Balance: big.NewInt(150), - }, - }, nil -} - -func (m *MockStore) UpsertAccounts(ctx context.Context, accounts []*models.Account) error { - return nil -} - -func (m *MockStore) ListAccounts(ctx context.Context, q storage.ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) { - return nil, nil -} - -func (m *MockStore) GetAccount(ctx context.Context, id string) (*models.Account, error) { - return nil, nil -} - -func (m *MockStore) ListBankAccounts(ctx context.Context, q storage.ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) { - return nil, nil -} - -func (m *MockStore) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - return nil, nil -} - -func (m *MockStore) UpsertPayments(ctx context.Context, payments []*models.Payment) error { - return nil -} - -func (m *MockStore) ListPayments(ctx context.Context, q storage.ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) { - return nil, nil -} - -func (m *MockStore) GetPayment(ctx context.Context, id string) (*models.Payment, error) { - return nil, nil -} - -func (m *MockStore) UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error { - return nil -} - -func (m *MockStore) ListTransferInitiations(ctx context.Context, q storage.ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) { - return nil, nil -} - -func (m *MockStore) GetTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - return nil, nil -} - -func (m *MockStore) CreatePool(ctx context.Context, pool *models.Pool) error { - return nil -} - -func (m *MockStore) AddAccountsToPool(ctx context.Context, poolAccounts []*models.PoolAccounts) error { - return nil -} - -func (m *MockStore) AddAccountToPool(ctx context.Context, poolAccount *models.PoolAccounts) error { - return nil -} - -func (m *MockStore) RemoveAccountFromPool(ctx context.Context, poolAccount *models.PoolAccounts) error { - return nil -} - -func (m *MockStore) ListPools(ctx context.Context, q storage.ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) { - return nil, nil -} - -func (m *MockStore) GetPool(ctx context.Context, poolID uuid.UUID) (*models.Pool, error) { - return &models.Pool{ - ID: poolID, - Name: "test", - PoolAccounts: []*models.PoolAccounts{ - { - PoolID: poolID, - AccountID: models.AccountID{ - Reference: "acc1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - }, - }, - { - PoolID: poolID, - AccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - }, - }, - }, - }, nil -} - -func (m *MockStore) DeletePool(ctx context.Context, poolID uuid.UUID) error { - return nil -} - -type MockPublisher struct { - errorToSend error - messagesChan chan *message.Message -} - -func (m *MockPublisher) WithError(err error) *MockPublisher { - m.errorToSend = err - return m -} - -func (m *MockPublisher) WithMessagesChan(messagesChan chan *message.Message) *MockPublisher { - m.messagesChan = messagesChan - return m -} - -func (m *MockPublisher) Publish(topic string, messages ...*message.Message) error { - if m.errorToSend != nil { - return m.errorToSend - } - - if m.messagesChan != nil { - for _, msg := range messages { - m.messagesChan <- msg - } - } - - return nil -} - -func (m *MockPublisher) Close() error { - return nil -} diff --git a/components/payments/cmd/api/internal/api/service/transfer_initiations.go b/components/payments/cmd/api/internal/api/service/transfer_initiations.go deleted file mode 100644 index 4b8ea64191..0000000000 --- a/components/payments/cmd/api/internal/api/service/transfer_initiations.go +++ /dev/null @@ -1,20 +0,0 @@ -package service - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" -) - -func (s *Service) ListTransferInitiations(ctx context.Context, q storage.ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) { - cursor, err := s.store.ListTransferInitiations(ctx, q) - return cursor, newStorageError(err, "listing transfer initiations") -} - -func (s *Service) ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - transferInitiation, err := s.store.GetTransferInitiation(ctx, id) - return transferInitiation, newStorageError(err, "reading transfer initiation") -} diff --git a/components/payments/cmd/api/internal/api/transfer_initiation.go b/components/payments/cmd/api/internal/api/transfer_initiation.go deleted file mode 100644 index ea1a0670be..0000000000 --- a/components/payments/cmd/api/internal/api/transfer_initiation.go +++ /dev/null @@ -1,208 +0,0 @@ -package api - -import ( - "encoding/json" - "math/big" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/api/internal/api/backend" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" -) - -type transferInitiationResponse struct { - ID string `json:"id"` - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - ScheduledAt time.Time `json:"scheduledAt"` - Description string `json:"description"` - SourceAccountID string `json:"sourceAccountID"` - DestinationAccountID string `json:"destinationAccountID"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` - Type string `json:"type"` - Amount *big.Int `json:"amount"` - InitialAmount *big.Int `json:"initialAmount"` - Asset string `json:"asset"` - Status string `json:"status"` - Error string `json:"error"` - Metadata map[string]string `json:"metadata"` -} - -type transferInitiationPaymentsResponse struct { - PaymentID string `json:"paymentID"` - CreatedAt time.Time `json:"createdAt"` - Status string `json:"status"` - Error string `json:"error"` -} - -type transferInitiationAdjustmentsResponse struct { - AdjustmentID string `json:"adjustmentID"` - CreatedAt time.Time `json:"createdAt"` - Status string `json:"status"` - Error string `json:"error"` - Metadata map[string]string `json:"metadata"` -} - -type readTransferInitiationResponse struct { - transferInitiationResponse - RelatedPayments []*transferInitiationPaymentsResponse `json:"relatedPayments"` - RelatedAdjustments []*transferInitiationAdjustmentsResponse `json:"relatedAdjustments"` -} - -func readTransferInitiationHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readTransferInitiationHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - transferID, err := models.TransferInitiationIDFromString(mux.Vars(r)["transferID"]) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("request.transferID", transferID.String())) - - ret, err := b.GetService().ReadTransferInitiation(ctx, transferID) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := &readTransferInitiationResponse{ - transferInitiationResponse: transferInitiationResponse{ - ID: ret.ID.String(), - Reference: ret.ID.Reference, - CreatedAt: ret.CreatedAt, - ScheduledAt: ret.ScheduledAt, - Description: ret.Description, - SourceAccountID: ret.SourceAccountID.String(), - DestinationAccountID: ret.DestinationAccountID.String(), - ConnectorID: ret.ConnectorID.String(), - Provider: ret.Provider.String(), - Type: ret.Type.String(), - Amount: ret.Amount, - InitialAmount: ret.InitialAmount, - Asset: ret.Asset.String(), - Metadata: ret.Metadata, - }, - } - - if len(ret.RelatedAdjustments) > 0 { - // Take the status and error from the last adjustment - data.Status = ret.RelatedAdjustments[0].Status.String() - data.Error = ret.RelatedAdjustments[0].Error - } - - for _, adjustments := range ret.RelatedAdjustments { - data.RelatedAdjustments = append(data.RelatedAdjustments, &transferInitiationAdjustmentsResponse{ - AdjustmentID: adjustments.ID.String(), - CreatedAt: adjustments.CreatedAt, - Status: adjustments.Status.String(), - Error: adjustments.Error, - Metadata: adjustments.Metadata, - }) - } - - for _, payments := range ret.RelatedPayments { - data.RelatedPayments = append(data.RelatedPayments, &transferInitiationPaymentsResponse{ - PaymentID: payments.PaymentID.String(), - CreatedAt: payments.CreatedAt, - Status: payments.Status.String(), - Error: payments.Error, - }) - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[readTransferInitiationResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func listTransferInitiationsHandler(b backend.Backend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listTransferInitiationsHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - query, err := bunpaginate.Extract[storage.ListTransferInitiationsQuery](r, func() (*storage.ListTransferInitiationsQuery, error) { - options, err := getPagination(r, storage.TransferInitiationQuery{}) - if err != nil { - return nil, err - } - return pointer.For(storage.NewListTransferInitiationsQuery(*options)), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - cursor, err := b.GetService().ListTransferInitiations(ctx, *query) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - ret := cursor.Data - data := make([]*transferInitiationResponse, len(ret)) - for i := range ret { - ret[i].SortRelatedAdjustments() - data[i] = &transferInitiationResponse{ - ID: ret[i].ID.String(), - Reference: ret[i].ID.Reference, - CreatedAt: ret[i].CreatedAt, - ScheduledAt: ret[i].ScheduledAt, - Description: ret[i].Description, - SourceAccountID: ret[i].SourceAccountID.String(), - DestinationAccountID: ret[i].DestinationAccountID.String(), - Provider: ret[i].Provider.String(), - ConnectorID: ret[i].ConnectorID.String(), - Type: ret[i].Type.String(), - Amount: ret[i].Amount, - InitialAmount: ret[i].InitialAmount, - Asset: ret[i].Asset.String(), - Metadata: ret[i].Metadata, - } - - if len(ret[i].RelatedAdjustments) > 0 { - // Take the status and error from the last adjustment - data[i].Status = ret[i].RelatedAdjustments[0].Status.String() - data[i].Error = ret[i].RelatedAdjustments[0].Error - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[*transferInitiationResponse]{ - Cursor: &bunpaginate.Cursor[*transferInitiationResponse]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} diff --git a/components/payments/cmd/api/internal/api/transfer_initiation_test.go b/components/payments/cmd/api/internal/api/transfer_initiation_test.go deleted file mode 100644 index 93fdf5bf34..0000000000 --- a/components/payments/cmd/api/internal/api/transfer_initiation_test.go +++ /dev/null @@ -1,677 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/api/service" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestListTransferInitiations(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - queryParams url.Values - pageSize int - expectedQuery storage.ListTransferInitiationsQuery - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nomimal", - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - pageSize: 15, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(100), - ), - pageSize: 100, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "invalid query builder json", - queryParams: url.Values{ - "query": {"invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "valid query builder json", - queryParams: url.Values{ - "query": {"{\"$match\": {\"source_account_id\": \"acc1\"}}"}, - }, - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15). - WithQueryBuilder(query.Match("source_account_id", "acc1")), - ), - pageSize: 15, - }, - { - name: "valid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:asc"}, - }, - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15). - WithSorter(storage.Sorter{}.Add("source_account_id", storage.SortOrderAsc)), - ), - pageSize: 15, - }, - { - name: "invalid sorter", - queryParams: url.Values{ - "sort": {"source_account_id:invalid"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "err validation from backend", - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - expectedQuery: storage.NewListTransferInitiationsQuery( - storage.NewPaginatedQueryOptions(storage.TransferInitiationQuery{}). - WithPageSize(15), - ), - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - tfs := []models.TransferInitiation{ - { - ID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 30, 0, 0, time.UTC), - Description: "test1", - Type: models.TransferInitiationTypePayout, - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(100), - Asset: models.Asset("EUR/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 45, 0, 0, time.UTC), - Status: models.TransferInitiationStatusProcessed, - }, - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 40, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - RelatedPayments: []*models.TransferInitiationPayment{ - { - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 30, 0, 0, time.UTC), - Status: models.TransferInitiationStatusProcessed, - Error: "", - }, - }, - Metadata: map[string]string{ - "foo": "bar", - }, - }, - { - ID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 9, 30, 0, 0, time.UTC), - Description: "test2", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "acc3", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc4", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(2000), - Asset: models.Asset("USD/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 45, 0, 0, time.UTC), - Status: models.TransferInitiationStatusFailed, - Error: "error", - }, - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 40, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - }, - } - listTFsResponse := &bunpaginate.Cursor[models.TransferInitiation]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: tfs, - } - - expectedTFsResponse := []*transferInitiationResponse{ - { - ID: tfs[0].ID.String(), - Reference: tfs[0].ID.Reference, - CreatedAt: tfs[0].CreatedAt, - ScheduledAt: tfs[0].ScheduledAt, - Description: tfs[0].Description, - SourceAccountID: tfs[0].SourceAccountID.String(), - DestinationAccountID: tfs[0].DestinationAccountID.String(), - Provider: tfs[0].Provider.String(), - Type: tfs[0].Type.String(), - Amount: tfs[0].Amount, - Asset: tfs[0].Asset.String(), - Status: models.TransferInitiationStatusProcessed.String(), - ConnectorID: tfs[0].ConnectorID.String(), - Error: "", - Metadata: tfs[0].Metadata, - }, - { - ID: tfs[1].ID.String(), - Reference: tfs[1].ID.Reference, - CreatedAt: tfs[1].CreatedAt, - ScheduledAt: tfs[1].ScheduledAt, - Description: tfs[1].Description, - SourceAccountID: tfs[1].SourceAccountID.String(), - DestinationAccountID: tfs[1].DestinationAccountID.String(), - Provider: tfs[1].Provider.String(), - Type: tfs[1].Type.String(), - Amount: tfs[1].Amount, - Asset: tfs[1].Asset.String(), - ConnectorID: tfs[1].ConnectorID.String(), - Status: models.TransferInitiationStatusFailed.String(), - Error: "error", - Metadata: tfs[1].Metadata, - }, - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListTransferInitiations(gomock.Any(), testCase.expectedQuery). - Return(listTFsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListTransferInitiations(gomock.Any(), testCase.expectedQuery). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, "/transfer-initiations", nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[*transferInitiationResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedTFsResponse, resp.Cursor.Data) - require.Equal(t, listTFsResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listTFsResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listTFsResponse.Next, resp.Cursor.Next) - require.Equal(t, listTFsResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestGetTransferInitiation(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - tfID1 := models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - } - tfID2 := models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - } - - type testCase struct { - name string - tfID string - expectedTFID models.TransferInitiationID - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nomimal acc1", - tfID: tfID1.String(), - expectedTFID: tfID1, - }, - { - name: "nomimal acc2", - tfID: tfID2.String(), - expectedTFID: tfID2, - }, - { - name: "invalid tf ID", - tfID: "invalid", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "err validation from backend", - tfID: tfID1.String(), - expectedTFID: tfID1, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "ErrNotFound from storage", - tfID: tfID1.String(), - expectedTFID: tfID1, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "ErrDuplicateKeyValue from storage", - tfID: tfID1.String(), - expectedTFID: tfID1, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "other storage errors from storage", - tfID: tfID1.String(), - expectedTFID: tfID1, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - var getTransferInitiationResponse *models.TransferInitiation - var expectedTransferInitiationResponse *readTransferInitiationResponse - if testCase.expectedTFID == tfID1 { - getTransferInitiationResponse = &models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 30, 0, 0, time.UTC), - Description: "test1", - Type: models.TransferInitiationTypePayout, - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(100), - Asset: models.Asset("EUR/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 45, 0, 0, time.UTC), - Status: models.TransferInitiationStatusProcessed, - }, - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 40, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - RelatedPayments: []*models.TransferInitiationPayment{ - { - TransferInitiationID: models.TransferInitiationID{ - Reference: "t1", - ConnectorID: connectorID, - }, - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 30, 0, 0, time.UTC), - Status: models.TransferInitiationStatusProcessed, - Error: "", - }, - }, - Metadata: map[string]string{ - "foo": "bar", - }, - } - - expectedTransferInitiationResponse = &readTransferInitiationResponse{ - transferInitiationResponse: transferInitiationResponse{ - ID: getTransferInitiationResponse.ID.String(), - Reference: getTransferInitiationResponse.ID.Reference, - CreatedAt: getTransferInitiationResponse.CreatedAt, - ScheduledAt: getTransferInitiationResponse.ScheduledAt, - Description: getTransferInitiationResponse.Description, - SourceAccountID: getTransferInitiationResponse.SourceAccountID.String(), - DestinationAccountID: getTransferInitiationResponse.DestinationAccountID.String(), - Provider: getTransferInitiationResponse.Provider.String(), - Type: getTransferInitiationResponse.Type.String(), - Amount: getTransferInitiationResponse.Amount, - ConnectorID: getTransferInitiationResponse.ConnectorID.String(), - Asset: getTransferInitiationResponse.Asset.String(), - Status: models.TransferInitiationStatusProcessed.String(), - Error: "", - Metadata: getTransferInitiationResponse.Metadata, - }, - RelatedPayments: []*transferInitiationPaymentsResponse{ - { - PaymentID: getTransferInitiationResponse.RelatedPayments[0].PaymentID.String(), - CreatedAt: getTransferInitiationResponse.RelatedPayments[0].CreatedAt, - Status: getTransferInitiationResponse.RelatedPayments[0].Status.String(), - Error: getTransferInitiationResponse.RelatedPayments[0].Error, - }, - }, - RelatedAdjustments: []*transferInitiationAdjustmentsResponse{ - { - AdjustmentID: getTransferInitiationResponse.RelatedAdjustments[0].ID.String(), - CreatedAt: getTransferInitiationResponse.RelatedAdjustments[0].CreatedAt, - Status: getTransferInitiationResponse.RelatedAdjustments[0].Status.String(), - Error: getTransferInitiationResponse.RelatedAdjustments[0].Error, - Metadata: getTransferInitiationResponse.RelatedAdjustments[0].Metadata, - }, - { - AdjustmentID: getTransferInitiationResponse.RelatedAdjustments[1].ID.String(), - CreatedAt: getTransferInitiationResponse.RelatedAdjustments[1].CreatedAt, - Status: getTransferInitiationResponse.RelatedAdjustments[1].Status.String(), - Error: getTransferInitiationResponse.RelatedAdjustments[1].Error, - Metadata: getTransferInitiationResponse.RelatedAdjustments[1].Metadata, - }, - }, - } - } else { - getTransferInitiationResponse = &models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 9, 30, 0, 0, time.UTC), - Description: "test2", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "acc3", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc4", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(2000), - Asset: models.Asset("USD/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 45, 0, 0, time.UTC), - Status: models.TransferInitiationStatusFailed, - Error: "error", - }, - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "t2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 40, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - } - expectedTransferInitiationResponse = &readTransferInitiationResponse{ - transferInitiationResponse: transferInitiationResponse{ - ID: getTransferInitiationResponse.ID.String(), - Reference: getTransferInitiationResponse.ID.Reference, - CreatedAt: getTransferInitiationResponse.CreatedAt, - ScheduledAt: getTransferInitiationResponse.ScheduledAt, - Description: getTransferInitiationResponse.Description, - SourceAccountID: getTransferInitiationResponse.SourceAccountID.String(), - DestinationAccountID: getTransferInitiationResponse.DestinationAccountID.String(), - Provider: getTransferInitiationResponse.Provider.String(), - Type: getTransferInitiationResponse.Type.String(), - Amount: getTransferInitiationResponse.Amount, - ConnectorID: getTransferInitiationResponse.ConnectorID.String(), - Asset: getTransferInitiationResponse.Asset.String(), - Status: models.TransferInitiationStatusFailed.String(), - Error: "error", - Metadata: getTransferInitiationResponse.Metadata, - }, - RelatedAdjustments: []*transferInitiationAdjustmentsResponse{ - { - AdjustmentID: getTransferInitiationResponse.RelatedAdjustments[0].ID.String(), - CreatedAt: getTransferInitiationResponse.RelatedAdjustments[0].CreatedAt, - Status: getTransferInitiationResponse.RelatedAdjustments[0].Status.String(), - Error: getTransferInitiationResponse.RelatedAdjustments[0].Error, - Metadata: getTransferInitiationResponse.RelatedAdjustments[0].Metadata, - }, - { - AdjustmentID: getTransferInitiationResponse.RelatedAdjustments[1].ID.String(), - CreatedAt: getTransferInitiationResponse.RelatedAdjustments[1].CreatedAt, - Status: getTransferInitiationResponse.RelatedAdjustments[1].Status.String(), - Error: getTransferInitiationResponse.RelatedAdjustments[1].Error, - Metadata: getTransferInitiationResponse.RelatedAdjustments[1].Metadata, - }, - }, - } - } - - backend, mockService := newTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ReadTransferInitiation(gomock.Any(), testCase.expectedTFID). - Return(getTransferInitiationResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ReadTransferInitiation(gomock.Any(), testCase.expectedTFID). - Return(nil, testCase.serviceError) - } - - router := httpRouter(backend, logging.Testing(), sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), false) - - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/transfer-initiations/%s", testCase.tfID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[readTransferInitiationResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedTransferInitiationResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/api/internal/api/utils.go b/components/payments/cmd/api/internal/api/utils.go deleted file mode 100644 index 86d06917c0..0000000000 --- a/components/payments/cmd/api/internal/api/utils.go +++ /dev/null @@ -1,76 +0,0 @@ -package api - -import ( - "io" - "net/http" - "strings" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/pkg/errors" -) - -func getQueryBuilder(r *http.Request) (query.Builder, error) { - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, err - } - - if len(data) > 0 { - return query.ParseJSON(string(data)) - } else { - // In order to be backward compatible - return query.ParseJSON(r.URL.Query().Get("query")) - } -} - -func getSorter(r *http.Request) (storage.Sorter, error) { - var sorter storage.Sorter - - if sortParams := r.URL.Query()["sort"]; sortParams != nil { - for _, s := range sortParams { - parts := strings.SplitN(s, ":", 2) - - var order storage.SortOrder - - if len(parts) > 1 { - //nolint:goconst // allow duplicate string - switch parts[1] { - case "asc", "ASC": - order = storage.SortOrderAsc - case "dsc", "desc", "DSC", "DESC": - order = storage.SortOrderDesc - default: - return sorter, errors.New("sort order not well specified, got " + parts[1]) - } - } - - column := parts[0] - - sorter = sorter.Add(column, order) - } - } - - return sorter, nil -} - -func getPagination[T any](r *http.Request, options T) (*storage.PaginatedQueryOptions[T], error) { - qb, err := getQueryBuilder(r) - if err != nil { - return nil, err - } - - sorter, err := getSorter(r) - if err != nil { - return nil, err - } - - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - return nil, err - } - - return pointer.For(storage.NewPaginatedQueryOptions(options).WithQueryBuilder(qb).WithSorter(sorter).WithPageSize(pageSize)), nil -} diff --git a/components/payments/cmd/api/internal/storage/accounts.go b/components/payments/cmd/api/internal/storage/accounts.go deleted file mode 100644 index 8a99efbc03..0000000000 --- a/components/payments/cmd/api/internal/storage/accounts.go +++ /dev/null @@ -1,114 +0,0 @@ -package storage - -import ( - "context" - "fmt" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type AccountQuery struct{} - -type ListAccountsQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[AccountQuery]] - -func NewListAccountsQuery(opts PaginatedQueryOptions[AccountQuery]) ListAccountsQuery { - return ListAccountsQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) accountQueryContext(qb query.Builder) (string, []any, error) { - return qb.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "reference": - return fmt.Sprintf("%s %s ?", key, query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, errors.Wrap(ErrValidation, "'metadata' column can only be used with $match") - } - match := metadataRegex.FindAllStringSubmatch(key, 3) - - key := "metadata" - return key + " @> ?", []any{map[string]any{ - match[0][1]: value, - }}, nil - default: - return "", nil, errors.Wrap(ErrValidation, fmt.Sprintf("unknown key '%s' when building query", key)) - } - })) -} - -func (s *Storage) ListAccounts(ctx context.Context, q ListAccountsQuery) (*bunpaginate.Cursor[models.Account], error) { - var ( - where string - args []any - err error - ) - if q.Options.QueryBuilder != nil { - where, args, err = s.accountQueryContext(q.Options.QueryBuilder) - if err != nil { - return nil, err - } - } - - return PaginateWithOffset[PaginatedQueryOptions[AccountQuery], models.Account](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[AccountQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query.Relation("PoolAccounts") - - if where != "" { - query = query.Where(where, args...) - } - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) -} - -func (s *Storage) GetAccount(ctx context.Context, id string) (*models.Account, error) { - var account models.Account - - err := s.db.NewSelect(). - Model(&account). - Relation("PoolAccounts"). - Where("account.id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("failed to get account", err) - } - - return &account, nil -} - -func (s *Storage) UpsertAccounts(ctx context.Context, accounts []*models.Account) error { - if len(accounts) == 0 { - return nil - } - - _, err := s.db.NewInsert(). - Model(&accounts). - On("CONFLICT (id) DO UPDATE"). - Set("connector_id = EXCLUDED.connector_id"). - Set("raw_data = EXCLUDED.raw_data"). - Set("default_currency = EXCLUDED.default_currency"). - Set("account_name = EXCLUDED.account_name"). - Set("metadata = EXCLUDED.metadata"). - Exec(ctx) - if err != nil { - return e("failed to create accounts", err) - } - - return nil -} diff --git a/components/payments/cmd/api/internal/storage/accounts_test.go b/components/payments/cmd/api/internal/storage/accounts_test.go deleted file mode 100644 index 3b8bb25bfb..0000000000 --- a/components/payments/cmd/api/internal/storage/accounts_test.go +++ /dev/null @@ -1,266 +0,0 @@ -package storage - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -func insertAccounts(t *testing.T, store *Storage, connectorID models.ConnectorID) []models.AccountID { - id1 := models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - } - acc1 := models.Account{ - ID: id1, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - Reference: "test_account", - AccountName: "test", - Type: models.AccountTypeInternal, - Metadata: map[string]string{"foo": "bar"}, - } - - _, err := store.DB().NewInsert(). - Model(&acc1). - Exec(context.Background()) - require.NoError(t, err) - - id2 := models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - } - acc2 := models.Account{ - ID: id2, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - Reference: "test_account2", - AccountName: "test2", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - "foo2": "bar2", - }, - } - - _, err = store.DB().NewInsert(). - Model(&acc2). - Exec(context.Background()) - require.NoError(t, err) - - return []models.AccountID{id1, id2} -} - -func TestListAccounts(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - insertAccounts(t, store, connectorID) - - acc1 := models.Account{ - ID: models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - Reference: "test_account", - AccountName: "test", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - }, - } - - acc2 := models.Account{ - ID: models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - Reference: "test_account2", - AccountName: "test2", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - "foo2": "bar2", - }, - } - - t.Run("list all accounts with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}).WithPageSize(1)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - - var query ListAccountsQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListAccounts( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, acc1, cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListAccounts( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - }) - - t.Run("list all accounts with page size 2", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}).WithPageSize(2)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - require.Equal(t, acc1, cursor.Data[1]) - }) - - t.Run("list all accounts with page size > number of accounts", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}).WithPageSize(10)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - require.Equal(t, acc1, cursor.Data[1]) - }) - - t.Run("list all accounts with reference", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("reference", "test_account")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, acc1, cursor.Data[0]) - }) - - t.Run("list all accounts with unknown reference", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("reference", "unknown")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 0) - require.False(t, cursor.HasMore) - }) - - t.Run("list all accounts with metadata (1)", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("metadata[foo]", "bar")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - require.Equal(t, acc1, cursor.Data[1]) - }) - - t.Run("list all accounts with metadata (2)", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("metadata[foo2]", "bar2")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, acc2, cursor.Data[0]) - }) - - t.Run("list all accounts with unknown metadata key", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("metadata[unknown]", "bar")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 0) - require.False(t, cursor.HasMore) - }) - - t.Run("list all accounts with unknown metadata value", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListAccounts( - context.Background(), - NewListAccountsQuery(NewPaginatedQueryOptions(AccountQuery{}). - WithPageSize(10). - WithQueryBuilder(query.Match("metadata[foo]", "unknown")), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 0) - require.False(t, cursor.HasMore) - }) -} diff --git a/components/payments/cmd/api/internal/storage/balances.go b/components/payments/cmd/api/internal/storage/balances.go deleted file mode 100644 index 501851efde..0000000000 --- a/components/payments/cmd/api/internal/storage/balances.go +++ /dev/null @@ -1,150 +0,0 @@ -package storage - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type BalanceQuery struct { - AccountID *models.AccountID - Currency string - From time.Time - To time.Time -} - -func NewBalanceQuery() BalanceQuery { - return BalanceQuery{} -} - -func (b BalanceQuery) WithAccountID(accountID *models.AccountID) BalanceQuery { - b.AccountID = accountID - - return b -} - -func (b BalanceQuery) WithCurrency(currency string) BalanceQuery { - b.Currency = currency - - return b -} - -func (b BalanceQuery) WithFrom(from time.Time) BalanceQuery { - b.From = from - - return b -} - -func (b BalanceQuery) WithTo(to time.Time) BalanceQuery { - b.To = to - - return b -} - -func applyBalanceQuery(query *bun.SelectQuery, balanceQuery BalanceQuery) *bun.SelectQuery { - if balanceQuery.AccountID != nil { - query = query.Where("balance.account_id = ?", balanceQuery.AccountID) - } - - if balanceQuery.Currency != "" { - query = query.Where("balance.currency = ?", balanceQuery.Currency) - } - - if !balanceQuery.From.IsZero() { - query = query.Where("balance.last_updated_at >= ?", balanceQuery.From) - } - - if !balanceQuery.To.IsZero() { - query = query.Where("(balance.created_at <= ?)", balanceQuery.To) - } - - return query -} - -type ListBalancesQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[BalanceQuery]] - -func NewListBalancesQuery(opts PaginatedQueryOptions[BalanceQuery]) ListBalancesQuery { - return ListBalancesQuery{ - Order: bunpaginate.OrderAsc, - PageSize: opts.PageSize, - Options: opts, - } -} - -func (s *Storage) ListBalances(ctx context.Context, q ListBalancesQuery) (*bunpaginate.Cursor[models.Balance], error) { - return PaginateWithOffset[PaginatedQueryOptions[BalanceQuery], models.Balance](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[BalanceQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = applyBalanceQuery(query, q.Options.Options) - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) -} - -func (s *Storage) ListBalanceCurrencies(ctx context.Context, accountID models.AccountID) ([]string, error) { - var currencies []string - - err := s.db.NewSelect(). - ColumnExpr("DISTINCT currency"). - Model(&models.Balance{}). - Where("account_id = ?", accountID). - Scan(ctx, ¤cies) - if err != nil { - return nil, e("failed to list balance currencies", err) - } - - return currencies, nil -} - -func (s *Storage) GetBalanceAtByCurrency(ctx context.Context, accountID models.AccountID, currency string, at time.Time) (*models.Balance, error) { - var balance models.Balance - - err := s.db.NewSelect(). - Model(&balance). - Where("account_id = ?", accountID). - Where("currency = ?", currency). - Where("created_at <= ?", at). - Where("last_updated_at >= ?", at). - Order("last_updated_at DESC"). - Limit(1). - Scan(ctx) - if err != nil { - return nil, e("failed to get balance", err) - } - - return &balance, nil -} - -func (s *Storage) GetBalancesAt(ctx context.Context, accountID models.AccountID, at time.Time) ([]*models.Balance, error) { - currencies, err := s.ListBalanceCurrencies(ctx, accountID) - if err != nil { - return nil, fmt.Errorf("failed to list balance currencies: %w", err) - } - - var balances []*models.Balance - for _, currency := range currencies { - balance, err := s.GetBalanceAtByCurrency(ctx, accountID, currency, at) - if err != nil { - if errors.Is(err, ErrNotFound) { - continue - } - return nil, fmt.Errorf("failed to get balance: %w", err) - } - - balances = append(balances, balance) - } - - return balances, nil -} diff --git a/components/payments/cmd/api/internal/storage/balances_test.go b/components/payments/cmd/api/internal/storage/balances_test.go deleted file mode 100644 index 6930901af8..0000000000 --- a/components/payments/cmd/api/internal/storage/balances_test.go +++ /dev/null @@ -1,365 +0,0 @@ -package storage - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -func insertBalances(t *testing.T, store *Storage, accountID models.AccountID) []models.Balance { - b1 := models.Balance{ - AccountID: accountID, - Asset: "EUR/2", - Balance: big.NewInt(100), - CreatedAt: time.Date(2023, 11, 14, 10, 0, 0, 0, time.UTC), - LastUpdatedAt: time.Date(2023, 11, 14, 11, 0, 0, 0, time.UTC), - } - - b2 := models.Balance{ - AccountID: accountID, - Asset: "EUR/2", - Balance: big.NewInt(200), - CreatedAt: time.Date(2023, 11, 14, 11, 0, 0, 0, time.UTC), - LastUpdatedAt: time.Date(2023, 11, 14, 11, 30, 0, 0, time.UTC), - } - - b3 := models.Balance{ - AccountID: accountID, - Asset: "EUR/2", - Balance: big.NewInt(150), - CreatedAt: time.Date(2023, 11, 14, 11, 30, 0, 0, time.UTC), - LastUpdatedAt: time.Date(2023, 11, 14, 11, 45, 0, 0, time.UTC), - } - - b4 := models.Balance{ - AccountID: accountID, - Asset: "USD/2", - Balance: big.NewInt(1000), - CreatedAt: time.Date(2023, 11, 14, 10, 30, 0, 0, time.UTC), - LastUpdatedAt: time.Date(2023, 11, 14, 12, 0, 0, 0, time.UTC), - } - - balances := []models.Balance{b1, b2, b3, b4} - _, err := store.DB().NewInsert(). - Model(&balances). - Exec(context.Background()) - require.NoError(t, err) - - return balances -} - -func TestListBalances(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - balancesPerAccountAndAssets := make(map[string]map[string][]models.Balance) - for _, account := range accounts { - if balancesPerAccountAndAssets[account.String()] == nil { - balancesPerAccountAndAssets[account.String()] = make(map[string][]models.Balance) - } - - balances := insertBalances(t, store, account) - for _, balance := range balances { - balancesPerAccountAndAssets[account.String()][balance.Asset.String()] = append(balancesPerAccountAndAssets[account.String()][balance.Asset.String()], balance) - } - } - - t.Run("list all balances with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery(NewPaginatedQueryOptions(NewBalanceQuery().WithAccountID(&accounts[0])).WithPageSize(1)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][2], cursor.Data[0]) - - var query ListBalancesQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][0], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], cursor.Data[0]) - }) - - t.Run("list all balances with page size 2", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery(NewPaginatedQueryOptions(NewBalanceQuery().WithAccountID(&accounts[0])).WithPageSize(2)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - cursor.Data[1].LastUpdatedAt = cursor.Data[1].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][2], cursor.Data[0]) - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], cursor.Data[1]) - - var query ListBalancesQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - cursor.Data[1].LastUpdatedAt = cursor.Data[1].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], cursor.Data[0]) - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][0], cursor.Data[1]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListBalances( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - cursor.Data[1].LastUpdatedAt = cursor.Data[1].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][2], cursor.Data[0]) - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], cursor.Data[1]) - }) - - t.Run("list balances for asset", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery(NewPaginatedQueryOptions(NewBalanceQuery().WithAccountID(&accounts[0]).WithCurrency("USD/2")).WithPageSize(15)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], cursor.Data[0]) - }) - - t.Run("list balances for asset and limit", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery( - NewPaginatedQueryOptions( - NewBalanceQuery(). - WithAccountID(&accounts[0]). - WithCurrency("EUR/2"), - ). - WithPageSize(1), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][2], cursor.Data[0]) - }) - - t.Run("list balances for asset and time range", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery( - NewPaginatedQueryOptions( - NewBalanceQuery(). - WithAccountID(&accounts[0]). - WithFrom(time.Date(2023, 11, 14, 10, 15, 0, 0, time.UTC)). - WithTo(time.Date(2023, 11, 14, 11, 15, 0, 0, time.UTC)), - ). - WithPageSize(15), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 3) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[1].LastUpdatedAt = cursor.Data[1].LastUpdatedAt.UTC() - cursor.Data[2].CreatedAt = cursor.Data[2].CreatedAt.UTC() - cursor.Data[2].LastUpdatedAt = cursor.Data[2].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], cursor.Data[0]) - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], cursor.Data[1]) - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][0], cursor.Data[2]) - }) - - t.Run("get balances at a precise time", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBalances( - context.Background(), - NewListBalancesQuery( - NewPaginatedQueryOptions( - NewBalanceQuery(). - WithAccountID(&accounts[0]). - WithCurrency("EUR/2"). - WithTo(time.Date(2023, 11, 14, 11, 15, 0, 0, time.UTC)), - ).WithPageSize(1), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].LastUpdatedAt = cursor.Data[0].LastUpdatedAt.UTC() - require.Equal(t, balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], cursor.Data[0]) - - cursor, err = store.ListBalances( - context.Background(), - NewListBalancesQuery( - NewPaginatedQueryOptions( - NewBalanceQuery(). - WithAccountID(&accounts[0]). - WithCurrency("EUR/2"). - WithTo(time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC)), - ).WithPageSize(1), - ), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 0) - require.False(t, cursor.HasMore) - }) -} - -func TestGetBalanceAt(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - balancesPerAccountAndAssets := make(map[string]map[string][]models.Balance) - for _, account := range accounts { - if balancesPerAccountAndAssets[account.String()] == nil { - balancesPerAccountAndAssets[account.String()] = make(map[string][]models.Balance) - } - - balances := insertBalances(t, store, account) - for _, balance := range balances { - balancesPerAccountAndAssets[account.String()][balance.Asset.String()] = append(balancesPerAccountAndAssets[account.String()][balance.Asset.String()], balance) - } - } - - // Should have only one EUR/2 balance of 100 - t.Run("get balance at 10:15", func(t *testing.T) { - balances, err := store.GetBalancesAt(context.Background(), accounts[0], time.Date(2023, 11, 14, 10, 15, 0, 0, time.UTC)) - require.NoError(t, err) - require.Len(t, balances, 1) - balances[0].CreatedAt = balances[0].CreatedAt.UTC() - balances[0].LastUpdatedAt = balances[0].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][0], balances[0]) - require.Equal(t, big.NewInt(100), balances[0].Balance) - }) - - t.Run("get balance at 11:15", func(t *testing.T) { - balances, err := store.GetBalancesAt(context.Background(), accounts[0], time.Date(2023, 11, 14, 11, 15, 0, 0, time.UTC)) - require.NoError(t, err) - require.Len(t, balances, 2) - balances[0].CreatedAt = balances[0].CreatedAt.UTC() - balances[0].LastUpdatedAt = balances[0].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][1], balances[0]) - require.Equal(t, big.NewInt(200), balances[0].Balance) - balances[1].CreatedAt = balances[1].CreatedAt.UTC() - balances[1].LastUpdatedAt = balances[1].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], balances[1]) - require.Equal(t, big.NewInt(1000), balances[1].Balance) - }) - - t.Run("get balance at 11:45", func(t *testing.T) { - balances, err := store.GetBalancesAt(context.Background(), accounts[0], time.Date(2023, 11, 14, 11, 45, 0, 0, time.UTC)) - require.NoError(t, err) - require.Len(t, balances, 2) - balances[0].CreatedAt = balances[0].CreatedAt.UTC() - balances[0].LastUpdatedAt = balances[0].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["EUR/2"][2], balances[0]) - require.Equal(t, big.NewInt(150), balances[0].Balance) - balances[1].CreatedAt = balances[1].CreatedAt.UTC() - balances[1].LastUpdatedAt = balances[1].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], balances[1]) - require.Equal(t, big.NewInt(1000), balances[1].Balance) - }) - - t.Run("get balance at 12:00", func(t *testing.T) { - balances, err := store.GetBalancesAt(context.Background(), accounts[0], time.Date(2023, 11, 14, 12, 0, 0, 0, time.UTC)) - require.NoError(t, err) - require.Len(t, balances, 1) - balances[0].CreatedAt = balances[0].CreatedAt.UTC() - balances[0].LastUpdatedAt = balances[0].LastUpdatedAt.UTC() - require.Equal(t, &balancesPerAccountAndAssets[accounts[0].String()]["USD/2"][0], balances[0]) - require.Equal(t, big.NewInt(1000), balances[0].Balance) - }) -} diff --git a/components/payments/cmd/api/internal/storage/bank_accounts.go b/components/payments/cmd/api/internal/storage/bank_accounts.go deleted file mode 100644 index a523ee86fb..0000000000 --- a/components/payments/cmd/api/internal/storage/bank_accounts.go +++ /dev/null @@ -1,63 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type BankAccountQuery struct{} - -type ListBankAccountQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[BankAccountQuery]] - -func NewListBankAccountQuery(opts PaginatedQueryOptions[BankAccountQuery]) ListBankAccountQuery { - return ListBankAccountQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) ListBankAccounts(ctx context.Context, q ListBankAccountQuery) (*bunpaginate.Cursor[models.BankAccount], error) { - return PaginateWithOffset[PaginatedQueryOptions[BankAccountQuery], models.BankAccount](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[BankAccountQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query. - Relation("RelatedAccounts") - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) -} - -func (s *Storage) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - var account models.BankAccount - query := s.db.NewSelect(). - Model(&account). - Column("id", "created_at", "name", "created_at", "country", "metadata"). - Relation("RelatedAccounts") - - if expand { - query = query.ColumnExpr("pgp_sym_decrypt(account_number, ?, ?) AS decrypted_account_number", s.configEncryptionKey, encryptionOptions). - ColumnExpr("pgp_sym_decrypt(iban, ?, ?) AS decrypted_iban", s.configEncryptionKey, encryptionOptions). - ColumnExpr("pgp_sym_decrypt(swift_bic_code, ?, ?) AS decrypted_swift_bic_code", s.configEncryptionKey, encryptionOptions) - } - - err := query. - Where("id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("get bank account", err) - } - - return &account, nil -} diff --git a/components/payments/cmd/api/internal/storage/bank_accounts_test.go b/components/payments/cmd/api/internal/storage/bank_accounts_test.go deleted file mode 100644 index c2e1b2ac77..0000000000 --- a/components/payments/cmd/api/internal/storage/bank_accounts_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package storage - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func insertBankAccounts(t *testing.T, store *Storage, connectorID models.ConnectorID) []models.BankAccount { - acc1 := models.BankAccount{ - ID: uuid.New(), - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - Name: "test_1", - IBAN: "FR7630006000011234567890189", - Country: "FR", - Metadata: map[string]string{ - "foo": "bar", - }, - } - _, err := store.DB().NewInsert(). - Model(&acc1). - Exec(context.Background()) - require.NoError(t, err) - - acc2 := models.BankAccount{ - ID: uuid.New(), - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - Name: "test_2", - IBAN: "FR7630006000011234567891234", - Country: "GB", - Metadata: map[string]string{ - "foo2": "bar2", - }, - } - _, err = store.DB().NewInsert(). - Model(&acc2). - Exec(context.Background()) - require.NoError(t, err) - - return []models.BankAccount{acc1, acc2} -} - -func TestListBankAccounts(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - bankAccounts := insertBankAccounts(t, store, connectorID) - - for i := range bankAccounts { - bankAccounts[i].CreatedAt = bankAccounts[i].CreatedAt.UTC() - // The listing of bank accounts does not sent the IBAN info - bankAccounts[i].IBAN = "" - } - - t.Run("list all bank accounts with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBankAccounts( - context.Background(), - NewListBankAccountQuery(NewPaginatedQueryOptions(BankAccountQuery{}).WithPageSize(1)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, bankAccounts[1], cursor.Data[0]) - - var query ListBankAccountQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListBankAccounts( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, bankAccounts[0], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListBankAccounts( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - require.Equal(t, bankAccounts[1], cursor.Data[0]) - }) - - t.Run("list all bank accounts with page size 2", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBankAccounts( - context.Background(), - NewListBankAccountQuery(NewPaginatedQueryOptions(BankAccountQuery{}).WithPageSize(2)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - require.Equal(t, bankAccounts[1], cursor.Data[0]) - require.Equal(t, bankAccounts[0], cursor.Data[1]) - }) - - t.Run("list all bank accounts with page size > number of bank accounts", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListBankAccounts( - context.Background(), - NewListBankAccountQuery(NewPaginatedQueryOptions(BankAccountQuery{}).WithPageSize(10)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - require.Equal(t, bankAccounts[1], cursor.Data[0]) - require.Equal(t, bankAccounts[0], cursor.Data[1]) - }) -} diff --git a/components/payments/cmd/api/internal/storage/connectors.go b/components/payments/cmd/api/internal/storage/connectors.go deleted file mode 100644 index bc38ea4a4a..0000000000 --- a/components/payments/cmd/api/internal/storage/connectors.go +++ /dev/null @@ -1,19 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) IsConnectorInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - exists, err := s.db.NewSelect(). - Model(&models.Connector{}). - Where("id = ?", connectorID). - Exists(ctx) - if err != nil { - return false, e("find connector", err) - } - - return exists, nil -} diff --git a/components/payments/cmd/api/internal/storage/connectors_test.go b/components/payments/cmd/api/internal/storage/connectors_test.go deleted file mode 100644 index f2e9a7ef1e..0000000000 --- a/components/payments/cmd/api/internal/storage/connectors_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package storage - -import ( - "context" - "encoding/json" - "testing" - "time" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -const testEncryptionOptions = "compress-algo=1, cipher-algo=aes256" -const encryptionKey = "test" - -// Helpers to add test data -func installConnector(t *testing.T, store *Storage) models.ConnectorID { - db := store.DB() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - connector := &models.Connector{ - ID: connectorID, - Name: "test_connector", - CreatedAt: time.Date(2023, 11, 13, 0, 0, 0, 0, time.UTC), - Provider: models.ConnectorProviderDummyPay, - } - - _, err := db.NewInsert().Model(connector).Exec(context.Background()) - require.NoError(t, err) - - _, err = db.NewUpdate(). - Model(&models.Connector{}). - Set("config = pgp_sym_encrypt(?::TEXT, ?, ?)", json.RawMessage(`{}`), encryptionKey, testEncryptionOptions). - Where("id = ?", connectorID). // Connector name is unique - Exec(context.Background()) - require.NoError(t, err) - - return connectorID -} diff --git a/components/payments/cmd/api/internal/storage/error.go b/components/payments/cmd/api/internal/storage/error.go deleted file mode 100644 index d120abf9bb..0000000000 --- a/components/payments/cmd/api/internal/storage/error.go +++ /dev/null @@ -1,30 +0,0 @@ -package storage - -import ( - "database/sql" - "fmt" - - "github.com/jackc/pgx/v5/pgconn" - "github.com/pkg/errors" -) - -var ErrValidation = errors.New("validation error") -var ErrNotFound = errors.New("not found") -var ErrDuplicateKeyValue = errors.New("duplicate key value") - -func e(msg string, err error) error { - if err == nil { - return nil - } - - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == "23505" { - return ErrDuplicateKeyValue - } - - if errors.Is(err, sql.ErrNoRows) { - return ErrNotFound - } - - return fmt.Errorf("%s: %w", msg, err) -} diff --git a/components/payments/cmd/api/internal/storage/main_test.go b/components/payments/cmd/api/internal/storage/main_test.go deleted file mode 100644 index 29828d318c..0000000000 --- a/components/payments/cmd/api/internal/storage/main_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package storage - -import ( - "context" - "crypto/rand" - "testing" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - migrationstorage "github.com/formancehq/payments/internal/storage" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/stdlib" - "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" -) - -var ( - srv *pgtesting.PostgresServer -) - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} - -func newStore(t *testing.T) *Storage { - t.Helper() - - pgServer := srv.NewDatabase(t) - - config, err := pgx.ParseConfig(pgServer.ConnString()) - require.NoError(t, err) - - key := make([]byte, 64) - _, err = rand.Read(key) - require.NoError(t, err) - - db := bun.NewDB(stdlib.OpenDB(*config), pgdialect.New()) - t.Cleanup(func() { - _ = db.Close() - }) - - err = migrationstorage.Migrate(context.Background(), db) - require.NoError(t, err) - - store := NewStorage( - db, - string(key), - ) - - return store -} diff --git a/components/payments/cmd/api/internal/storage/metadata.go b/components/payments/cmd/api/internal/storage/metadata.go deleted file mode 100644 index 7625655816..0000000000 --- a/components/payments/cmd/api/internal/storage/metadata.go +++ /dev/null @@ -1,39 +0,0 @@ -package storage - -import ( - "context" - "time" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error { - var metadataToInsert []models.PaymentMetadata // nolint:prealloc // it's against a map - - for key, value := range metadata { - metadataToInsert = append(metadataToInsert, models.PaymentMetadata{ - PaymentID: paymentID, - Key: key, - Value: value, - Changelog: []models.MetadataChangelog{ - { - CreatedAt: time.Now(), - Value: value, - }, - }, - }) - } - - _, err := s.db.NewInsert(). - Model(&metadataToInsert). - On("CONFLICT (payment_id, key) DO UPDATE"). - Set("value = EXCLUDED.value"). - Set("changelog = payment_metadata.changelog || EXCLUDED.changelog"). - Where("payment_metadata.value != EXCLUDED.value"). - Exec(ctx) - if err != nil { - return e("failed to update payment metadata", err) - } - - return nil -} diff --git a/components/payments/cmd/api/internal/storage/module.go b/components/payments/cmd/api/internal/storage/module.go deleted file mode 100644 index e000fdff85..0000000000 --- a/components/payments/cmd/api/internal/storage/module.go +++ /dev/null @@ -1,30 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/logging" - "github.com/uptrace/bun" - "go.uber.org/fx" -) - -func Module(connectionOptions bunconnect.ConnectionOptions, configEncryptionKey string, debug bool) fx.Option { - return fx.Options( - bunconnect.Module(connectionOptions, debug), - fx.Provide(func(db *bun.DB) *Storage { - return NewStorage(db, configEncryptionKey) - }), - fx.Invoke(func(lc fx.Lifecycle, repo *Storage) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - logging.FromContext(ctx).Debug("Ping database...") - - // TODO: Check migrations state and panic if migrations are not applied - - return nil - }, - }) - }), - ) -} diff --git a/components/payments/cmd/api/internal/storage/paginate.go b/components/payments/cmd/api/internal/storage/paginate.go deleted file mode 100644 index d8853fc5e9..0000000000 --- a/components/payments/cmd/api/internal/storage/paginate.go +++ /dev/null @@ -1,77 +0,0 @@ -package storage - -import ( - "context" - "encoding/json" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/uptrace/bun" -) - -type PaginatedQueryOptions[T any] struct { - QueryBuilder query.Builder `json:"qb"` - Sorter Sorter - PageSize uint64 `json:"pageSize"` - Options T `json:"options"` -} - -func (v *PaginatedQueryOptions[T]) UnmarshalJSON(data []byte) error { - type aux struct { - QueryBuilder json.RawMessage `json:"qb"` - Sorter Sorter `json:"Sorter"` - PageSize uint64 `json:"pageSize"` - Options T `json:"options"` - } - x := &aux{} - if err := json.Unmarshal(data, x); err != nil { - return err - } - - *v = PaginatedQueryOptions[T]{ - PageSize: x.PageSize, - Options: x.Options, - Sorter: x.Sorter, - } - - var err error - if x.QueryBuilder != nil { - v.QueryBuilder, err = query.ParseJSON(string(x.QueryBuilder)) - if err != nil { - return err - } - } - - return nil -} - -func (opts PaginatedQueryOptions[T]) WithQueryBuilder(qb query.Builder) PaginatedQueryOptions[T] { - opts.QueryBuilder = qb - - return opts -} - -func (opts PaginatedQueryOptions[T]) WithSorter(sorter Sorter) PaginatedQueryOptions[T] { - opts.Sorter = sorter - - return opts -} - -func (opts PaginatedQueryOptions[T]) WithPageSize(pageSize uint64) PaginatedQueryOptions[T] { - opts.PageSize = pageSize - - return opts -} - -func NewPaginatedQueryOptions[T any](options T) PaginatedQueryOptions[T] { - return PaginatedQueryOptions[T]{ - Options: options, - PageSize: bunpaginate.QueryDefaultPageSize, - } -} - -func PaginateWithOffset[FILTERS any, RETURN any](s *Storage, ctx context.Context, - q *bunpaginate.OffsetPaginatedQuery[FILTERS], builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (*bunpaginate.Cursor[RETURN], error) { - query := s.db.NewSelect() - return bunpaginate.UsingOffset[FILTERS, RETURN](ctx, query, *q, builders...) -} diff --git a/components/payments/cmd/api/internal/storage/payments.go b/components/payments/cmd/api/internal/storage/payments.go deleted file mode 100644 index 4a3b0df99c..0000000000 --- a/components/payments/cmd/api/internal/storage/payments.go +++ /dev/null @@ -1,213 +0,0 @@ -package storage - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type PaymentQuery struct{} - -type ListPaymentsQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PaymentQuery]] - -func NewListPaymentsQuery(opts PaginatedQueryOptions[PaymentQuery]) ListPaymentsQuery { - return ListPaymentsQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) paymentsQueryContext(qb query.Builder) (map[string]string, string, []any, error) { - metadata := make(map[string]string) - - where, args, err := qb.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "reference": - return fmt.Sprintf("%s %s ?", key, query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - - case key == "type", - key == "status", - key == "asset": - if operator != "$match" { - return "", nil, errors.Wrap(ErrValidation, "'type' column can only be used with $match") - } - return fmt.Sprintf("%s = ?", key), []any{value}, nil - - case key == "connectorID": - if operator != "$match" { - return "", nil, errors.Wrap(ErrValidation, "'type' column can only be used with $match") - } - return "connector_id = ?", []any{value}, nil - - case key == "amount": - return fmt.Sprintf("%s %s ?", key, query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - - case key == "initialAmount": - return fmt.Sprintf("initial_amount %s ?", query.DefaultComparisonOperatorsMapping[operator]), []any{value}, nil - - case metadataRegex.Match([]byte(key)): - if operator != "$match" { - return "", nil, errors.Wrap(ErrValidation, "'metadata' column can only be used with $match") - } - match := metadataRegex.FindAllStringSubmatch(key, 3) - - valueString, ok := value.(string) - if !ok { - return "", nil, errors.Wrap(ErrValidation, fmt.Sprintf("metadata value must be a string, got %T", value)) - } - - metadata[match[0][1]] = valueString - - // Do nothing here, as we don't want to add this to the query - return "", nil, nil - - default: - return "", nil, errors.Wrap(ErrValidation, fmt.Sprintf("unknown key '%s' when building query", key)) - } - })) - - return metadata, where, args, err -} - -func (s *Storage) ListPayments(ctx context.Context, q ListPaymentsQuery) (*bunpaginate.Cursor[models.Payment], error) { - var ( - metadata map[string]string - where string - args []any - err error - ) - if q.Options.QueryBuilder != nil { - metadata, where, args, err = s.paymentsQueryContext(q.Options.QueryBuilder) - if err != nil { - return nil, err - } - } - - return PaginateWithOffset[PaginatedQueryOptions[PaymentQuery], models.Payment](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PaymentQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query. - Relation("Metadata"). - Relation("Connector"). - Relation("Adjustments") - - if where != "" { - query = query.Where(where, args...) - } - - if len(metadata) > 0 { - metadataQuery := s.db.NewSelect().Model((*models.PaymentMetadata)(nil)) - for key, value := range metadata { - metadataQuery = metadataQuery.Where("payment_metadata.key = ? AND payment_metadata.value = ?", key, value) - } - query = query.With("_metadata", metadataQuery) - query = query.Where("payment.id IN (SELECT payment_id FROM _metadata)") - } - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) -} - -func (s *Storage) GetPayment(ctx context.Context, id string) (*models.Payment, error) { - var payment models.Payment - - err := s.db.NewSelect(). - Model(&payment). - Relation("Connector"). - Relation("Metadata"). - Relation("Adjustments"). - Where("payment.id = ?", id). - Scan(ctx) - if err != nil { - return nil, e(fmt.Sprintf("failed to get payment %s", id), err) - } - - return &payment, nil -} - -func (s *Storage) UpsertPayments(ctx context.Context, payments []*models.Payment) error { - if len(payments) == 0 { - return nil - } - - _, err := s.db.NewInsert(). - Model(&payments). - On("CONFLICT (reference) DO UPDATE"). - Set("amount = EXCLUDED.amount"). - Set("type = EXCLUDED.type"). - Set("status = EXCLUDED.status"). - Set("raw_data = EXCLUDED.raw_data"). - Set("scheme = EXCLUDED.scheme"). - Set("asset = EXCLUDED.asset"). - Set("source_account_id = EXCLUDED.source_account_id"). - Set("destination_account_id = EXCLUDED.destination_account_id"). - Exec(ctx) - if err != nil { - return e("failed to create payments", err) - } - - var adjustments []*models.PaymentAdjustment - var metadata []*models.PaymentMetadata - - for i := range payments { - for _, adjustment := range payments[i].Adjustments { - if adjustment.Reference == "" { - continue - } - - adjustment.PaymentID = payments[i].ID - - adjustments = append(adjustments, adjustment) - } - - for _, data := range payments[i].Metadata { - data.PaymentID = payments[i].ID - data.Changelog = append(data.Changelog, - models.MetadataChangelog{ - CreatedAt: time.Now(), - Value: data.Value, - }) - - metadata = append(metadata, data) - } - } - - if len(adjustments) > 0 { - _, err = s.db.NewInsert(). - Model(&adjustments). - On("CONFLICT (reference) DO NOTHING"). - Exec(ctx) - if err != nil { - return e("failed to create adjustments", err) - } - } - - if len(metadata) > 0 { - _, err = s.db.NewInsert(). - Model(&metadata). - On("CONFLICT (payment_id, key) DO UPDATE"). - Set("value = EXCLUDED.value"). - Set("changelog = metadata.changelog || EXCLUDED.changelog"). - Where("metadata.value != EXCLUDED.value"). - Exec(ctx) - if err != nil { - return e("failed to create metadata", err) - } - } - - return nil -} diff --git a/components/payments/cmd/api/internal/storage/payments_test.go b/components/payments/cmd/api/internal/storage/payments_test.go deleted file mode 100644 index 997094c024..0000000000 --- a/components/payments/cmd/api/internal/storage/payments_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package storage - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -func insertPayments(t *testing.T, store *Storage, connectorID models.ConnectorID) []models.Payment { - p1 := models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "test_1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - Reference: "test_1", - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusPending, - Scheme: models.PaymentSchemeA2A, - Asset: models.Asset("USD/2"), - SourceAccountID: &models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - }, - DestinationAccountID: &models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - }, - } - _, err := store.DB().NewInsert(). - Model(&p1). - Exec(context.Background()) - require.NoError(t, err) - - p2 := models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "test_2", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - Reference: "test_2", - Amount: big.NewInt(200), - InitialAmount: big.NewInt(100), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusPending, - Scheme: models.PaymentSchemeA2A, - Asset: models.Asset("EUR/2"), - SourceAccountID: &models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - }, - DestinationAccountID: &models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - }, - } - _, err = store.DB().NewInsert(). - Model(&p2). - Exec(context.Background()) - require.NoError(t, err) - - return []models.Payment{p1, p2} -} - -func TestListPayments(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - insertAccounts(t, store, connectorID) - payments := insertPayments(t, store, connectorID) - - t.Run("list all payments with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListPayments( - context.Background(), - NewListPaymentsQuery(NewPaginatedQueryOptions(PaymentQuery{}).WithPageSize(1)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].Connector = nil - require.Equal(t, payments[1], cursor.Data[0]) - - var query ListPaymentsQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListPayments( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].Connector = nil - require.Equal(t, payments[0], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListPayments( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].Connector = nil - require.Equal(t, payments[1], cursor.Data[0]) - }) - - t.Run("list all payments with page size 2", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListPayments( - context.Background(), - NewListPaymentsQuery(NewPaginatedQueryOptions(PaymentQuery{}).WithPageSize(2)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].Connector = nil - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[1].Connector = nil - require.Equal(t, payments[1], cursor.Data[0]) - require.Equal(t, payments[0], cursor.Data[1]) - }) - - t.Run("list all payments with page size > number of payments", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListPayments( - context.Background(), - NewListPaymentsQuery(NewPaginatedQueryOptions(PaymentQuery{}).WithPageSize(10)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].Connector = nil - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[1].Connector = nil - require.Equal(t, payments[1], cursor.Data[0]) - require.Equal(t, payments[0], cursor.Data[1]) - }) -} diff --git a/components/payments/cmd/api/internal/storage/ping.go b/components/payments/cmd/api/internal/storage/ping.go deleted file mode 100644 index 2832abb0b1..0000000000 --- a/components/payments/cmd/api/internal/storage/ping.go +++ /dev/null @@ -1,5 +0,0 @@ -package storage - -func (s *Storage) Ping() error { - return s.db.Ping() -} diff --git a/components/payments/cmd/api/internal/storage/pools.go b/components/payments/cmd/api/internal/storage/pools.go deleted file mode 100644 index d9827f9c2c..0000000000 --- a/components/payments/cmd/api/internal/storage/pools.go +++ /dev/null @@ -1,117 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -func (s *Storage) CreatePool(ctx context.Context, pool *models.Pool) error { - var id uuid.UUID - err := s.db.NewInsert(). - Model(pool). - Returning("id"). - Scan(ctx, &id) - if err != nil { - return e("failed to create pool", err) - } - pool.ID = id - - return nil -} - -func (s *Storage) AddAccountsToPool(ctx context.Context, poolAccounts []*models.PoolAccounts) error { - _, err := s.db.NewInsert(). - Model(&poolAccounts). - Exec(ctx) - if err != nil { - return e("failed to add accounts to pool", err) - } - - return nil -} - -func (s *Storage) AddAccountToPool(ctx context.Context, poolAccount *models.PoolAccounts) error { - _, err := s.db.NewInsert(). - Model(poolAccount). - Exec(ctx) - if err != nil { - return e("failed to add account to pool", err) - } - - return nil -} - -func (s *Storage) RemoveAccountFromPool(ctx context.Context, poolAccount *models.PoolAccounts) error { - _, err := s.db.NewDelete(). - Model(poolAccount). - Where("pool_id = ?", poolAccount.PoolID). - Where("account_id = ?", poolAccount.AccountID). - Exec(ctx) - if err != nil { - return e("failed to remove account from pool", err) - } - - return nil -} - -type PoolQuery struct{} - -type ListPoolsQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PoolQuery]] - -func NewListPoolsQuery(opts PaginatedQueryOptions[PoolQuery]) ListPoolsQuery { - return ListPoolsQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) ListPools(ctx context.Context, q ListPoolsQuery) (*bunpaginate.Cursor[models.Pool], error) { - cursor, err := PaginateWithOffset[PaginatedQueryOptions[PoolQuery], models.Pool](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[PoolQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query. - Relation("PoolAccounts") - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) - return cursor, err -} - -func (s *Storage) GetPool(ctx context.Context, poolID uuid.UUID) (*models.Pool, error) { - var pool models.Pool - - err := s.db.NewSelect(). - Model(&pool). - Where("id = ?", poolID). - Relation("PoolAccounts"). - Scan(ctx) - if err != nil { - return nil, e("failed to get pool", err) - } - - return &pool, nil -} - -func (s *Storage) DeletePool(ctx context.Context, poolID uuid.UUID) error { - _, err := s.db.NewDelete(). - Model(&models.Pool{}). - Where("id = ?", poolID). - Exec(ctx) - if err != nil { - return e("failed to delete pool", err) - } - - return nil -} diff --git a/components/payments/cmd/api/internal/storage/pools_test.go b/components/payments/cmd/api/internal/storage/pools_test.go deleted file mode 100644 index 5c6d78fe8d..0000000000 --- a/components/payments/cmd/api/internal/storage/pools_test.go +++ /dev/null @@ -1,202 +0,0 @@ -package storage - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func insertPools(t *testing.T, store *Storage, accountIDs []models.AccountID) []uuid.UUID { - pool1 := models.Pool{ - Name: "test", - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - } - var uuid1 uuid.UUID - err := store.DB().NewInsert(). - Model(&pool1). - Returning("id"). - Scan(context.Background(), &uuid1) - require.NoError(t, err) - - poolAccounts1 := models.PoolAccounts{ - PoolID: uuid1, - AccountID: accountIDs[0], - } - _, err = store.DB().NewInsert(). - Model(&poolAccounts1). - Exec(context.Background()) - require.NoError(t, err) - - var uuid2 uuid.UUID - pool2 := models.Pool{ - Name: "test2", - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - } - err = store.DB().NewInsert(). - Model(&pool2). - Returning("id"). - Scan(context.Background(), &uuid2) - require.NoError(t, err) - - poolAccounts2 := []*models.PoolAccounts{ - { - PoolID: uuid2, - AccountID: accountIDs[0], - }, - { - PoolID: uuid2, - AccountID: accountIDs[1], - }, - } - _, err = store.DB().NewInsert(). - Model(&poolAccounts2). - Exec(context.Background()) - require.NoError(t, err) - - return []uuid.UUID{uuid1, uuid2} -} - -func TestCreatePools(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - - pool := &models.Pool{ - Name: "test", - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - PoolAccounts: []*models.PoolAccounts{}, - } - for _, account := range accounts { - pool.PoolAccounts = append(pool.PoolAccounts, &models.PoolAccounts{ - AccountID: account, - }) - } - - err := store.CreatePool(context.Background(), pool) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, pool.ID) -} - -func TestAddAccountsToPool(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - poolIDs := insertPools(t, store, accounts) - - poolAccounts := []*models.PoolAccounts{ - { - PoolID: poolIDs[0], - AccountID: accounts[1], - }, - } - - err := store.AddAccountsToPool(context.Background(), poolAccounts) - require.NoError(t, err) - - pool, err := store.GetPool(context.Background(), poolIDs[0]) - require.NoError(t, err) - require.Equal(t, 2, len(pool.PoolAccounts)) - require.Equal(t, accounts[0], pool.PoolAccounts[0].AccountID) - require.Equal(t, accounts[1], pool.PoolAccounts[1].AccountID) -} - -func TestRemoveAccoutsToPool(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - poolIDs := insertPools(t, store, accounts) - - poolAccounts := []*models.PoolAccounts{ - { - PoolID: poolIDs[0], - AccountID: accounts[0], - }, - } - - err := store.RemoveAccountFromPool(context.Background(), poolAccounts[0]) - require.NoError(t, err) - - pool, err := store.GetPool(context.Background(), poolIDs[0]) - require.NoError(t, err) - require.Equal(t, 0, len(pool.PoolAccounts)) -} - -func TestListPools(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - accounts := insertAccounts(t, store, connectorID) - insertedPools := insertPools(t, store, accounts) - - t.Run("list all pools", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListPools( - context.Background(), - NewListPoolsQuery(NewPaginatedQueryOptions(PoolQuery{}).WithPageSize(15)), - ) - require.NoError(t, err) - require.Equal(t, 2, len(cursor.Data)) - require.Equal(t, 15, cursor.PageSize) - require.Equal(t, false, cursor.HasMore) - require.Equal(t, "", cursor.Previous) - require.Equal(t, "", cursor.Next) - require.Equal(t, insertedPools[1], cursor.Data[0].ID) - require.Equal(t, 2, len(cursor.Data[0].PoolAccounts)) - require.Equal(t, insertedPools[0], cursor.Data[1].ID) - require.Equal(t, 1, len(cursor.Data[1].PoolAccounts)) - }) - - t.Run("list all pools with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListPools( - context.Background(), - NewListPoolsQuery(NewPaginatedQueryOptions(PoolQuery{}).WithPageSize(1)), - ) - require.NoError(t, err) - require.Equal(t, 1, len(cursor.Data)) - require.Equal(t, 1, cursor.PageSize) - require.Equal(t, true, cursor.HasMore) - require.Equal(t, "", cursor.Previous) - require.Equal(t, insertedPools[1], cursor.Data[0].ID) - require.Equal(t, 2, len(cursor.Data[0].PoolAccounts)) - - var query ListPoolsQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListPools(context.Background(), query) - require.NoError(t, err) - require.Equal(t, 1, len(cursor.Data)) - require.Equal(t, 1, cursor.PageSize) - require.Equal(t, false, cursor.HasMore) - require.Equal(t, insertedPools[0], cursor.Data[0].ID) - require.Equal(t, 1, len(cursor.Data[0].PoolAccounts)) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListPools(context.Background(), query) - require.NoError(t, err) - require.Equal(t, 1, len(cursor.Data)) - require.Equal(t, 1, cursor.PageSize) - require.Equal(t, true, cursor.HasMore) - require.Equal(t, insertedPools[1], cursor.Data[0].ID) - require.Equal(t, 2, len(cursor.Data[0].PoolAccounts)) - }) -} diff --git a/components/payments/cmd/api/internal/storage/repository.go b/components/payments/cmd/api/internal/storage/repository.go deleted file mode 100644 index 6b203bf62b..0000000000 --- a/components/payments/cmd/api/internal/storage/repository.go +++ /dev/null @@ -1,20 +0,0 @@ -package storage - -import ( - "github.com/uptrace/bun" -) - -type Storage struct { - db *bun.DB - configEncryptionKey string -} - -const encryptionOptions = "compress-algo=1, cipher-algo=aes256" - -func NewStorage(db *bun.DB, configEncryptionKey string) *Storage { - return &Storage{db: db, configEncryptionKey: configEncryptionKey} -} - -func (s *Storage) DB() *bun.DB { - return s.db -} diff --git a/components/payments/cmd/api/internal/storage/sort.go b/components/payments/cmd/api/internal/storage/sort.go deleted file mode 100644 index 2ec3d5c0a0..0000000000 --- a/components/payments/cmd/api/internal/storage/sort.go +++ /dev/null @@ -1,33 +0,0 @@ -package storage - -import ( - "fmt" - - "github.com/uptrace/bun" -) - -type SortOrder string - -const ( - SortOrderAsc SortOrder = "asc" - SortOrderDesc SortOrder = "desc" -) - -type sortExpression struct { - Column string `json:"column"` - Order SortOrder `json:"order"` -} - -type Sorter []sortExpression - -func (s Sorter) Add(column string, order SortOrder) Sorter { - return append(s, sortExpression{column, order}) -} - -func (s Sorter) Apply(query *bun.SelectQuery) *bun.SelectQuery { - for _, expr := range s { - query = query.Order(fmt.Sprintf("%s %s", expr.Column, expr.Order)) - } - - return query -} diff --git a/components/payments/cmd/api/internal/storage/transfer_initiation.go b/components/payments/cmd/api/internal/storage/transfer_initiation.go deleted file mode 100644 index 313639e8e8..0000000000 --- a/components/payments/cmd/api/internal/storage/transfer_initiation.go +++ /dev/null @@ -1,112 +0,0 @@ -package storage - -import ( - "context" - "fmt" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/formancehq/payments/internal/models" - "github.com/uptrace/bun" -) - -func (s *Storage) GetTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - var transferInitiation models.TransferInitiation - - query := s.db.NewSelect(). - Column("id", "connector_id", "created_at", "scheduled_at", "description", "type", "source_account_id", "destination_account_id", "provider", "initial_amount", "amount", "asset", "metadata"). - Model(&transferInitiation). - Relation("RelatedAdjustments"). - Where("id = ?", id) - - err := query.Scan(ctx) - if err != nil { - return nil, e("failed to get transfer initiation", err) - } - - transferInitiation.SortRelatedAdjustments() - - transferInitiation.RelatedPayments, err = s.ReadTransferInitiationPayments(ctx, id) - if err != nil { - return nil, e("failed to get transfer initiation payments", err) - } - - return &transferInitiation, nil -} - -func (s *Storage) ReadTransferInitiationPayments(ctx context.Context, id models.TransferInitiationID) ([]*models.TransferInitiationPayment, error) { - var payments []*models.TransferInitiationPayment - - query := s.db.NewSelect(). - Column("transfer_initiation_id", "payment_id", "created_at", "status", "error"). - Model(&payments). - Where("transfer_initiation_id = ?", id). - Order("created_at DESC") - - err := query.Scan(ctx) - if err != nil { - return nil, e("failed to get transfer initiation payments", err) - } - - return payments, nil -} - -type TransferInitiationQuery struct{} - -type ListTransferInitiationsQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[TransferInitiationQuery]] - -func NewListTransferInitiationsQuery(opts PaginatedQueryOptions[TransferInitiationQuery]) ListTransferInitiationsQuery { - return ListTransferInitiationsQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) ListTransferInitiations(ctx context.Context, q ListTransferInitiationsQuery) (*bunpaginate.Cursor[models.TransferInitiation], error) { - return PaginateWithOffset[PaginatedQueryOptions[TransferInitiationQuery], models.TransferInitiation](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[TransferInitiationQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query. - Column("id", "connector_id", "created_at", "scheduled_at", "description", "type", "source_account_id", "destination_account_id", "provider", "initial_amount", "amount", "asset", "metadata"). - Relation("RelatedAdjustments") - - if q.Options.QueryBuilder != nil { - where, args, err := s.transferInitiationQueryContext(q.Options.QueryBuilder) - if err != nil { - // TODO: handle error - panic(err) - } - query = query.Where(where, args...) - } - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } else { - query = query.Order("created_at DESC") - } - - return query - }, - ) -} - -func (s *Storage) transferInitiationQueryContext(qb query.Builder) (string, []any, error) { - return qb.Build(query.ContextFn(func(key, operator string, value any) (string, []any, error) { - switch { - case key == "source_account_id", key == "destination_account_id": - if operator != "$match" { - return "", nil, fmt.Errorf("'%s' columns can only be used with $match", key) - } - - switch accountID := value.(type) { - case string: - return fmt.Sprintf("%s = ?", key), []any{accountID}, nil - default: - return "", nil, fmt.Errorf("unexpected type %T for column '%s'", accountID, key) - } - default: - return "", nil, fmt.Errorf("unknown key '%s' when building query", key) - } - })) -} diff --git a/components/payments/cmd/api/internal/storage/transfer_initiation_test.go b/components/payments/cmd/api/internal/storage/transfer_initiation_test.go deleted file mode 100644 index 0cd9aae7ef..0000000000 --- a/components/payments/cmd/api/internal/storage/transfer_initiation_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package storage - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -func insertTransferInitiation(t *testing.T, store *Storage, connectorID models.ConnectorID) []models.TransferInitiation { - tf1 := models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "tf_1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 14, 8, 0, 0, 0, time.UTC), - Description: "test_1", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: "EUR/2", - Metadata: map[string]string{ - "foo": "bar", - }, - } - _, err := store.DB().NewInsert(). - Model(&tf1). - Exec(context.Background()) - require.NoError(t, err) - - tf2 := models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "tf_2", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 14, 9, 0, 0, 0, time.UTC), - Description: "test_2", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "test_account", - ConnectorID: connectorID, - }, - DestinationAccountID: models.AccountID{ - Reference: "test_account2", - ConnectorID: connectorID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: "USD/2", - Metadata: map[string]string{ - "foo2": "bar2", - }, - } - - _, err = store.DB().NewInsert(). - Model(&tf2). - Exec(context.Background()) - require.NoError(t, err) - - return []models.TransferInitiation{tf1, tf2} -} - -func TestListTransferInitiation(t *testing.T) { - t.Parallel() - - store := newStore(t) - - connectorID := installConnector(t, store) - insertAccounts(t, store, connectorID) - tfs := insertTransferInitiation(t, store, connectorID) - - t.Run("list all transfer initiations with page size 1", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListTransferInitiations( - context.Background(), - NewListTransferInitiationsQuery(NewPaginatedQueryOptions(TransferInitiationQuery{}).WithPageSize(1)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].ScheduledAt = cursor.Data[0].ScheduledAt.UTC() - require.Equal(t, tfs[1], cursor.Data[0]) - - var query ListTransferInitiationsQuery - err = bunpaginate.UnmarshalCursor(cursor.Next, &query) - require.NoError(t, err) - cursor, err = store.ListTransferInitiations( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].ScheduledAt = cursor.Data[0].ScheduledAt.UTC() - require.Equal(t, tfs[0], cursor.Data[0]) - - err = bunpaginate.UnmarshalCursor(cursor.Previous, &query) - require.NoError(t, err) - cursor, err = store.ListTransferInitiations( - context.Background(), - query, - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 1) - require.True(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].ScheduledAt = cursor.Data[0].ScheduledAt.UTC() - require.Equal(t, tfs[1], cursor.Data[0]) - }) - - t.Run("list all transfer initiations with page size 2", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListTransferInitiations( - context.Background(), - NewListTransferInitiationsQuery(NewPaginatedQueryOptions(TransferInitiationQuery{}).WithPageSize(2)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].ScheduledAt = cursor.Data[0].ScheduledAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[1].ScheduledAt = cursor.Data[1].ScheduledAt.UTC() - require.Equal(t, tfs[1], cursor.Data[0]) - require.Equal(t, tfs[0], cursor.Data[1]) - }) - - t.Run("list all transfer initiations with page size > number of transfer initiations", func(t *testing.T) { - t.Parallel() - - cursor, err := store.ListTransferInitiations( - context.Background(), - NewListTransferInitiationsQuery(NewPaginatedQueryOptions(TransferInitiationQuery{}).WithPageSize(10)), - ) - require.NoError(t, err) - require.Len(t, cursor.Data, 2) - require.False(t, cursor.HasMore) - cursor.Data[0].CreatedAt = cursor.Data[0].CreatedAt.UTC() - cursor.Data[0].ScheduledAt = cursor.Data[0].ScheduledAt.UTC() - cursor.Data[1].CreatedAt = cursor.Data[1].CreatedAt.UTC() - cursor.Data[1].ScheduledAt = cursor.Data[1].ScheduledAt.UTC() - require.Equal(t, tfs[1], cursor.Data[0]) - require.Equal(t, tfs[0], cursor.Data[1]) - }) -} diff --git a/components/payments/cmd/api/internal/storage/utils.go b/components/payments/cmd/api/internal/storage/utils.go deleted file mode 100644 index f61f5e345d..0000000000 --- a/components/payments/cmd/api/internal/storage/utils.go +++ /dev/null @@ -1,7 +0,0 @@ -package storage - -import "regexp" - -var ( - metadataRegex = regexp.MustCompile("metadata\\[(.+)\\]") -) diff --git a/components/payments/cmd/api/root.go b/components/payments/cmd/api/root.go deleted file mode 100644 index 145e5cf88c..0000000000 --- a/components/payments/cmd/api/root.go +++ /dev/null @@ -1,47 +0,0 @@ -package api - -import ( - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/otlp" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/service" - "github.com/spf13/cobra" -) - -func NewAPI( - version string, - addAutoMigrateCommandFunc func(cmd *cobra.Command), -) *cobra.Command { - - root := &cobra.Command{ - Use: "api", - Short: "api", - DisableAutoGenTag: true, - } - - cobra.EnableTraverseRunHooks = true - - server := newServer(version) - addAutoMigrateCommandFunc(server) - root.AddCommand(server) - - server.Flags().BoolP("toggle", "t", false, "Help message for toggle") - server.Flags().String(configEncryptionKeyFlag, "", "Config encryption key") - server.Flags().String(envFlag, "local", "Environment") - server.Flags().String(listenFlag, ":8080", "Listen address") - - service.AddFlags(server.Flags()) - otlp.AddFlags(server.Flags()) - otlptraces.AddFlags(server.Flags()) - otlpmetrics.AddFlags(server.Flags()) - auth.AddFlags(server.Flags()) - publish.AddFlags(serviceName, server.Flags()) - bunconnect.AddFlags(server.Flags()) - iam.AddFlags(server.Flags()) - - return root -} diff --git a/components/payments/cmd/api/serve.go b/components/payments/cmd/api/serve.go deleted file mode 100644 index ae52dc9ed5..0000000000 --- a/components/payments/cmd/api/serve.go +++ /dev/null @@ -1,91 +0,0 @@ -package api - -import ( - "github.com/bombsimon/logrusr/v3" - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/payments/cmd/api/internal/api" - "github.com/formancehq/payments/cmd/api/internal/storage" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - "go.uber.org/fx" -) - -const ( - stackURLFlag = "stack-url" - configEncryptionKeyFlag = "config-encryption-key" - envFlag = "env" - listenFlag = "listen" - - serviceName = "Payments" -) - -func newServer(version string) *cobra.Command { - return &cobra.Command{ - Use: "serve", - Aliases: []string{"server"}, - Short: "Launch server", - SilenceUsage: true, - RunE: runServer(version), - } -} - -func runServer(version string) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - setLogger() - - databaseOptions, err := prepareDatabaseOptions(cmd, service.IsDebug(cmd)) - if err != nil { - return err - } - - options := make([]fx.Option, 0) - - options = append(options, databaseOptions) - options = append(options, - otlptraces.FXModuleFromFlags(cmd), - otlpmetrics.FXModuleFromFlags(cmd), - auth.FXModuleFromFlags(cmd), - fx.Provide(fx.Annotate(noop.NewMeterProvider, fx.As(new(metric.MeterProvider)))), - ) - options = append(options, publish.FXModuleFromFlags(cmd, service.IsDebug(cmd))) - listen, _ := cmd.Flags().GetString(listenFlag) - stackURL, _ := cmd.Flags().GetString(stackURLFlag) - otlpTraces, _ := cmd.Flags().GetBool(otlptraces.OtelTracesFlag) - - options = append(options, api.HTTPModule(sharedapi.ServiceInfo{ - Version: version, - Debug: service.IsDebug(cmd), - }, listen, stackURL, otlpTraces)) - - return service.New(cmd.OutOrStdout(), options...).Run(cmd) - } -} - -func setLogger() { - // Add a dedicated logger for opentelemetry in case of error - otel.SetLogger(logrusr.New(logrus.New().WithField("component", "otlp"))) -} - -func prepareDatabaseOptions(cmd *cobra.Command, debug bool) (fx.Option, error) { - configEncryptionKey, _ := cmd.Flags().GetString(configEncryptionKeyFlag) - if configEncryptionKey == "" { - return nil, errors.New("missing config encryption key") - } - - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return nil, err - } - - return storage.Module(*connectionOptions, configEncryptionKey, debug), nil -} diff --git a/components/payments/cmd/connectors/internal/api/api_utils_test.go b/components/payments/cmd/connectors/internal/api/api_utils_test.go deleted file mode 100644 index 266f5c1cf1..0000000000 --- a/components/payments/cmd/connectors/internal/api/api_utils_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package api - -import ( - "testing" - - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/dummypay" - gomock "github.com/golang/mock/gomock" -) - -func newServiceTestingBackend(t *testing.T) (*backend.MockServiceBackend, *backend.MockService) { - ctrl := gomock.NewController(t) - mockService := backend.NewMockService(ctrl) - backend := backend.NewMockServiceBackend(ctrl) - backend. - EXPECT(). - GetService(). - MinTimes(0). - Return(mockService) - t.Cleanup(func() { - ctrl.Finish() - }) - return backend, mockService -} - -func newConnectorManagerTestingBackend(t *testing.T) (*backend.MockManagerBackend[dummypay.Config], *backend.MockManager[dummypay.Config]) { - ctrl := gomock.NewController(t) - mockManager := backend.NewMockManager[dummypay.Config](ctrl) - backend := backend.NewMockManagerBackend[dummypay.Config](ctrl) - backend. - EXPECT(). - GetManager(). - MinTimes(0). - Return(mockManager) - t.Cleanup(func() { - ctrl.Finish() - }) - return backend, mockManager -} - -func ptr[T any](v T) *T { - return &v -} diff --git a/components/payments/cmd/connectors/internal/api/backend/backend.go b/components/payments/cmd/connectors/internal/api/backend/backend.go deleted file mode 100644 index 119117d37b..0000000000 --- a/components/payments/cmd/connectors/internal/api/backend/backend.go +++ /dev/null @@ -1,77 +0,0 @@ -package backend - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -//go:generate mockgen -source backend.go -destination backend_generated.go -package backend . Service -type Service interface { - Ping() error - CreateBankAccount(ctx context.Context, req *service.CreateBankAccountRequest) (*models.BankAccount, error) - ForwardBankAccountToConnector(ctx context.Context, id string, req *service.ForwardBankAccountToConnectorRequest) (*models.BankAccount, error) - UpdateBankAccountMetadata(ctx context.Context, id string, req *service.UpdateBankAccountMetadataRequest) error - ListConnectors(ctx context.Context) ([]*models.Connector, error) - CreateTransferInitiation(ctx context.Context, req *service.CreateTransferInitiationRequest) (*models.TransferInitiation, error) - UpdateTransferInitiationStatus(ctx context.Context, transferID string, req *service.UpdateTransferInitiationStatusRequest) error - RetryTransferInitiation(ctx context.Context, id string) error - DeleteTransferInitiation(ctx context.Context, id string) error - ReverseTransferInitiation(ctx context.Context, transferID string, req *service.ReverseTransferInitiationRequest) (*models.TransferReversal, error) -} - -//go:generate mockgen -source backend.go -destination backend_generated.go -package backend . Manager -type Manager[ConnectorConfig models.ConnectorConfigObject] interface { - IsInstalled(ctx context.Context, connectorID models.ConnectorID) (bool, error) - Connectors() map[string]*manager.ConnectorManager - ReadConfig(ctx context.Context, connectorID models.ConnectorID) (ConnectorConfig, error) - UpdateConfig(ctx context.Context, connectorID models.ConnectorID, config ConnectorConfig) error - ListTasksStates(ctx context.Context, connectorID models.ConnectorID, q storage.ListTasksQuery) (*bunpaginate.Cursor[models.Task], error) - CreateWebhookAndContext(ctx context.Context, webhook *models.Webhook) (context.Context, error) - ReadTaskState(ctx context.Context, connectorID models.ConnectorID, taskID uuid.UUID) (*models.Task, error) - Install(ctx context.Context, name string, config ConnectorConfig) (models.ConnectorID, error) - Reset(ctx context.Context, connectorID models.ConnectorID) error - Uninstall(ctx context.Context, connectorID models.ConnectorID) error -} - -type ServiceBackend interface { - GetService() Service -} - -type DefaultServiceBackend struct { - service Service -} - -func (d DefaultServiceBackend) GetService() Service { - return d.service -} - -func NewDefaultBackend(service Service) ServiceBackend { - return &DefaultServiceBackend{ - service: service, - } -} - -type ManagerBackend[ConnectorConfig models.ConnectorConfigObject] interface { - GetManager() Manager[ConnectorConfig] -} - -type DefaultManagerBackend[ConnectorConfig models.ConnectorConfigObject] struct { - manager Manager[ConnectorConfig] -} - -func (m DefaultManagerBackend[ConnectorConfig]) GetManager() Manager[ConnectorConfig] { - return m.manager -} - -func NewDefaultManagerBackend[ConnectorConfig models.ConnectorConfigObject](manager Manager[ConnectorConfig]) ManagerBackend[ConnectorConfig] { - return DefaultManagerBackend[ConnectorConfig]{ - manager: manager, - } -} diff --git a/components/payments/cmd/connectors/internal/api/backend/backend_generated.go b/components/payments/cmd/connectors/internal/api/backend/backend_generated.go deleted file mode 100644 index b4f7b09f33..0000000000 --- a/components/payments/cmd/connectors/internal/api/backend/backend_generated.go +++ /dev/null @@ -1,429 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: backend.go - -// Package backend is a generated GoMock package. -package backend - -import ( - context "context" - api "github.com/formancehq/go-libs/bun/bunpaginate" - reflect "reflect" - - connectors_manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - service "github.com/formancehq/payments/cmd/connectors/internal/api/service" - storage "github.com/formancehq/payments/cmd/connectors/internal/storage" - models "github.com/formancehq/payments/internal/models" - gomock "github.com/golang/mock/gomock" - uuid "github.com/google/uuid" -) - -// MockService is a mock of Service interface. -type MockService struct { - ctrl *gomock.Controller - recorder *MockServiceMockRecorder -} - -// MockServiceMockRecorder is the mock recorder for MockService. -type MockServiceMockRecorder struct { - mock *MockService -} - -// NewMockService creates a new mock instance. -func NewMockService(ctrl *gomock.Controller) *MockService { - mock := &MockService{ctrl: ctrl} - mock.recorder = &MockServiceMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockService) EXPECT() *MockServiceMockRecorder { - return m.recorder -} - -// CreateBankAccount mocks base method. -func (m *MockService) CreateBankAccount(ctx context.Context, req *service.CreateBankAccountRequest) (*models.BankAccount, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateBankAccount", ctx, req) - ret0, _ := ret[0].(*models.BankAccount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateBankAccount indicates an expected call of CreateBankAccount. -func (mr *MockServiceMockRecorder) CreateBankAccount(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBankAccount", reflect.TypeOf((*MockService)(nil).CreateBankAccount), ctx, req) -} - -// CreateTransferInitiation mocks base method. -func (m *MockService) CreateTransferInitiation(ctx context.Context, req *service.CreateTransferInitiationRequest) (*models.TransferInitiation, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTransferInitiation", ctx, req) - ret0, _ := ret[0].(*models.TransferInitiation) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateTransferInitiation indicates an expected call of CreateTransferInitiation. -func (mr *MockServiceMockRecorder) CreateTransferInitiation(ctx, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTransferInitiation", reflect.TypeOf((*MockService)(nil).CreateTransferInitiation), ctx, req) -} - -// DeleteTransferInitiation mocks base method. -func (m *MockService) DeleteTransferInitiation(ctx context.Context, id string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteTransferInitiation", ctx, id) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteTransferInitiation indicates an expected call of DeleteTransferInitiation. -func (mr *MockServiceMockRecorder) DeleteTransferInitiation(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTransferInitiation", reflect.TypeOf((*MockService)(nil).DeleteTransferInitiation), ctx, id) -} - -// ForwardBankAccountToConnector mocks base method. -func (m *MockService) ForwardBankAccountToConnector(ctx context.Context, id string, req *service.ForwardBankAccountToConnectorRequest) (*models.BankAccount, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ForwardBankAccountToConnector", ctx, id, req) - ret0, _ := ret[0].(*models.BankAccount) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ForwardBankAccountToConnector indicates an expected call of ForwardBankAccountToConnector. -func (mr *MockServiceMockRecorder) ForwardBankAccountToConnector(ctx, id, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForwardBankAccountToConnector", reflect.TypeOf((*MockService)(nil).ForwardBankAccountToConnector), ctx, id, req) -} - -// ListConnectors mocks base method. -func (m *MockService) ListConnectors(ctx context.Context) ([]*models.Connector, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListConnectors", ctx) - ret0, _ := ret[0].([]*models.Connector) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListConnectors indicates an expected call of ListConnectors. -func (mr *MockServiceMockRecorder) ListConnectors(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListConnectors", reflect.TypeOf((*MockService)(nil).ListConnectors), ctx) -} - -// Ping mocks base method. -func (m *MockService) Ping() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Ping") - ret0, _ := ret[0].(error) - return ret0 -} - -// Ping indicates an expected call of Ping. -func (mr *MockServiceMockRecorder) Ping() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockService)(nil).Ping)) -} - -// RetryTransferInitiation mocks base method. -func (m *MockService) RetryTransferInitiation(ctx context.Context, id string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RetryTransferInitiation", ctx, id) - ret0, _ := ret[0].(error) - return ret0 -} - -// RetryTransferInitiation indicates an expected call of RetryTransferInitiation. -func (mr *MockServiceMockRecorder) RetryTransferInitiation(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryTransferInitiation", reflect.TypeOf((*MockService)(nil).RetryTransferInitiation), ctx, id) -} - -// ReverseTransferInitiation mocks base method. -func (m *MockService) ReverseTransferInitiation(ctx context.Context, transferID string, req *service.ReverseTransferInitiationRequest) (*models.TransferReversal, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReverseTransferInitiation", ctx, transferID, req) - ret0, _ := ret[0].(*models.TransferReversal) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReverseTransferInitiation indicates an expected call of ReverseTransferInitiation. -func (mr *MockServiceMockRecorder) ReverseTransferInitiation(ctx, transferID, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReverseTransferInitiation", reflect.TypeOf((*MockService)(nil).ReverseTransferInitiation), ctx, transferID, req) -} - -// UpdateBankAccountMetadata mocks base method. -func (m *MockService) UpdateBankAccountMetadata(ctx context.Context, id string, req *service.UpdateBankAccountMetadataRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateBankAccountMetadata", ctx, id, req) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateBankAccountMetadata indicates an expected call of UpdateBankAccountMetadata. -func (mr *MockServiceMockRecorder) UpdateBankAccountMetadata(ctx, id, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateBankAccountMetadata", reflect.TypeOf((*MockService)(nil).UpdateBankAccountMetadata), ctx, id, req) -} - -// UpdateTransferInitiationStatus mocks base method. -func (m *MockService) UpdateTransferInitiationStatus(ctx context.Context, transferID string, req *service.UpdateTransferInitiationStatusRequest) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateTransferInitiationStatus", ctx, transferID, req) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateTransferInitiationStatus indicates an expected call of UpdateTransferInitiationStatus. -func (mr *MockServiceMockRecorder) UpdateTransferInitiationStatus(ctx, transferID, req interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTransferInitiationStatus", reflect.TypeOf((*MockService)(nil).UpdateTransferInitiationStatus), ctx, transferID, req) -} - -// MockManager is a mock of Manager interface. -type MockManager[ConnectorConfig models.ConnectorConfigObject] struct { - ctrl *gomock.Controller - recorder *MockManagerMockRecorder[ConnectorConfig] -} - -// MockManagerMockRecorder is the mock recorder for MockManager. -type MockManagerMockRecorder[ConnectorConfig models.ConnectorConfigObject] struct { - mock *MockManager[ConnectorConfig] -} - -// NewMockManager creates a new mock instance. -func NewMockManager[ConnectorConfig models.ConnectorConfigObject](ctrl *gomock.Controller) *MockManager[ConnectorConfig] { - mock := &MockManager[ConnectorConfig]{ctrl: ctrl} - mock.recorder = &MockManagerMockRecorder[ConnectorConfig]{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockManager[ConnectorConfig]) EXPECT() *MockManagerMockRecorder[ConnectorConfig] { - return m.recorder -} - -// Connectors mocks base method. -func (m *MockManager[ConnectorConfig]) Connectors() map[string]*connectors_manager.ConnectorManager { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Connectors") - ret0, _ := ret[0].(map[string]*connectors_manager.ConnectorManager) - return ret0 -} - -// Connectors indicates an expected call of Connectors. -func (mr *MockManagerMockRecorder[ConnectorConfig]) Connectors() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connectors", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).Connectors)) -} - -// CreateWebhookAndContext mocks base method. -func (m *MockManager[ConnectorConfig]) CreateWebhookAndContext(ctx context.Context, webhook *models.Webhook) (context.Context, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateWebhookAndContext", ctx, webhook) - ret0, _ := ret[0].(context.Context) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateWebhookAndContext indicates an expected call of CreateWebhookAndContext. -func (mr *MockManagerMockRecorder[ConnectorConfig]) CreateWebhookAndContext(ctx, webhook interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWebhookAndContext", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).CreateWebhookAndContext), ctx, webhook) -} - -// Install mocks base method. -func (m *MockManager[ConnectorConfig]) Install(ctx context.Context, name string, config ConnectorConfig) (models.ConnectorID, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Install", ctx, name, config) - ret0, _ := ret[0].(models.ConnectorID) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Install indicates an expected call of Install. -func (mr *MockManagerMockRecorder[ConnectorConfig]) Install(ctx, name, config interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Install", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).Install), ctx, name, config) -} - -// IsInstalled mocks base method. -func (m *MockManager[ConnectorConfig]) IsInstalled(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "IsInstalled", ctx, connectorID) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// IsInstalled indicates an expected call of IsInstalled. -func (mr *MockManagerMockRecorder[ConnectorConfig]) IsInstalled(ctx, connectorID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsInstalled", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).IsInstalled), ctx, connectorID) -} - -// ListTasksStates mocks base method. -func (m *MockManager[ConnectorConfig]) ListTasksStates(ctx context.Context, connectorID models.ConnectorID, q storage.ListTasksQuery) (*api.Cursor[models.Task], error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListTasksStates", ctx, connectorID, q) - ret0, _ := ret[0].(*api.Cursor[models.Task]) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListTasksStates indicates an expected call of ListTasksStates. -func (mr *MockManagerMockRecorder[ConnectorConfig]) ListTasksStates(ctx, connectorID, q interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTasksStates", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).ListTasksStates), ctx, connectorID, q) -} - -// ReadConfig mocks base method. -func (m *MockManager[ConnectorConfig]) ReadConfig(ctx context.Context, connectorID models.ConnectorID) (ConnectorConfig, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadConfig", ctx, connectorID) - ret0, _ := ret[0].(ConnectorConfig) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReadConfig indicates an expected call of ReadConfig. -func (mr *MockManagerMockRecorder[ConnectorConfig]) ReadConfig(ctx, connectorID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadConfig", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).ReadConfig), ctx, connectorID) -} - -// ReadTaskState mocks base method. -func (m *MockManager[ConnectorConfig]) ReadTaskState(ctx context.Context, connectorID models.ConnectorID, taskID uuid.UUID) (*models.Task, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadTaskState", ctx, connectorID, taskID) - ret0, _ := ret[0].(*models.Task) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReadTaskState indicates an expected call of ReadTaskState. -func (mr *MockManagerMockRecorder[ConnectorConfig]) ReadTaskState(ctx, connectorID, taskID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadTaskState", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).ReadTaskState), ctx, connectorID, taskID) -} - -// Reset mocks base method. -func (m *MockManager[ConnectorConfig]) Reset(ctx context.Context, connectorID models.ConnectorID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reset", ctx, connectorID) - ret0, _ := ret[0].(error) - return ret0 -} - -// Reset indicates an expected call of Reset. -func (mr *MockManagerMockRecorder[ConnectorConfig]) Reset(ctx, connectorID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).Reset), ctx, connectorID) -} - -// Uninstall mocks base method. -func (m *MockManager[ConnectorConfig]) Uninstall(ctx context.Context, connectorID models.ConnectorID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Uninstall", ctx, connectorID) - ret0, _ := ret[0].(error) - return ret0 -} - -// Uninstall indicates an expected call of Uninstall. -func (mr *MockManagerMockRecorder[ConnectorConfig]) Uninstall(ctx, connectorID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Uninstall", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).Uninstall), ctx, connectorID) -} - -// UpdateConfig mocks base method. -func (m *MockManager[ConnectorConfig]) UpdateConfig(ctx context.Context, connectorID models.ConnectorID, config ConnectorConfig) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateConfig", ctx, connectorID, config) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateConfig indicates an expected call of UpdateConfig. -func (mr *MockManagerMockRecorder[ConnectorConfig]) UpdateConfig(ctx, connectorID, config interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateConfig", reflect.TypeOf((*MockManager[ConnectorConfig])(nil).UpdateConfig), ctx, connectorID, config) -} - -// MockServiceBackend is a mock of ServiceBackend interface. -type MockServiceBackend struct { - ctrl *gomock.Controller - recorder *MockServiceBackendMockRecorder -} - -// MockServiceBackendMockRecorder is the mock recorder for MockServiceBackend. -type MockServiceBackendMockRecorder struct { - mock *MockServiceBackend -} - -// NewMockServiceBackend creates a new mock instance. -func NewMockServiceBackend(ctrl *gomock.Controller) *MockServiceBackend { - mock := &MockServiceBackend{ctrl: ctrl} - mock.recorder = &MockServiceBackendMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockServiceBackend) EXPECT() *MockServiceBackendMockRecorder { - return m.recorder -} - -// GetService mocks base method. -func (m *MockServiceBackend) GetService() Service { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetService") - ret0, _ := ret[0].(Service) - return ret0 -} - -// GetService indicates an expected call of GetService. -func (mr *MockServiceBackendMockRecorder) GetService() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockServiceBackend)(nil).GetService)) -} - -// MockManagerBackend is a mock of ManagerBackend interface. -type MockManagerBackend[ConnectorConfig models.ConnectorConfigObject] struct { - ctrl *gomock.Controller - recorder *MockManagerBackendMockRecorder[ConnectorConfig] -} - -// MockManagerBackendMockRecorder is the mock recorder for MockManagerBackend. -type MockManagerBackendMockRecorder[ConnectorConfig models.ConnectorConfigObject] struct { - mock *MockManagerBackend[ConnectorConfig] -} - -// NewMockManagerBackend creates a new mock instance. -func NewMockManagerBackend[ConnectorConfig models.ConnectorConfigObject](ctrl *gomock.Controller) *MockManagerBackend[ConnectorConfig] { - mock := &MockManagerBackend[ConnectorConfig]{ctrl: ctrl} - mock.recorder = &MockManagerBackendMockRecorder[ConnectorConfig]{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockManagerBackend[ConnectorConfig]) EXPECT() *MockManagerBackendMockRecorder[ConnectorConfig] { - return m.recorder -} - -// GetManager mocks base method. -func (m *MockManagerBackend[ConnectorConfig]) GetManager() Manager[ConnectorConfig] { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetManager") - ret0, _ := ret[0].(Manager[ConnectorConfig]) - return ret0 -} - -// GetManager indicates an expected call of GetManager. -func (mr *MockManagerBackendMockRecorder[ConnectorConfig]) GetManager() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetManager", reflect.TypeOf((*MockManagerBackend[ConnectorConfig])(nil).GetManager)) -} diff --git a/components/payments/cmd/connectors/internal/api/bank_account.go b/components/payments/cmd/connectors/internal/api/bank_account.go deleted file mode 100644 index 5091454d12..0000000000 --- a/components/payments/cmd/connectors/internal/api/bank_account.go +++ /dev/null @@ -1,240 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -type bankAccountRelatedAccountsResponse struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - AccountID string `json:"accountID"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` -} - -type bankAccountResponse struct { - ID string `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - Country string `json:"country"` - Iban string `json:"iban,omitempty"` - AccountNumber string `json:"accountNumber,omitempty"` - SwiftBicCode string `json:"swiftBicCode,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` - RelatedAccounts []*bankAccountRelatedAccountsResponse `json:"relatedAccounts,omitempty"` - - // Deprecated fields, but clients still use them - // They correspond to the first adjustment now. - Provider string `json:"provider,omitempty"` - ConnectorID string `json:"connectorID"` - AccountID string `json:"accountID,omitempty"` -} - -func createBankAccountHandler( - b backend.ServiceBackend, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "createBankAccountHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - var bankAccountRequest service.CreateBankAccountRequest - err := json.NewDecoder(r.Body).Decode(&bankAccountRequest) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - setAttributesFromRequest(span, &bankAccountRequest) - - if err := bankAccountRequest.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - bankAccount, err := b.GetService().CreateBankAccount(ctx, &bankAccountRequest) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - span.SetAttributes(attribute.String("bankAccount.id", bankAccount.ID.String())) - span.SetAttributes(attribute.String("bankAccount.createdAt", bankAccount.ID.String())) - - data := &bankAccountResponse{ - ID: bankAccount.ID.String(), - Name: bankAccount.Name, - CreatedAt: bankAccount.CreatedAt, - Country: bankAccount.Country, - Metadata: bankAccount.Metadata, - } - - for _, relatedAccount := range bankAccount.RelatedAccounts { - data.RelatedAccounts = append(data.RelatedAccounts, &bankAccountRelatedAccountsResponse{ - ID: relatedAccount.ID.String(), - CreatedAt: relatedAccount.CreatedAt, - AccountID: relatedAccount.AccountID.String(), - ConnectorID: relatedAccount.ConnectorID.String(), - Provider: relatedAccount.ConnectorID.Provider.String(), - }) - } - - // Keep compatibility with previous api version - data.ConnectorID = bankAccountRequest.ConnectorID - if len(bankAccount.RelatedAccounts) > 0 { - data.AccountID = bankAccount.RelatedAccounts[0].AccountID.String() - data.Provider = bankAccount.RelatedAccounts[0].ConnectorID.Provider.String() - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[bankAccountResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func forwardBankAccountToConnector( - b backend.ServiceBackend, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "forwardBankAccountToConnector") - defer span.End() - - payload := &service.ForwardBankAccountToConnectorRequest{} - err := json.NewDecoder(r.Body).Decode(payload) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes(attribute.String("request.connectorID", payload.ConnectorID)) - - if err := payload.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - bankAccountID, ok := mux.Vars(r)["bankAccountID"] - if !ok { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("bankAccount.id", bankAccountID)) - - bankAccount, err := b.GetService().ForwardBankAccountToConnector(ctx, bankAccountID, payload) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - data := &bankAccountResponse{ - ID: bankAccount.ID.String(), - Name: bankAccount.Name, - CreatedAt: bankAccount.CreatedAt, - Country: bankAccount.Country, - Metadata: bankAccount.Metadata, - } - - for _, relatedAccount := range bankAccount.RelatedAccounts { - data.RelatedAccounts = append(data.RelatedAccounts, &bankAccountRelatedAccountsResponse{ - ID: relatedAccount.ID.String(), - CreatedAt: relatedAccount.CreatedAt, - AccountID: relatedAccount.AccountID.String(), - ConnectorID: relatedAccount.ConnectorID.String(), - Provider: relatedAccount.ConnectorID.Provider.String(), - }) - } - - // Keep compatibility with previous api version - data.ConnectorID = payload.ConnectorID - if len(bankAccount.RelatedAccounts) > 0 { - data.AccountID = bankAccount.RelatedAccounts[0].AccountID.String() - data.Provider = bankAccount.RelatedAccounts[0].ConnectorID.Provider.String() - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[bankAccountResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func updateBankAccountMetadataHandler( - b backend.ServiceBackend, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "updateBankAccountMetadataHandler") - defer span.End() - - payload := &service.UpdateBankAccountMetadataRequest{} - err := json.NewDecoder(r.Body).Decode(payload) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - for k, v := range payload.Metadata { - span.SetAttributes(attribute.String(k, v)) - } - - if err := payload.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - bankAccountID, ok := mux.Vars(r)["bankAccountID"] - if !ok { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("bankAccount.id", bankAccountID)) - - err = b.GetService().UpdateBankAccountMetadata(ctx, bankAccountID, payload) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - api.NoContent(w) - } -} - -func setAttributesFromRequest(span trace.Span, request *service.CreateBankAccountRequest) { - span.SetAttributes( - attribute.String("request.name", request.Name), - attribute.String("request.country", request.Country), - attribute.String("request.connectorID", request.ConnectorID), - ) -} diff --git a/components/payments/cmd/connectors/internal/api/bank_account_test.go b/components/payments/cmd/connectors/internal/api/bank_account_test.go deleted file mode 100644 index 68189ac260..0000000000 --- a/components/payments/cmd/connectors/internal/api/bank_account_test.go +++ /dev/null @@ -1,626 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestCreateBankAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.CreateBankAccountRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - acc1 := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - testCases := []testCase{ - { - name: "nominal", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - }, - { - name: "nominal without connectorID", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - Name: "test_nominal", - }, - }, - { - name: "no body", - req: nil, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "missing AccountNumber and Iban", - req: &service.CreateBankAccountRequest{ - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing name", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing country", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - - { - name: "service error duplicate key", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: service.ErrInvalidID, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: service.ErrPublish, - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - req: &service.CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorID.String(), - Name: "test_nominal", - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - bankAccountID := uuid.New() - createBankAccountResponse := models.BankAccount{ - ID: bankAccountID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "test_nominal", - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - } - - if testCase.req != nil && testCase.req.ConnectorID != "" { - createBankAccountResponse.RelatedAccounts = []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - BankAccountID: bankAccountID, - ConnectorID: connectorID, - AccountID: acc1, - }, - } - } - - expectedCreateBankAccountResponse := &bankAccountResponse{ - ID: createBankAccountResponse.ID.String(), - Name: createBankAccountResponse.Name, - CreatedAt: createBankAccountResponse.CreatedAt, - Country: createBankAccountResponse.Country, - } - - if testCase.req != nil && testCase.req.ConnectorID != "" { - expectedCreateBankAccountResponse.ConnectorID = createBankAccountResponse.RelatedAccounts[0].ConnectorID.String() - expectedCreateBankAccountResponse.AccountID = createBankAccountResponse.RelatedAccounts[0].AccountID.String() - expectedCreateBankAccountResponse.Provider = createBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String() - expectedCreateBankAccountResponse.RelatedAccounts = []*bankAccountRelatedAccountsResponse{ - { - ID: createBankAccountResponse.RelatedAccounts[0].ID.String(), - AccountID: createBankAccountResponse.RelatedAccounts[0].AccountID.String(), - ConnectorID: createBankAccountResponse.RelatedAccounts[0].ConnectorID.String(), - Provider: createBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String(), - }, - } - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - CreateBankAccount(gomock.Any(), testCase.req). - Return(&createBankAccountResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - CreateBankAccount(gomock.Any(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), nil, false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, "/bank-accounts", bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[bankAccountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedCreateBankAccountResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestForwardBankAccountToConnector(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.ForwardBankAccountToConnectorRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - acc1 := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - testCases := []testCase{ - { - name: "nominal", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - }, - { - name: "nominal without connectorID", - req: &service.ForwardBankAccountToConnectorRequest{}, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "no body", - req: nil, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "service error duplicate key", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: service.ErrInvalidID, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: service.ErrPublish, - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - req: &service.ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorID.String(), - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - bankAccountID := uuid.New() - forwardBankAccountResponse := models.BankAccount{ - ID: bankAccountID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "test_nominal", - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - } - - if testCase.req != nil && testCase.req.ConnectorID != "" { - forwardBankAccountResponse.RelatedAccounts = []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - BankAccountID: bankAccountID, - ConnectorID: connectorID, - AccountID: acc1, - }, - } - } - - expectedForwardBankAccountResponse := &bankAccountResponse{ - ID: forwardBankAccountResponse.ID.String(), - Name: forwardBankAccountResponse.Name, - CreatedAt: forwardBankAccountResponse.CreatedAt, - Country: forwardBankAccountResponse.Country, - } - - if testCase.req != nil && testCase.req.ConnectorID != "" { - expectedForwardBankAccountResponse.ConnectorID = forwardBankAccountResponse.RelatedAccounts[0].ConnectorID.String() - expectedForwardBankAccountResponse.AccountID = forwardBankAccountResponse.RelatedAccounts[0].AccountID.String() - expectedForwardBankAccountResponse.Provider = forwardBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String() - expectedForwardBankAccountResponse.RelatedAccounts = []*bankAccountRelatedAccountsResponse{ - { - ID: forwardBankAccountResponse.RelatedAccounts[0].ID.String(), - AccountID: forwardBankAccountResponse.RelatedAccounts[0].AccountID.String(), - ConnectorID: forwardBankAccountResponse.RelatedAccounts[0].ConnectorID.String(), - Provider: forwardBankAccountResponse.RelatedAccounts[0].ConnectorID.Provider.String(), - }, - } - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ForwardBankAccountToConnector(gomock.Any(), bankAccountID.String(), testCase.req). - Return(&forwardBankAccountResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ForwardBankAccountToConnector(gomock.Any(), bankAccountID.String(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), nil, false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/bank-accounts/%s/forward", bankAccountID), bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[bankAccountResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedForwardBankAccountResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestUpdateBankAccountMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.UpdateBankAccountMetadataRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nominal", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "empty metadata", - req: &service.UpdateBankAccountMetadataRequest{}, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "no body", - req: nil, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "service error duplicate key", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: service.ErrInvalidID, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: service.ErrPublish, - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - req: &service.UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - bankAccountID := uuid.New() - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - UpdateBankAccountMetadata(gomock.Any(), bankAccountID.String(), testCase.req). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - UpdateBankAccountMetadata(gomock.Any(), bankAccountID.String(), testCase.req). - Return(testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{ - Debug: testing.Verbose(), - }, auth.NewNoAuth(), nil, false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPatch, fmt.Sprintf("/bank-accounts/%s/metadata", bankAccountID), bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/connector.go b/components/payments/cmd/connectors/internal/api/connector.go deleted file mode 100644 index e027cb35af..0000000000 --- a/components/payments/cmd/connectors/internal/api/connector.go +++ /dev/null @@ -1,483 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/pointer" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "github.com/gorilla/mux" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -type APIVersion int - -const ( - V0 APIVersion = iota - V1 APIVersion = iota -) - -func (a APIVersion) String() string { - switch a { - case V0: - return "v0" - case V1: - return "v1" - default: - return "unknown" - } -} - -func updateConfig[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "updateConfig") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - var config Config - if r.ContentLength > 0 { - err := json.NewDecoder(r.Body).Decode(&config) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - } - - err = b.GetManager().UpdateConfig(ctx, connectorID, config) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - api.NoContent(w) - } -} - -func readConfig[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readConfig") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("connectorID", connectorID.String())) - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - config, err := b.GetManager().ReadConfig(ctx, connectorID) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[Config]{ - Data: &config, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -type listTasksResponseElement struct { - ID string `json:"id"` - ConnectorID string `json:"connectorID"` - CreatedAt string `json:"createdAt"` - UpdatedAt string `json:"updatedAt"` - Descriptor json.RawMessage `json:"descriptor"` - Status models.TaskStatus `json:"status"` - State json.RawMessage `json:"state"` - Error string `json:"error"` -} - -func listTasks[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "listTasks") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - query, err := bunpaginate.Extract[storage.ListTasksQuery](r, func() (*storage.ListTasksQuery, error) { - pageSize, err := bunpaginate.GetPageSize(r) - if err != nil { - return nil, err - } - - return pointer.For(storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(pageSize))), nil - }) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - span.SetAttributes(attribute.Int("pageSize", int(query.PageSize))) - span.SetAttributes(attribute.String("cursor", r.URL.Query().Get("cursor"))) - - cursor, err := b.GetManager().ListTasksStates(ctx, connectorID, *query) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - tasks := cursor.Data - data := make([]listTasksResponseElement, len(tasks)) - for i, task := range tasks { - data[i] = listTasksResponseElement{ - ID: task.ID.String(), - ConnectorID: task.ConnectorID.String(), - CreatedAt: task.CreatedAt.Format(time.RFC3339), - UpdatedAt: task.UpdatedAt.Format(time.RFC3339), - Descriptor: task.Descriptor, - Status: task.Status, - State: task.State, - Error: task.Error, - } - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[listTasksResponseElement]{ - Cursor: &bunpaginate.Cursor[listTasksResponseElement]{ - PageSize: cursor.PageSize, - HasMore: cursor.HasMore, - Previous: cursor.Previous, - Next: cursor.Next, - Data: data, - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func webhooksMiddleware[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) func(handler http.Handler) http.Handler { - return func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "webhooksMiddleware") - defer span.End() - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - body, err := io.ReadAll(r.Body) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - } - defer r.Body.Close() - - webhook := &models.Webhook{ - ID: uuid.New(), - ConnectorID: connectorID, - RequestBody: body, - } - - span.SetAttributes(attribute.String("webhook.id", webhook.ID.String())) - - ctx, err = b.GetManager().CreateWebhookAndContext(ctx, webhook) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - handler.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} - -func readTask[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readTask") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - taskID, err := uuid.Parse(mux.Vars(r)["taskID"]) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - span.SetAttributes(attribute.String("taskID", taskID.String())) - - task, err := b.GetManager().ReadTaskState(ctx, connectorID, taskID) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - data := listTasksResponseElement{ - ID: task.ID.String(), - ConnectorID: task.ConnectorID.String(), - CreatedAt: task.CreatedAt.Format(time.RFC3339), - UpdatedAt: task.UpdatedAt.Format(time.RFC3339), - Descriptor: task.Descriptor, - Status: task.Status, - State: task.State, - Error: task.Error, - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[listTasksResponseElement]{ - Data: &data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func uninstall[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "uninstall") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - err = b.GetManager().Uninstall(ctx, connectorID) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -type installResponse struct { - ConnectorID string `json:"connectorID"` -} - -func install[Config models.ConnectorConfigObject](b backend.ManagerBackend[Config]) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "install") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - var config Config - if r.ContentLength > 0 { - err := json.NewDecoder(r.Body).Decode(&config) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - } - - connectorID, err := b.GetManager().Install(ctx, config.ConnectorName(), config) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusCreated) - err = json.NewEncoder(w).Encode(api.BaseResponse[installResponse]{ - Data: &installResponse{ - ConnectorID: connectorID.String(), - }, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func reset[Config models.ConnectorConfigObject]( - b backend.ManagerBackend[Config], - apiVersion APIVersion, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "reset") - defer span.End() - - span.SetAttributes(attribute.String("apiVersion", apiVersion.String())) - - connectorID, err := getConnectorID(span, b, r, apiVersion) - if err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrInvalidID, err) - return - } - - if connectorNotInstalled(span, b, connectorID, w, r) { - return - } - - err = b.GetManager().Reset(ctx, connectorID) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func connectorNotInstalled[Config models.ConnectorConfigObject]( - span trace.Span, - b backend.ManagerBackend[Config], - connectorID models.ConnectorID, - w http.ResponseWriter, r *http.Request, -) bool { - installed, err := b.GetManager().IsInstalled(r.Context(), connectorID) - if err != nil { - otel.RecordError(span, err) - handleConnectorsManagerErrors(w, r, err) - return true - } - - if !installed { - otel.RecordError(span, fmt.Errorf("connector not installed")) - api.BadRequest(w, ErrValidation, fmt.Errorf("connector not installed")) - return true - } - - return false -} - -func getConnectorID[Config models.ConnectorConfigObject]( - span trace.Span, - b backend.ManagerBackend[Config], - r *http.Request, - apiVersion APIVersion, -) (models.ConnectorID, error) { - switch apiVersion { - case V0: - connectors := b.GetManager().Connectors() - if len(connectors) == 0 { - return models.ConnectorID{}, fmt.Errorf("no connectors installed") - } - - span.SetAttributes(attribute.Int("connectors.count", len(connectors))) - - if len(connectors) > 1 { - return models.ConnectorID{}, fmt.Errorf("more than one connectors installed") - } - - for id := range connectors { - return models.MustConnectorIDFromString(id), nil - } - - case V1: - c := mux.Vars(r)["connectorID"] - - span.SetAttributes(attribute.String("connectorID", c)) - - connectorID, err := models.ConnectorIDFromString(c) - if err != nil { - return models.ConnectorID{}, err - } - - return connectorID, nil - } - - return models.ConnectorID{}, fmt.Errorf("unknown API version") -} diff --git a/components/payments/cmd/connectors/internal/api/connector_test.go b/components/payments/cmd/connectors/internal/api/connector_test.go deleted file mode 100644 index c7ad36207b..0000000000 --- a/components/payments/cmd/connectors/internal/api/connector_test.go +++ /dev/null @@ -1,1356 +0,0 @@ -package api - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/dummypay" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestReadConfig(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - connectorID string - connectors map[string]*manager.ConnectorManager - installed *bool - apiVersion APIVersion - expectedStatusCode int - expectedErrorCode string - managerError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - // V0 tests - { - name: "nominal V0", - apiVersion: V0, - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - }, - installed: ptr(true), - }, - { - name: "too many connectors for V0", - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - "1": nil, - }, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "no connector for V0", - connectors: map[string]*manager.ConnectorManager{}, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - // V1 tests - { - name: "nominal V1", - connectorID: connectorID.String(), - apiVersion: V1, - installed: ptr(true), - }, - // Common test for V0 and V1 - { - name: "connector not installed", - connectorID: connectorID.String(), - apiVersion: V1, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(false), - }, - { - name: "manager error duplicate key value storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - installed: ptr(true), - }, - { - name: "manager error err not found storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error already installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error not installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error connector not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error err not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error err validation", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error other errors", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - installed: ptr(true), - }, - } - - for _, tc := range testCases { - testCase := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - readConfigResponse := dummypay.Config{ - Name: "test", - Directory: "test", - FilePollingPeriod: connectors.Duration{ - Duration: 2 * time.Minute, - }, - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - ReadConfig(gomock.Any(), connectorID). - Return(readConfigResponse, nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - ReadConfig(gomock.Any(), connectorID). - Return(dummypay.Config{}, testCase.managerError) - } - - if testCase.apiVersion == V0 { - mockManager.EXPECT(). - Connectors(). - Return(testCase.connectors) - } - - if testCase.installed != nil { - mockManager.EXPECT(). - IsInstalled(gomock.Any(), connectorID). - Return(*testCase.installed, nil) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - var endpoint string - switch testCase.apiVersion { - case V0: - endpoint = "/connectors/dummy-pay/config" - case V1: - endpoint = fmt.Sprintf("/connectors/dummy-pay/%s/config", testCase.connectorID) - } - req := httptest.NewRequest(http.MethodGet, endpoint, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[dummypay.Config] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, &readConfigResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestListTasks(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - connectorID string - connectors map[string]*manager.ConnectorManager - installed *bool - apiVersion APIVersion - queryParams url.Values - pageSize int - expectedQuery storage.ListTasksQuery - expectedStatusCode int - expectedErrorCode string - managerError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - // V0 tests - { - name: "nominal V0", - apiVersion: V0, - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - }, - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - connectorID: connectorID.String(), - installed: ptr(true), - queryParams: map[string][]string{}, - pageSize: 15, - }, - { - name: "too many connectors for V0", - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - "1": nil, - }, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "no connector for V0", - connectors: map[string]*manager.ConnectorManager{}, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - // V1 tests - { - name: "nominal V1", - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - connectorID: connectorID.String(), - installed: ptr(true), - apiVersion: V1, - pageSize: 15, - }, - // Common test for V0 and V1 - { - name: "page size too low", - queryParams: url.Values{ - "pageSize": {"0"}, - }, - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - pageSize: 15, - connectorID: connectorID.String(), - installed: ptr(true), - apiVersion: V1, - }, - { - name: "page size too high", - queryParams: url.Values{ - "pageSize": {"100000"}, - }, - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(100)), - pageSize: 100, - connectorID: connectorID.String(), - installed: ptr(true), - apiVersion: V1, - }, - { - name: "with invalid page size", - queryParams: url.Values{ - "pageSize": {"nan"}, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - connectorID: connectorID.String(), - installed: ptr(true), - apiVersion: V1, - }, - { - name: "connector not installed", - connectorID: connectorID.String(), - apiVersion: V1, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(false), - }, - { - name: "manager error duplicate key value storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error err not found storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error already installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error not installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error connector not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error err not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error err validation", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - { - name: "manager error other errors", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - installed: ptr(true), - expectedQuery: storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}).WithPageSize(15)), - }, - } - - for _, tc := range testCases { - testCase := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - tasks := []models.Task{ - { - ID: uuid.New(), - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "test", - Descriptor: []byte("{}"), - Status: models.TaskStatusActive, - State: json.RawMessage("{}"), - }, - } - listTasksResponse := &bunpaginate.Cursor[models.Task]{ - PageSize: testCase.pageSize, - HasMore: false, - Previous: "", - Next: "", - Data: tasks, - } - - expectedListTasksResponse := []listTasksResponseElement{ - { - ID: tasks[0].ID.String(), - ConnectorID: tasks[0].ConnectorID.String(), - CreatedAt: tasks[0].CreatedAt.Format(time.RFC3339), - UpdatedAt: tasks[0].UpdatedAt.Format(time.RFC3339), - Descriptor: tasks[0].Descriptor, - Status: tasks[0].Status, - State: tasks[0].State, - Error: tasks[0].Error, - }, - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - ListTasksStates(gomock.Any(), connectorID, testCase.expectedQuery). - Return(listTasksResponse, nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - ListTasksStates(gomock.Any(), connectorID, testCase.expectedQuery). - Return(nil, testCase.managerError) - } - - if testCase.apiVersion == V0 { - mockManager.EXPECT(). - Connectors(). - Return(testCase.connectors) - } - - if testCase.installed != nil { - mockManager.EXPECT(). - IsInstalled(gomock.Any(), connectorID). - Return(*testCase.installed, nil) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - var endpoint string - switch testCase.apiVersion { - case V0: - endpoint = "/connectors/dummy-pay/tasks" - case V1: - endpoint = fmt.Sprintf("/connectors/dummy-pay/%s/tasks", testCase.connectorID) - } - req := httptest.NewRequest(http.MethodGet, endpoint, nil) - rec := httptest.NewRecorder() - req.URL.RawQuery = testCase.queryParams.Encode() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[listTasksResponseElement] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedListTasksResponse, resp.Cursor.Data) - require.Equal(t, listTasksResponse.PageSize, resp.Cursor.PageSize) - require.Equal(t, listTasksResponse.HasMore, resp.Cursor.HasMore) - require.Equal(t, listTasksResponse.Next, resp.Cursor.Next) - require.Equal(t, listTasksResponse.Previous, resp.Cursor.Previous) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestReadTask(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - connectorID string - taskID string - connectors map[string]*manager.ConnectorManager - installed *bool - apiVersion APIVersion - expectedStatusCode int - expectedErrorCode string - managerError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - taskID := uuid.New() - - testCases := []testCase{ - // V0 tests - { - name: "nominal V0", - apiVersion: V0, - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - }, - taskID: taskID.String(), - installed: ptr(true), - }, - { - name: "too many connectors for V0", - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - "1": nil, - }, - apiVersion: V0, - taskID: taskID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "no connector for V0", - connectors: map[string]*manager.ConnectorManager{}, - apiVersion: V0, - taskID: taskID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - // V1 tests - { - name: "nominal V1", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - installed: ptr(true), - }, - // Common test for V0 and V1 - { - name: "connector not installed", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(false), - }, - { - name: "manager error duplicate key value storage", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - installed: ptr(true), - }, - { - name: "manager error err not found storage", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error already installed", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error not installed", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error connector not found", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error err not found", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error err validation", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error other errors", - connectorID: connectorID.String(), - taskID: taskID.String(), - apiVersion: V1, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - installed: ptr(true), - }, - { - name: "invalid task ID", - apiVersion: V1, - connectorID: connectorID.String(), - taskID: "invalid", - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - installed: ptr(true), - }, - } - - for _, tc := range testCases { - testCase := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - readTaskResponse := &models.Task{ - ID: uuid.New(), - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - UpdatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Name: "test", - Descriptor: []byte("{}"), - Status: models.TaskStatusActive, - State: json.RawMessage("{}"), - } - - expectedReadTasksResponse := listTasksResponseElement{ - ID: readTaskResponse.ID.String(), - ConnectorID: readTaskResponse.ConnectorID.String(), - CreatedAt: readTaskResponse.CreatedAt.Format(time.RFC3339), - UpdatedAt: readTaskResponse.UpdatedAt.Format(time.RFC3339), - Descriptor: readTaskResponse.Descriptor, - Status: readTaskResponse.Status, - State: readTaskResponse.State, - Error: readTaskResponse.Error, - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - ReadTaskState(gomock.Any(), connectorID, taskID). - Return(readTaskResponse, nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - ReadTaskState(gomock.Any(), connectorID, taskID). - Return(nil, testCase.managerError) - } - - if testCase.apiVersion == V0 { - mockManager.EXPECT(). - Connectors(). - Return(testCase.connectors) - } - - if testCase.installed != nil { - mockManager.EXPECT(). - IsInstalled(gomock.Any(), connectorID). - Return(*testCase.installed, nil) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - var endpoint string - switch testCase.apiVersion { - case V0: - endpoint = fmt.Sprintf("/connectors/dummy-pay/tasks/%s", testCase.taskID) - case V1: - endpoint = fmt.Sprintf("/connectors/dummy-pay/%s/tasks/%s", testCase.connectorID, testCase.taskID) - } - req := httptest.NewRequest(http.MethodGet, endpoint, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[listTasksResponseElement] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, &expectedReadTasksResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestUninstall(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - connectorID string - connectors map[string]*manager.ConnectorManager - installed *bool - apiVersion APIVersion - expectedStatusCode int - expectedErrorCode string - managerError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - // V0 tests - { - name: "nominal V0", - apiVersion: V0, - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - }, - installed: ptr(true), - }, - { - name: "too many connectors for V0", - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - "1": nil, - }, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "no connector for V0", - connectors: map[string]*manager.ConnectorManager{}, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - // V1 tests - { - name: "nominal V1", - connectorID: connectorID.String(), - apiVersion: V1, - installed: ptr(true), - }, - // Common test for V0 and V1 - { - name: "connector not installed", - connectorID: connectorID.String(), - apiVersion: V1, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(false), - }, - { - name: "manager error duplicate key value storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - installed: ptr(true), - }, - { - name: "manager error err not found storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error already installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error not installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error connector not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error err not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error err validation", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error other errors", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - installed: ptr(true), - }, - } - - for _, tc := range testCases { - testCase := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - Uninstall(gomock.Any(), connectorID). - Return(nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - Uninstall(gomock.Any(), connectorID). - Return(testCase.managerError) - } - - if testCase.apiVersion == V0 { - mockManager.EXPECT(). - Connectors(). - Return(testCase.connectors) - } - - if testCase.installed != nil { - mockManager.EXPECT(). - IsInstalled(gomock.Any(), connectorID). - Return(*testCase.installed, nil) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - var endpoint string - switch testCase.apiVersion { - case V0: - endpoint = "/connectors/dummy-pay" - case V1: - endpoint = fmt.Sprintf("/connectors/dummy-pay/%s", testCase.connectorID) - } - req := httptest.NewRequest(http.MethodDelete, endpoint, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestInstall(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - body []byte - expectedStatusCode int - expectedErrorCode string - managerError error - } - - dummypayConfig := dummypay.Config{ - Name: "test", - Directory: "test", - FilePollingPeriod: connectors.Duration{ - Duration: 2 * time.Minute, - }, - } - - body, err := json.Marshal(dummypayConfig) - require.NoError(t, err) - - testCases := []testCase{ - { - name: "nominal", - body: body, - }, - { - name: "invalid body", - body: []byte("invalid"), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "manager error duplicate key value storage", - body: body, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "manager error err not found storage", - body: body, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "manager error already installed", - body: body, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "manager error not installed", - body: body, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "manager error connector not found", - body: body, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "manager error err not found", - body: body, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "manager error err validation", - body: body, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "manager error other errors", - body: body, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, tc := range testCases { - testCase := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusCreated - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - expectedResponse := installResponse{ - ConnectorID: connectorID.String(), - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - Install(gomock.Any(), dummypayConfig.Name, dummypayConfig). - Return(connectorID, nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - Install(gomock.Any(), dummypayConfig.Name, dummypayConfig). - Return(models.ConnectorID{}, testCase.managerError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - req := httptest.NewRequest(http.MethodPost, "/connectors/dummy-pay", bytes.NewReader(testCase.body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } else { - var resp sharedapi.BaseResponse[installResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, &expectedResponse, resp.Data) - } - - }) - } -} - -func TestReset(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - connectorID string - connectors map[string]*manager.ConnectorManager - installed *bool - apiVersion APIVersion - expectedStatusCode int - expectedErrorCode string - managerError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - testCases := []testCase{ - // V0 tests - { - name: "nominal V0", - apiVersion: V0, - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - }, - installed: ptr(true), - }, - { - name: "too many connectors for V0", - connectors: map[string]*manager.ConnectorManager{ - connectorID.String(): nil, - "1": nil, - }, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "no connector for V0", - connectors: map[string]*manager.ConnectorManager{}, - apiVersion: V0, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - // V1 tests - { - name: "nominal V1", - connectorID: connectorID.String(), - apiVersion: V1, - installed: ptr(true), - }, - // Common test for V0 and V1 - { - name: "connector not installed", - connectorID: connectorID.String(), - apiVersion: V1, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(false), - }, - { - name: "manager error duplicate key value storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - installed: ptr(true), - }, - { - name: "manager error err not found storage", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error already installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrAlreadyInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error not installed", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotInstalled, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error connector not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrConnectorNotFound, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error err not found", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - installed: ptr(true), - }, - { - name: "manager error err validation", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: manager.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - installed: ptr(true), - }, - { - name: "manager error other errors", - connectorID: connectorID.String(), - apiVersion: V1, - managerError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - installed: ptr(true), - }, - } - - for _, tc := range testCases { - testCase := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, _ := newServiceTestingBackend(t) - managerBackend, mockManager := newConnectorManagerTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockManager.EXPECT(). - Reset(gomock.Any(), connectorID). - Return(nil) - } - - if testCase.managerError != nil { - mockManager.EXPECT(). - Reset(gomock.Any(), connectorID). - Return(testCase.managerError) - } - - if testCase.apiVersion == V0 { - mockManager.EXPECT(). - Connectors(). - Return(testCase.connectors) - } - - if testCase.installed != nil { - mockManager.EXPECT(). - IsInstalled(gomock.Any(), connectorID). - Return(*testCase.installed, nil) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), []connectorHandler{ - { - Handler: connectorRouter[dummypay.Config](models.ConnectorProviderDummyPay, managerBackend), - Provider: models.ConnectorProviderDummyPay, - initiatePayment: func(ctx context.Context, transfer *models.TransferInitiation) error { - return nil - }, - }, - }, false) - - var endpoint string - switch testCase.apiVersion { - case V0: - endpoint = "/connectors/dummy-pay/reset" - case V1: - endpoint = fmt.Sprintf("/connectors/dummy-pay/%s/reset", testCase.connectorID) - } - req := httptest.NewRequest(http.MethodPost, endpoint, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/connectorconfigs.go b/components/payments/cmd/connectors/internal/api/connectorconfigs.go deleted file mode 100644 index 6088b97fc0..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectorconfigs.go +++ /dev/null @@ -1,49 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/adyen" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/dummypay" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise" -) - -func connectorConfigsHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // TODO: It's not ideal to re-identify available connectors - // Refactor it when refactoring the HTTP lib. - - configs := configtemplate.BuildConfigs( - atlar.Config{}, - adyen.Config{}, - bankingcircle.Config{}, - currencycloud.Config{}, - dummypay.Config{}, - modulr.Config{}, - stripe.Config{}, - wise.Config{}, - mangopay.Config{}, - moneycorp.Config{}, - generic.Config{}, - ) - - err := json.NewEncoder(w).Encode(api.BaseResponse[configtemplate.Configs]{ - Data: &configs, - }) - if err != nil { - api.InternalServerError(w, r, err) - return - } - } -} diff --git a/components/payments/cmd/connectors/internal/api/connectormodule.go b/components/payments/cmd/connectors/internal/api/connectormodule.go deleted file mode 100644 index 945226e00f..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectormodule.go +++ /dev/null @@ -1,105 +0,0 @@ -package api - -import ( - "context" - "net/http" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "github.com/pkg/errors" - "go.uber.org/dig" - "go.uber.org/fx" -) - -type connectorHandler struct { - Handler http.Handler - WebhookHandler http.Handler - Provider models.ConnectorProvider - - // TODO(polo): refactor to remove this ugly hack to access the connector manager - initiatePayment service.InitiatePaymentHandler - reversePayment service.ReversePaymentHandler - createExternalBankAccount service.BankAccountHandler -} - -func addConnector[ConnectorConfig models.ConnectorConfigObject](loader manager.Loader[ConnectorConfig], -) fx.Option { - return fx.Options( - fx.Provide(func(store *storage.Storage, - publisher message.Publisher, - metricsRegistry metrics.MetricsRegistry, - messages *messages.Messages, - ) *manager.ConnectorsManager[ConnectorConfig] { - schedulerFactory := manager.TaskSchedulerFactoryFn(func( - connectorID models.ConnectorID, resolver task.Resolver, maxTasks int, - ) *task.DefaultTaskScheduler { - return task.NewDefaultScheduler(connectorID, store, func(ctx context.Context, - descriptor models.TaskDescriptor, - taskID uuid.UUID, - ) (*dig.Container, error) { - container := dig.New() - - if err := container.Provide(func() ingestion.Ingester { - return ingestion.NewDefaultIngester(loader.Name(), connectorID, descriptor, store, publisher, messages) - }); err != nil { - return nil, err - } - - if err := container.Provide(func() storage.Reader { - return store - }); err != nil { - return nil, err - } - - return container, nil - }, resolver, metricsRegistry, maxTasks) - }) - - return manager.NewConnectorManager( - loader.Name(), store, loader, schedulerFactory, publisher, messages) - }), - fx.Provide(func(cm *manager.ConnectorsManager[ConnectorConfig]) backend.ManagerBackend[ConnectorConfig] { - return backend.NewDefaultManagerBackend[ConnectorConfig](cm) - }), - fx.Provide(fx.Annotate(func( - store *storage.Storage, - b backend.ManagerBackend[ConnectorConfig], - cm *manager.ConnectorsManager[ConnectorConfig], - ) connectorHandler { - return connectorHandler{ - Handler: connectorRouter(loader.Name(), b), - WebhookHandler: webhookConnectorRouter(loader.Name(), loader.Router(store), b), - Provider: loader.Name(), - initiatePayment: cm.InitiatePayment, - reversePayment: cm.ReversePayment, - createExternalBankAccount: cm.CreateExternalBankAccount, - } - }, fx.ResultTags(`group:"connectorHandlers"`))), - fx.Invoke(func(lc fx.Lifecycle, cm *manager.ConnectorsManager[ConnectorConfig]) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - ctx, span := otel.Tracer().Start(ctx, "connectorsManager.Restore") - defer span.End() - - err := cm.Restore(ctx) - if err != nil && !errors.Is(err, manager.ErrNotInstalled) { - return err - } - - return nil - }, - OnStop: cm.Close, - }) - }), - ) -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/connector_test.go b/components/payments/cmd/connectors/internal/api/connectors_manager/connector_test.go deleted file mode 100644 index e8aac712ea..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/connector_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package connectors_manager - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -type ConnectorBuilder struct { - name string - uninstall func(ctx context.Context) error - resolve func(descriptor models.TaskDescriptor) task.Task - install func(ctx task.ConnectorContext) error - initiatePayment func(ctx task.ConnectorContext, transfer *models.TransferInitiation) error - createExternalBankAccount func(ctx task.ConnectorContext, account *models.BankAccount) error -} - -func (b *ConnectorBuilder) WithUninstall( - uninstallFunction func(ctx context.Context) error, -) *ConnectorBuilder { - b.uninstall = uninstallFunction - - return b -} - -func (b *ConnectorBuilder) WithResolve(resolveFunction func(name models.TaskDescriptor) task.Task) *ConnectorBuilder { - b.resolve = resolveFunction - - return b -} - -func (b *ConnectorBuilder) WithInstall(installFunction func(ctx task.ConnectorContext) error) *ConnectorBuilder { - b.install = installFunction - - return b -} - -func (b *ConnectorBuilder) Build() connectors.Connector { - return &BuiltConnector{ - name: b.name, - uninstall: b.uninstall, - resolve: b.resolve, - install: b.install, - initiatePayment: b.initiatePayment, - createExternalBankAccount: b.createExternalBankAccount, - } -} - -func NewConnectorBuilder() *ConnectorBuilder { - return &ConnectorBuilder{} -} - -type BuiltConnector struct { - name string - uninstall func(ctx context.Context) error - resolve func(name models.TaskDescriptor) task.Task - updateConfig func(ctx task.ConnectorContext, config models.ConnectorConfigObject) error - install func(ctx task.ConnectorContext) error - initiatePayment func(ctx task.ConnectorContext, transfer *models.TransferInitiation) error - reversePayment func(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error - createExternalBankAccount func(ctx task.ConnectorContext, account *models.BankAccount) error -} - -func (b *BuiltConnector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - if b.updateConfig != nil { - return b.updateConfig(ctx, config) - } - - return nil -} - -func (b *BuiltConnector) SupportedCurrenciesAndDecimals() map[string]int { - return map[string]int{} -} - -func (b *BuiltConnector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - if b.initiatePayment != nil { - return b.initiatePayment(ctx, transfer) - } - - return nil -} - -func (b *BuiltConnector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - if b.reversePayment != nil { - return b.reversePayment(ctx, transferReversal) - } - - return nil -} - -func (b *BuiltConnector) CreateExternalBankAccount(ctx task.ConnectorContext, account *models.BankAccount) error { - if b.createExternalBankAccount != nil { - return b.createExternalBankAccount(ctx, account) - } - - return nil -} - -func (b *BuiltConnector) HandleWebhook(ctx task.ConnectorContext, webhook *models.Webhook) error { - return nil -} - -func (b *BuiltConnector) Name() string { - return b.name -} - -func (b *BuiltConnector) Install(ctx task.ConnectorContext) error { - if b.install != nil { - return b.install(ctx) - } - - return nil -} - -func (b *BuiltConnector) Uninstall(ctx context.Context) error { - if b.uninstall != nil { - return b.uninstall(ctx) - } - - return nil -} - -func (b *BuiltConnector) Resolve(name models.TaskDescriptor) task.Task { - if b.resolve != nil { - return b.resolve(name) - } - - return nil -} - -var _ connectors.Connector = &BuiltConnector{} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/errors.go b/components/payments/cmd/connectors/internal/api/connectors_manager/errors.go deleted file mode 100644 index 81263ec8ad..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/errors.go +++ /dev/null @@ -1,44 +0,0 @@ -package connectors_manager - -import ( - "errors" - "fmt" -) - -var ( - ErrNotFound = errors.New("not found") - ErrAlreadyInstalled = errors.New("already installed") - ErrNotInstalled = errors.New("not installed") - ErrNotEnabled = errors.New("not enabled") - ErrAlreadyRunning = errors.New("already running") - ErrConnectorNotFound = errors.New("connector not found") - ErrValidation = errors.New("validation error") -) - -type storageError struct { - err error - msg string -} - -func (e *storageError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.err) -} - -func (e *storageError) Is(err error) bool { - _, ok := err.(*storageError) - return ok -} - -func (e *storageError) Unwrap() error { - return e.err -} - -func newStorageError(err error, msg string) error { - if err == nil { - return nil - } - return &storageError{ - err: err, - msg: msg, - } -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/loader.go b/components/payments/cmd/connectors/internal/api/connectors_manager/loader.go deleted file mode 100644 index 38ffcee45e..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/loader.go +++ /dev/null @@ -1,107 +0,0 @@ -package connectors_manager - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader[ConnectorConfig models.ConnectorConfigObject] interface { - Name() models.ConnectorProvider - Load(logger logging.Logger, config ConnectorConfig) connectors.Connector - - // ApplyDefaults is used to fill default values of the provided configuration object - ApplyDefaults(t ConnectorConfig) ConnectorConfig - - // Extra routes to be added to the connectors manager API - Router(store *storage.Storage) *mux.Router - - // AllowTasks define how many task the connector can run - // If too many tasks are scheduled by the connector, - // those will be set to pending state and restarted later when some other tasks will be terminated - AllowTasks() int -} - -type LoaderBuilder[ConnectorConfig models.ConnectorConfigObject] struct { - loadFunction func(logger logging.Logger, config ConnectorConfig) connectors.Connector - applyDefaults func(t ConnectorConfig) ConnectorConfig - name models.ConnectorProvider - allowedTasks int -} - -func (b *LoaderBuilder[ConnectorConfig]) WithLoad(loadFunction func(logger logging.Logger, - config ConnectorConfig) connectors.Connector, -) *LoaderBuilder[ConnectorConfig] { - b.loadFunction = loadFunction - - return b -} - -func (b *LoaderBuilder[ConnectorConfig]) WithApplyDefaults( - applyDefaults func(t ConnectorConfig) ConnectorConfig, -) *LoaderBuilder[ConnectorConfig] { - b.applyDefaults = applyDefaults - - return b -} - -func (b *LoaderBuilder[ConnectorConfig]) WithAllowedTasks(v int) *LoaderBuilder[ConnectorConfig] { - b.allowedTasks = v - - return b -} - -func (b *LoaderBuilder[ConnectorConfig]) Build() *BuiltLoader[ConnectorConfig] { - return &BuiltLoader[ConnectorConfig]{ - loadFunction: b.loadFunction, - applyDefaults: b.applyDefaults, - name: b.name, - allowedTasks: b.allowedTasks, - } -} - -func NewLoaderBuilder[ConnectorConfig models.ConnectorConfigObject](name models.ConnectorProvider, -) *LoaderBuilder[ConnectorConfig] { - return &LoaderBuilder[ConnectorConfig]{ - name: name, - } -} - -type BuiltLoader[ConnectorConfig models.ConnectorConfigObject] struct { - loadFunction func(logger logging.Logger, config ConnectorConfig) connectors.Connector - applyDefaults func(t ConnectorConfig) ConnectorConfig - name models.ConnectorProvider - allowedTasks int -} - -func (b *BuiltLoader[ConnectorConfig]) AllowTasks() int { - return b.allowedTasks -} - -func (b *BuiltLoader[ConnectorConfig]) Name() models.ConnectorProvider { - return b.name -} - -func (b *BuiltLoader[ConnectorConfig]) Load(logger logging.Logger, config ConnectorConfig) connectors.Connector { - if b.loadFunction != nil { - return b.loadFunction(logger, config) - } - - return nil -} - -func (b *BuiltLoader[ConnectorConfig]) ApplyDefaults(t ConnectorConfig) ConnectorConfig { - if b.applyDefaults != nil { - return b.applyDefaults(t) - } - - return t -} - -func (b *BuiltLoader[ConnectorConfig]) Router(store *storage.Storage) *mux.Router { - return nil -} - -var _ Loader[models.EmptyConnectorConfig] = &BuiltLoader[models.EmptyConnectorConfig]{} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/manager.go b/components/payments/cmd/connectors/internal/api/connectors_manager/manager.go deleted file mode 100644 index f28e06c623..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/manager.go +++ /dev/null @@ -1,565 +0,0 @@ -package connectors_manager - -import ( - "context" - "fmt" - "sync" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -type ConnectorManager struct { - connector connectors.Connector - scheduler *task.DefaultTaskScheduler -} - -type ConnectorsManager[Config models.ConnectorConfigObject] struct { - provider models.ConnectorProvider - loader Loader[Config] - store Store - schedulerFactory TaskSchedulerFactory - publisher message.Publisher - messages *messages.Messages - - connectors map[string]*ConnectorManager - mu sync.RWMutex -} - -func (l *ConnectorsManager[ConnectorConfig]) logger(ctx context.Context) logging.Logger { - return logging.FromContext(ctx).WithFields(map[string]interface{}{ - "component": "connector-manager", - "provider": l.loader.Name(), - }) -} - -func (l *ConnectorsManager[ConnectorConfig]) getManager(connectorID models.ConnectorID) (*ConnectorManager, error) { - l.mu.RLock() - defer l.mu.RUnlock() - - connector, ok := l.connectors[connectorID.String()] - if !ok { - return nil, ErrNotInstalled - } - - return connector, nil -} - -func (l *ConnectorsManager[ConnectorConfig]) Connectors() map[string]*ConnectorManager { - l.mu.RLock() - defer l.mu.RUnlock() - - copy := make(map[string]*ConnectorManager, len(l.connectors)) - for k, v := range l.connectors { - copy[k] = v - } - - return copy -} - -func (l *ConnectorsManager[ConnectorConfig]) ReadConfig( - ctx context.Context, - connectorID models.ConnectorID, -) (ConnectorConfig, error) { - var config ConnectorConfig - connector, err := l.store.GetConnector(ctx, connectorID) - if err != nil { - return config, newStorageError(err, "getting connector") - } - - return l.readConfig(ctx, connector) -} - -func (l *ConnectorsManager[ConnectorConfig]) readConfig( - ctx context.Context, - connector *models.Connector, -) (ConnectorConfig, error) { - var config ConnectorConfig - if connector == nil { - var err error - connector, err = l.store.GetConnector(ctx, connector.ID) - if err != nil { - return config, newStorageError(err, "getting connector") - } - } - - err := connector.ParseConfig(&config) - if err != nil { - return config, err - } - - config = l.loader.ApplyDefaults(config) - - return config, nil -} - -func (l *ConnectorsManager[ConnectorConfig]) UpdateConfig( - ctx context.Context, - connectorID models.ConnectorID, - config ConnectorConfig, -) error { - l.logger(ctx).Infof("Updating config of connector: %s", connectorID) - - connectorManager, err := l.getManager(connectorID) - if err != nil { - l.logger(ctx).Errorf("Connector not installed") - return err - } - - config = l.loader.ApplyDefaults(config) - if err = config.Validate(); err != nil { - return err - } - - cfg, err := config.Marshal() - if err != nil { - return err - } - - if err := l.store.UpdateConfig(ctx, connectorID, cfg); err != nil { - return newStorageError(err, "updating connector config") - } - - // Detach the context since we're launching an async task and we're mostly - // coming from a HTTP request. - detachedCtx, span := detachedCtxWithSpan(ctx, trace.SpanFromContext(ctx), "connectorManager.UpdateConfig", connectorID) - defer span.End() - if err := connectorManager.connector.UpdateConfig(task.NewConnectorContext(logging.ContextWithLogger( - detachedCtx, - logging.FromContext(ctx), - ), connectorManager.scheduler), config); err != nil { - switch { - case errors.Is(err, connectors.ErrInvalidConfig): - return errors.Wrap(ErrValidation, err.Error()) - default: - return err - } - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) load( - ctx context.Context, - connectorID models.ConnectorID, - connectorConfig ConnectorConfig, -) error { - c := l.loader.Load(l.logger(ctx), connectorConfig) - scheduler := l.schedulerFactory.Make(connectorID, c, l.loader.AllowTasks()) - - l.mu.Lock() - l.connectors[connectorID.String()] = &ConnectorManager{ - connector: c, - scheduler: scheduler, - } - l.mu.Unlock() - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) Install( - ctx context.Context, - name string, - config ConnectorConfig, -) (models.ConnectorID, error) { - l.logger(ctx).WithFields(map[string]interface{}{ - "config": config, - }).Infof("Install connector %s", name) - - isInstalled, err := l.store.IsInstalledByConnectorName(ctx, name) - if err != nil { - return models.ConnectorID{}, newStorageError(err, "checking if connector is installed") - } - - if isInstalled { - l.logger(ctx).Errorf("Connector already installed") - return models.ConnectorID{}, ErrAlreadyInstalled - } - - config = l.loader.ApplyDefaults(config) - - if err = config.Validate(); err != nil { - return models.ConnectorID{}, err - } - - cfg, err := config.Marshal() - if err != nil { - return models.ConnectorID{}, err - } - - connector := &models.Connector{ - ID: models.ConnectorID{ - Provider: l.provider, - Reference: uuid.New(), - }, - Name: name, - Provider: l.provider, - } - - err = l.store.Install(ctx, connector, cfg) - if err != nil { - return models.ConnectorID{}, newStorageError(err, "installing connector") - } - - if err := l.load(ctx, connector.ID, config); err != nil { - return models.ConnectorID{}, err - } - - connectorManager, err := l.getManager(connector.ID) - if err != nil { - return models.ConnectorID{}, err - } - - // Detach the context since we're launching an async task and we're mostly - // coming from a HTTP request. - detachedCtx, span := detachedCtxWithSpan(ctx, trace.SpanFromContext(ctx), "connectorManager.Install", connector.ID) - defer span.End() - err = connectorManager.connector.Install(task.NewConnectorContext(logging.ContextWithLogger( - detachedCtx, - logging.FromContext(ctx), - ), connectorManager.scheduler)) - if err != nil { - l.logger(ctx).Errorf("Error starting connector: %s", err) - - return models.ConnectorID{}, err - } - - l.logger(ctx).Infof("Connector installed") - - return connector.ID, nil -} - -func (l *ConnectorsManager[ConnectorConfig]) Uninstall(ctx context.Context, connectorID models.ConnectorID) error { - l.logger(ctx).Infof("Uninstalling connector: %s", connectorID) - - connectorManager, err := l.getManager(connectorID) - if err != nil { - l.logger(ctx).Errorf("Connector not installed") - return err - } - - err = connectorManager.scheduler.Shutdown(ctx) - if err != nil { - return err - } - - err = connectorManager.connector.Uninstall(ctx) - if err != nil { - return err - } - - err = l.store.Uninstall(ctx, connectorID) - if err != nil { - return newStorageError(err, "uninstalling connector") - } - - if l.publisher != nil { - err = l.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, l.messages.NewEventResetConnector(connectorID))) - if err != nil { - l.logger(ctx).Errorf("Publishing message: %w", err) - } - } - - l.mu.Lock() - delete(l.connectors, connectorID.String()) - l.mu.Unlock() - - l.logger(ctx).Infof("Connector %s uninstalled", connectorID) - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) Restore(ctx context.Context) error { - l.logger(ctx).Info("Restoring state for all connectors") - - connectors, err := l.store.ListConnectors(ctx) - if err != nil { - return newStorageError(err, "listing connectors") - } - - for _, connector := range connectors { - if connector.Provider != l.provider { - continue - } - - if err := l.restore(ctx, connector); err != nil { - l.logger(ctx).Errorf("Unable to restore connector %s: %s", connector.Name, err) - return err - } - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) restore(ctx context.Context, connector *models.Connector) error { - l.logger(ctx).Infof("Restoring state for connector: %s", connector.Name) - - if manager, _ := l.getManager(connector.ID); manager != nil { - return ErrAlreadyRunning - } - - connectorConfig, err := l.readConfig(ctx, connector) - if err != nil { - return err - } - - if err := l.load(ctx, connector.ID, connectorConfig); err != nil { - return err - } - - manager, err := l.getManager(connector.ID) - if err != nil { - return err - } - - if err := manager.scheduler.Restore(ctx); err != nil { - return err - } - - l.logger(ctx).Infof("State restored for connector: %s", connector.Name) - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) FindAll(ctx context.Context) ([]*models.Connector, error) { - connectors, err := l.store.ListConnectors(ctx) - if err != nil { - return nil, newStorageError(err, "listing connectors") - } - - providerConnectors := make([]*models.Connector, 0, len(connectors)) - for _, connector := range connectors { - if connector.Provider == l.provider { - providerConnectors = append(providerConnectors, connector) - } - } - - return providerConnectors, nil -} - -func (l *ConnectorsManager[ConnectorConfig]) IsInstalled(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - isInstalled, err := l.store.IsInstalledByConnectorID(ctx, connectorID) - return isInstalled, newStorageError(err, "checking if connector is installed") -} - -func (l *ConnectorsManager[ConnectorConfig]) ListTasksStates( - ctx context.Context, - connectorID models.ConnectorID, - q storage.ListTasksQuery, -) (*bunpaginate.Cursor[models.Task], error) { - connectorManager, err := l.getManager(connectorID) - if err != nil { - return nil, ErrConnectorNotFound - } - - return connectorManager.scheduler.ListTasks(ctx, q) -} - -func (l *ConnectorsManager[Config]) ReadTaskState(ctx context.Context, connectorID models.ConnectorID, taskID uuid.UUID) (*models.Task, error) { - connectorManager, err := l.getManager(connectorID) - if err != nil { - return nil, ErrConnectorNotFound - } - - return connectorManager.scheduler.ReadTask(ctx, taskID) -} - -func (l *ConnectorsManager[ConnectorConfig]) Reset(ctx context.Context, connectorID models.ConnectorID) error { - connector, err := l.store.GetConnector(ctx, connectorID) - if err != nil { - return newStorageError(err, "getting connector") - } - - config, err := l.readConfig(ctx, connector) - if err != nil { - return err - } - - err = l.Uninstall(ctx, connectorID) - if err != nil { - return err - } - - _, err = l.Install(ctx, connector.Name, config) - if err != nil { - return err - } - - err = l.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, l.messages.NewEventResetConnector(connectorID))) - if err != nil { - l.logger(ctx).Errorf("Publishing message: %w", err) - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) InitiatePayment(ctx context.Context, transfer *models.TransferInitiation) error { - connectorManager, err := l.getManager(transfer.ConnectorID) - if err != nil { - return ErrConnectorNotFound - } - - if err := l.validateAssets(ctx, connectorManager, transfer.ConnectorID, transfer.Asset); err != nil { - return err - } - - detachedCtx, span := detachedCtxWithSpan(ctx, trace.SpanFromContext(ctx), "connectorManager.InitiatePayment", transfer.ConnectorID) - defer span.End() - err = connectorManager.connector.InitiatePayment(task.NewConnectorContext(detachedCtx, connectorManager.scheduler), transfer) - if err != nil { - return fmt.Errorf("initiating transfer: %w", err) - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) ReversePayment(ctx context.Context, transferReversal *models.TransferReversal) error { - connectorManager, err := l.getManager(transferReversal.ConnectorID) - if err != nil { - return ErrConnectorNotFound - } - - if err := l.validateAssets(ctx, connectorManager, transferReversal.ConnectorID, transferReversal.Asset); err != nil { - return err - } - - err = connectorManager.connector.ReversePayment(task.NewConnectorContext(ctx, connectorManager.scheduler), transferReversal) - if err != nil { - return fmt.Errorf("reversing transfer: %w", err) - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) CreateExternalBankAccount(ctx context.Context, connectorID models.ConnectorID, bankAccount *models.BankAccount) error { - connectorManager, err := l.getManager(connectorID) - if err != nil { - return ErrConnectorNotFound - } - - detachedCtx, span := detachedCtxWithSpan(ctx, trace.SpanFromContext(ctx), "connectorManager.CreateExternalBankAccount", connectorID) - defer span.End() - err = connectorManager.connector.CreateExternalBankAccount(task.NewConnectorContext(detachedCtx, connectorManager.scheduler), bankAccount) - if err != nil { - switch { - case errors.Is(err, connectors.ErrNotImplemented): - return errors.Wrap(ErrValidation, "bank account creation not implemented for this connector") - default: - return fmt.Errorf("creating bank account: %w", err) - } - } - - return nil -} - -func (l *ConnectorsManager[ConnectorConfig]) CreateWebhookAndContext( - ctx context.Context, - webhook *models.Webhook, -) (context.Context, error) { - connectorManager, err := l.getManager(webhook.ConnectorID) - if err != nil { - return nil, ErrConnectorNotFound - } - - if err := l.store.CreateWebhook(ctx, webhook); err != nil { - return nil, newStorageError(err, "creating webhook") - } - - connectorContext := task.NewConnectorContext(ctx, connectorManager.scheduler) - ctx = task.ContextWithConnectorContext(connectors.ContextWithWebhookID(ctx, webhook.ID), connectorContext) - - return ctx, nil -} - -func (l *ConnectorsManager[ConnectorConfig]) validateAssets( - ctx context.Context, - connectorManager *ConnectorManager, - connectorID models.ConnectorID, - asset models.Asset, -) error { - supportedCurrencies := connectorManager.connector.SupportedCurrenciesAndDecimals() - currency, precision, err := models.GetCurrencyAndPrecisionFromAsset(asset) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - supportedPrecision, ok := supportedCurrencies[currency] - if !ok { - return errors.Wrap(ErrValidation, fmt.Sprintf("currency %s not supported", currency)) - } - - if precision != int64(supportedPrecision) { - return errors.Wrap(ErrValidation, fmt.Sprintf("currency %s has precision %d, but %d is required", currency, precision, supportedPrecision)) - } - - return nil -} - -func detachedCtxWithSpan( - ctx context.Context, - parentSpan trace.Span, - spanName string, - connectorID models.ConnectorID, -) (context.Context, trace.Span) { - detachedCtx, _ := contextutil.Detached(ctx) - - ctx, span := otel.Tracer().Start( - detachedCtx, - spanName, - trace.WithLinks(trace.Link{ - SpanContext: parentSpan.SpanContext(), - }), - trace.WithAttributes( - attribute.String("connectorID", connectorID.String()), - ), - ) - - return ctx, span -} - -func (l *ConnectorsManager[ConnectorConfig]) Close(ctx context.Context) error { - for _, connectorManager := range l.connectors { - err := connectorManager.scheduler.Shutdown(ctx) - if err != nil { - return err - } - } - - return nil -} - -func NewConnectorManager[ConnectorConfig models.ConnectorConfigObject]( - provider models.ConnectorProvider, - store Store, - loader Loader[ConnectorConfig], - schedulerFactory TaskSchedulerFactory, - publisher message.Publisher, - messages *messages.Messages, -) *ConnectorsManager[ConnectorConfig] { - return &ConnectorsManager[ConnectorConfig]{ - provider: provider, - connectors: make(map[string]*ConnectorManager), - store: store, - loader: loader, - schedulerFactory: schedulerFactory, - publisher: publisher, - messages: messages, - } -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/manager_test.go b/components/payments/cmd/connectors/internal/api/connectors_manager/manager_test.go deleted file mode 100644 index 6a7f5eaa45..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/manager_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package connectors_manager - -import ( - "context" - "testing" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "go.uber.org/dig" -) - -func ChanClosed[T any](ch chan T) bool { - select { - case <-ch: - return true - default: - return false - } -} - -type testContext[ConnectorConfig models.ConnectorConfigObject] struct { - manager *ConnectorsManager[ConnectorConfig] - taskStore task.Repository - connectorStore Store - loader Loader[ConnectorConfig] - provider models.ConnectorProvider -} - -func withManager[ConnectorConfig models.ConnectorConfigObject](builder *ConnectorBuilder, - callback func(ctx *testContext[ConnectorConfig]), -) { - l := logrus.New() - if testing.Verbose() { - l.SetLevel(logrus.DebugLevel) - } - - DefaultContainerFactory := task.ContainerCreateFunc(func(ctx context.Context, descriptor models.TaskDescriptor, taskID uuid.UUID) (*dig.Container, error) { - return dig.New(), nil - }) - - taskStore := task.NewInMemoryStore() - managerStore := NewInMemoryStore() - provider := models.ConnectorProvider(uuid.New().String()) - schedulerFactory := TaskSchedulerFactoryFn(func( - connectorID models.ConnectorID, - resolver task.Resolver, - maxTasks int, - ) *task.DefaultTaskScheduler { - return task.NewDefaultScheduler(connectorID, taskStore, - DefaultContainerFactory, resolver, metrics.NewNoOpMetricsRegistry(), maxTasks) - }) - - loader := NewLoaderBuilder[ConnectorConfig](provider). - WithLoad(func(logger logging.Logger, config ConnectorConfig) connectors.Connector { - return builder.Build() - }). - WithAllowedTasks(1). - Build() - manager := NewConnectorManager[ConnectorConfig](provider, managerStore, loader, schedulerFactory, nil, messages.NewMessages("")) - - callback(&testContext[ConnectorConfig]{ - manager: manager, - taskStore: taskStore, - connectorStore: managerStore, - loader: loader, - provider: provider, - }) -} - -func TestInstallConnector(t *testing.T) { - t.Parallel() - - installed := make(chan struct{}) - builder := NewConnectorBuilder(). - WithInstall(func(ctx task.ConnectorContext) error { - close(installed) - - return nil - }) - withManager(builder, func(tc *testContext[models.EmptyConnectorConfig]) { - _, err := tc.manager.Install(context.TODO(), "test1", models.EmptyConnectorConfig{ - Name: "test1", - }) - require.NoError(t, err) - require.True(t, ChanClosed(installed)) - - _, err = tc.manager.Install(context.TODO(), "test1", models.EmptyConnectorConfig{ - Name: "test1", - }) - require.Equal(t, ErrAlreadyInstalled, err) - - connectors, err := tc.manager.FindAll(context.TODO()) - require.NoError(t, err) - require.Len(t, connectors, 1) - require.Equal(t, "test1", connectors[0].Name) - - isInstalled, err := tc.manager.IsInstalled(context.TODO(), connectors[0].ID) - require.NoError(t, err) - require.True(t, isInstalled) - - err = tc.manager.Uninstall(context.TODO(), connectors[0].ID) - require.NoError(t, err) - - isInstalled, err = tc.manager.IsInstalled(context.TODO(), connectors[0].ID) - require.NoError(t, err) - require.False(t, isInstalled) - }) -} - -func TestUninstallConnector(t *testing.T) { - t.Parallel() - - uninstalled := make(chan struct{}) - taskTerminated := make(chan struct{}) - taskStarted := make(chan struct{}) - builder := NewConnectorBuilder(). - WithResolve(func(name models.TaskDescriptor) task.Task { - return func(ctx context.Context, stopChan task.StopChan) { - close(taskStarted) - defer close(taskTerminated) - select { - case flag := <-stopChan: - flag <- struct{}{} - case <-ctx.Done(): - } - } - }). - WithInstall(func(ctx task.ConnectorContext) error { - return ctx.Scheduler().Schedule(ctx.Context(), []byte(uuid.New().String()), models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - }). - WithUninstall(func(ctx context.Context) error { - close(uninstalled) - - return nil - }) - withManager(builder, func(tc *testContext[models.EmptyConnectorConfig]) { - _, err := tc.manager.Install(context.TODO(), "test1", models.EmptyConnectorConfig{ - Name: "test1", - }) - require.NoError(t, err) - <-taskStarted - - connectors, err := tc.manager.FindAll(context.TODO()) - require.NoError(t, err) - require.Len(t, connectors, 1) - require.Equal(t, "test1", connectors[0].Name) - - require.NoError(t, tc.manager.Uninstall(context.TODO(), connectors[0].ID)) - require.True(t, ChanClosed(uninstalled)) - // TODO: We need to give a chance to the connector to properly stop execution - require.True(t, ChanClosed(taskTerminated)) - - isInstalled, err := tc.manager.IsInstalled(context.TODO(), connectors[0].ID) - require.NoError(t, err) - require.False(t, isInstalled) - }) -} - -func TestRestoreConnector(t *testing.T) { - t.Parallel() - - builder := NewConnectorBuilder() - withManager(builder, func(tc *testContext[models.EmptyConnectorConfig]) { - cfg, err := models.EmptyConnectorConfig{ - Name: "test1", - }.Marshal() - require.NoError(t, err) - - connector := &models.Connector{ - ID: models.ConnectorID{ - Provider: tc.provider, - Reference: uuid.New(), - }, - Name: "test1", - Provider: tc.provider, - } - - err = tc.connectorStore.Install(context.TODO(), connector, cfg) - require.NoError(t, err) - - err = tc.manager.Restore(context.TODO()) - require.NoError(t, err) - require.Len(t, tc.manager.Connectors(), 1) - - require.NoError(t, tc.manager.Uninstall(context.TODO(), connector.ID)) - }) -} - -func TestRestoreNotInstalledConnector(t *testing.T) { - t.Parallel() - - builder := NewConnectorBuilder() - withManager(builder, func(tc *testContext[models.EmptyConnectorConfig]) { - err := tc.manager.Restore(context.TODO()) - require.NoError(t, err) - require.Len(t, tc.manager.Connectors(), 0) - }) -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/store.go b/components/payments/cmd/connectors/internal/api/connectors_manager/store.go deleted file mode 100644 index e1cb8570e9..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/store.go +++ /dev/null @@ -1,19 +0,0 @@ -package connectors_manager - -import ( - "context" - "encoding/json" - - "github.com/formancehq/payments/internal/models" -) - -type Store interface { - ListConnectors(ctx context.Context) ([]*models.Connector, error) - IsInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) - IsInstalledByConnectorName(ctx context.Context, name string) (bool, error) - Install(ctx context.Context, connector *models.Connector, config json.RawMessage) error - Uninstall(ctx context.Context, connectorID models.ConnectorID) error - UpdateConfig(ctx context.Context, connectorID models.ConnectorID, config json.RawMessage) error - GetConnector(ctx context.Context, connectorID models.ConnectorID) (*models.Connector, error) - CreateWebhook(ctx context.Context, webhook *models.Webhook) error -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/storememory_test.go b/components/payments/cmd/connectors/internal/api/connectors_manager/storememory_test.go deleted file mode 100644 index 6da72f4e41..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/storememory_test.go +++ /dev/null @@ -1,137 +0,0 @@ -package connectors_manager - -import ( - "context" - "encoding/json" - "sync" - - "github.com/formancehq/payments/internal/models" -) - -type connector struct { - name string - id models.ConnectorID - config json.RawMessage - provider models.ConnectorProvider -} - -type InMemoryConnectorStore struct { - connectorsByID map[string]*connector - connectorsByName map[string]*connector - mu sync.RWMutex -} - -func (i *InMemoryConnectorStore) Uninstall(ctx context.Context, connectorID models.ConnectorID) error { - i.mu.Lock() - defer i.mu.Unlock() - - connector, ok := i.connectorsByID[connectorID.String()] - if !ok { - return nil - } - - delete(i.connectorsByID, connectorID.String()) - delete(i.connectorsByName, connector.name) - - return nil -} - -func (i *InMemoryConnectorStore) ListConnectors(_ context.Context) ([]*models.Connector, error) { - i.mu.RLock() - defer i.mu.RUnlock() - - connectors := make([]*models.Connector, 0, len(i.connectorsByID)) - for _, c := range i.connectorsByID { - connectors = append(connectors, &models.Connector{ - ID: c.id, - Name: c.name, - Config: c.config, - Provider: c.provider, - }) - } - return connectors, nil -} - -func (i *InMemoryConnectorStore) IsInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - i.mu.RLock() - defer i.mu.RUnlock() - - _, ok := i.connectorsByID[connectorID.String()] - return ok, nil -} - -func (i *InMemoryConnectorStore) IsInstalledByConnectorName(ctx context.Context, name string) (bool, error) { - i.mu.RLock() - defer i.mu.RUnlock() - - _, ok := i.connectorsByName[name] - return ok, nil -} - -func (i *InMemoryConnectorStore) Install(ctx context.Context, newConnector *models.Connector, config json.RawMessage) error { - i.mu.Lock() - defer i.mu.Unlock() - - c := &connector{ - name: newConnector.Name, - id: newConnector.ID, - config: config, - provider: newConnector.Provider, - } - - i.connectorsByID[newConnector.ID.String()] = c - i.connectorsByName[newConnector.Name] = c - - return nil -} - -func (i *InMemoryConnectorStore) UpdateConfig(ctx context.Context, connectorID models.ConnectorID, config json.RawMessage) error { - i.mu.Lock() - defer i.mu.Unlock() - - i.connectorsByID[connectorID.String()].config = config - return nil -} - -func (i *InMemoryConnectorStore) GetConnector(ctx context.Context, connectorID models.ConnectorID) (*models.Connector, error) { - i.mu.RLock() - defer i.mu.RUnlock() - - c, ok := i.connectorsByID[connectorID.String()] - if !ok { - return nil, ErrNotFound - } - - return &models.Connector{ - ID: c.id, - Name: c.name, - Config: c.config, - Provider: c.provider, - }, nil -} - -func (i *InMemoryConnectorStore) ReadConfig(ctx context.Context, connectorID models.ConnectorID, to interface{}) error { - connector, err := i.GetConnector(ctx, connectorID) - if err != nil { - return err - } - - if err = connector.ParseConfig(to); err != nil { - return err - } - - return nil -} - -func (i *InMemoryConnectorStore) CreateWebhook(ctx context.Context, webhook *models.Webhook) error { - return nil -} - -var _ Store = &InMemoryConnectorStore{} - -func NewInMemoryStore() *InMemoryConnectorStore { - return &InMemoryConnectorStore{ - connectorsByID: make(map[string]*connector), - connectorsByName: make(map[string]*connector), - } -} diff --git a/components/payments/cmd/connectors/internal/api/connectors_manager/taskscheduler.go b/components/payments/cmd/connectors/internal/api/connectors_manager/taskscheduler.go deleted file mode 100644 index 86d54ce98a..0000000000 --- a/components/payments/cmd/connectors/internal/api/connectors_manager/taskscheduler.go +++ /dev/null @@ -1,16 +0,0 @@ -package connectors_manager - -import ( - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -type TaskSchedulerFactory interface { - Make(connectorID models.ConnectorID, resolver task.Resolver, maxTasks int) *task.DefaultTaskScheduler -} - -type TaskSchedulerFactoryFn func(connectorID models.ConnectorID, resolver task.Resolver, maxProcesses int) *task.DefaultTaskScheduler - -func (fn TaskSchedulerFactoryFn) Make(connectorID models.ConnectorID, resolver task.Resolver, maxTasks int) *task.DefaultTaskScheduler { - return fn(connectorID, resolver, maxTasks) -} diff --git a/components/payments/cmd/connectors/internal/api/health.go b/components/payments/cmd/connectors/internal/api/health.go deleted file mode 100644 index 9ed5c10471..0000000000 --- a/components/payments/cmd/connectors/internal/api/health.go +++ /dev/null @@ -1,25 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" -) - -func healthHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := b.GetService().Ping(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - - return - } - - w.WriteHeader(http.StatusOK) - } -} - -func liveHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - } -} diff --git a/components/payments/cmd/connectors/internal/api/module.go b/components/payments/cmd/connectors/internal/api/module.go deleted file mode 100644 index 7a1d4d1d1a..0000000000 --- a/components/payments/cmd/connectors/internal/api/module.go +++ /dev/null @@ -1,166 +0,0 @@ -package api - -import ( - "context" - "errors" - "net/http" - "runtime/debug" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/otlp" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/adyen" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/dummypay" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" - "github.com/rs/cors" - "github.com/sirupsen/logrus" - "go.uber.org/fx" -) - -const ( - serviceName = "Payments" - - ErrUniqueReference = "CONFLICT" - ErrNotFound = "NOT_FOUND" - ErrInvalidID = "INVALID_ID" - ErrMissingOrInvalidBody = "MISSING_OR_INVALID_BODY" - ErrValidation = "VALIDATION" -) - -func HTTPModule(serviceInfo api.ServiceInfo, bind, stackURL string, otelTraces bool) fx.Option { - return fx.Options( - fx.Invoke(func(m *mux.Router, lc fx.Lifecycle) { - lc.Append(httpserver.NewHook(m, httpserver.WithAddress(bind))) - }), - fx.Supply(serviceInfo), - fx.Provide(fx.Annotate(connectorsHandlerMap, fx.ParamTags(`group:"connectorHandlers"`))), - fx.Provide(func(store *storage.Storage) service.Store { - return store - }), - fx.Provide(fx.Annotate(service.New, fx.As(new(backend.Service)))), - fx.Provide(backend.NewDefaultBackend), - fx.Provide(fx.Annotate(func( - logger logging.Logger, - b backend.ServiceBackend, - serviceInfo api.ServiceInfo, - a auth.Authenticator, - connectorHandlers []connectorHandler, - ) *mux.Router { - return httpRouter(logger, b, serviceInfo, a, connectorHandlers, otelTraces) - }, fx.ParamTags(``, ``, ``, ``, `group:"connectorHandlers"`))), - fx.Provide(func() *messages.Messages { - return messages.NewMessages(stackURL) - }), - addConnector[dummypay.Config](dummypay.NewLoader()), - addConnector[modulr.Config](modulr.NewLoader()), - addConnector[stripe.Config](stripe.NewLoader()), - addConnector[wise.Config](wise.NewLoader()), - addConnector[currencycloud.Config](currencycloud.NewLoader()), - addConnector[bankingcircle.Config](bankingcircle.NewLoader()), - addConnector[mangopay.Config](mangopay.NewLoader()), - addConnector[moneycorp.Config](moneycorp.NewLoader()), - addConnector[atlar.Config](atlar.NewLoader()), - addConnector[adyen.Config](adyen.NewLoader()), - addConnector[generic.Config](generic.NewLoader()), - ) -} - -func connectorsHandlerMap(connectorHandlers []connectorHandler) map[models.ConnectorProvider]*service.ConnectorHandlers { - m := make(map[models.ConnectorProvider]*service.ConnectorHandlers) - for _, h := range connectorHandlers { - if handlers, ok := m[h.Provider]; ok { - handlers.InitiatePaymentHandler = h.initiatePayment - handlers.ReversePaymentHandler = h.reversePayment - handlers.BankAccountHandler = h.createExternalBankAccount - } else { - m[h.Provider] = &service.ConnectorHandlers{ - InitiatePaymentHandler: h.initiatePayment, - ReversePaymentHandler: h.reversePayment, - BankAccountHandler: h.createExternalBankAccount, - } - } - } - return m -} - -func httpRecoveryFunc(otelTraces bool) func(context.Context, interface{}) { - return func(ctx context.Context, e interface{}) { - if otelTraces { - otlp.RecordAsError(ctx, e) - } else { - logrus.Errorln(e) - debug.PrintStack() - } - } -} - -func httpCorsHandler() func(http.Handler) http.Handler { - return cors.New(cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut}, - AllowCredentials: true, - }).Handler -} - -func httpServeFunc(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - handler.ServeHTTP(w, r) - }) -} - -func handleServiceErrors(w http.ResponseWriter, r *http.Request, err error) { - switch { - case errors.Is(err, storage.ErrDuplicateKeyValue): - api.BadRequest(w, ErrUniqueReference, err) - case errors.Is(err, storage.ErrNotFound): - api.NotFound(w, err) - case errors.Is(err, service.ErrValidation): - api.BadRequest(w, ErrValidation, err) - case errors.Is(err, service.ErrInvalidID): - api.BadRequest(w, ErrInvalidID, err) - case errors.Is(err, service.ErrPublish): - api.InternalServerError(w, r, err) - default: - api.InternalServerError(w, r, err) - } -} - -func handleConnectorsManagerErrors(w http.ResponseWriter, r *http.Request, err error) { - switch { - case errors.Is(err, storage.ErrDuplicateKeyValue): - api.BadRequest(w, ErrUniqueReference, err) - case errors.Is(err, storage.ErrNotFound): - api.NotFound(w, err) - case errors.Is(err, manager.ErrAlreadyInstalled): - api.BadRequest(w, ErrValidation, err) - case errors.Is(err, manager.ErrNotInstalled): - api.BadRequest(w, ErrValidation, err) - case errors.Is(err, manager.ErrConnectorNotFound): - api.BadRequest(w, ErrValidation, err) - case errors.Is(err, manager.ErrNotFound): - api.NotFound(w, err) - case errors.Is(err, manager.ErrValidation): - api.BadRequest(w, ErrValidation, err) - default: - api.InternalServerError(w, r, err) - } -} diff --git a/components/payments/cmd/connectors/internal/api/read_connectors.go b/components/payments/cmd/connectors/internal/api/read_connectors.go deleted file mode 100644 index cf788a3000..0000000000 --- a/components/payments/cmd/connectors/internal/api/read_connectors.go +++ /dev/null @@ -1,56 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type readConnectorsResponseElement struct { - Provider models.ConnectorProvider `json:"provider" bson:"provider"` - ConnectorID string `json:"connectorID" bson:"connectorID"` - Name string `json:"name" bson:"name"` - Enabled bool `json:"enabled" bson:"enabled"` -} - -func readConnectorsHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "readConnectorsHandler") - defer span.End() - - res, err := b.GetService().ListConnectors(ctx) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - span.SetAttributes(attribute.Int("count", len(res))) - - data := make([]readConnectorsResponseElement, len(res)) - - for i := range res { - data[i] = readConnectorsResponseElement{ - Provider: res[i].Provider, - ConnectorID: res[i].ID.String(), - Name: res[i].Name, - Enabled: true, - } - } - - err = json.NewEncoder(w).Encode( - api.BaseResponse[[]readConnectorsResponseElement]{ - Data: &data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} diff --git a/components/payments/cmd/connectors/internal/api/read_connectors_test.go b/components/payments/cmd/connectors/internal/api/read_connectors_test.go deleted file mode 100644 index 0648c6970d..0000000000 --- a/components/payments/cmd/connectors/internal/api/read_connectors_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package api - -import ( - "net/http" - "net/http/httptest" - "testing" - "time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestReadConnectors(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - testCases := []testCase{ - { - name: "nominal", - }, - { - name: "service error duplicate key", - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - serviceError: service.ErrInvalidID, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - serviceError: service.ErrPublish, - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error unknown", - serviceError: errors.New("unknown"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - listConnectorsResponse := []*models.Connector{ - { - ID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - Name: "c1", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Provider: models.ConnectorProviderDummyPay, - }, - } - - expectedListConnectorsResponse := []readConnectorsResponseElement{ - { - Provider: listConnectorsResponse[0].Provider, - ConnectorID: listConnectorsResponse[0].ID.String(), - Name: "c1", - Enabled: true, - }, - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - ListConnectors(gomock.Any()). - Return(listConnectorsResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - ListConnectors(gomock.Any()). - Return(nil, testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), nil, false) - - req := httptest.NewRequest(http.MethodGet, "/connectors", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[[]readConnectorsResponseElement] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, &expectedListConnectorsResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/recovery.go b/components/payments/cmd/connectors/internal/api/recovery.go deleted file mode 100644 index fce3087e1c..0000000000 --- a/components/payments/cmd/connectors/internal/api/recovery.go +++ /dev/null @@ -1,20 +0,0 @@ -package api - -import ( - "context" - "net/http" -) - -func recoveryHandler(reporter func(ctx context.Context, e interface{})) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if e := recover(); e != nil { - w.WriteHeader(http.StatusInternalServerError) - reporter(r.Context(), e) - } - }() - h.ServeHTTP(w, r) - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/router.go b/components/payments/cmd/connectors/internal/api/router.go deleted file mode 100644 index f367ff656c..0000000000 --- a/components/payments/cmd/connectors/internal/api/router.go +++ /dev/null @@ -1,157 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" - "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" -) - -func httpRouter( - logger logging.Logger, - b backend.ServiceBackend, - serviceInfo api.ServiceInfo, - a auth.Authenticator, - connectorHandlers []connectorHandler, - otelTraces bool, -) *mux.Router { - rootMux := mux.NewRouter() - - // We have to keep this recovery handler here to ensure that the health - // endpoint is not panicking - rootMux.Use(recoveryHandler(httpRecoveryFunc(otelTraces))) - rootMux.Use(httpCorsHandler()) - rootMux.Use(httpServeFunc) - rootMux.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r.WithContext(logging.ContextWithLogger(r.Context(), logger))) - }) - }) - - rootMux.Path("/_health").Handler(healthHandler(b)) - - subRouter := rootMux.NewRoute().Subrouter() - if otelTraces { - subRouter.Use(otelmux.Middleware(serviceName)) - // Add a second recovery handler to ensure that the otel middleware - // is catching the error in the trace - rootMux.Use(recoveryHandler(httpRecoveryFunc(otelTraces))) - } - subRouter.Path("/_live").Handler(liveHandler()) - subRouter.Path("/_info").Handler(api.InfoHandler(serviceInfo)) - - authGroup := subRouter.Name("authenticated").Subrouter() - authGroup.Use(auth.Middleware(a)) - - authGroup.Path("/bank-accounts").Methods(http.MethodPost).Handler(createBankAccountHandler(b)) - authGroup.Path("/bank-accounts/{bankAccountID}/forward").Methods(http.MethodPost).Handler(forwardBankAccountToConnector(b)) - authGroup.Path("/bank-accounts/{bankAccountID}/metadata").Methods(http.MethodPatch).Handler(updateBankAccountMetadataHandler(b)) - - authGroup.Path("/transfer-initiations").Methods(http.MethodPost).Handler(createTransferInitiationHandler(b)) - authGroup.Path("/transfer-initiations/{transferID}/status").Methods(http.MethodPost).Handler(updateTransferInitiationStatusHandler(b)) - authGroup.Path("/transfer-initiations/{transferID}/retry").Methods(http.MethodPost).Handler(retryTransferInitiationHandler(b)) - authGroup.Path("/transfer-initiations/{transferID}/reverse").Methods(http.MethodPost).Handler(reverseTransferInitiationHandler(b)) - authGroup.Path("/transfer-initiations/{transferID}").Methods(http.MethodDelete).Handler(deleteTransferInitiationHandler(b)) - - authGroup.HandleFunc("/connectors", readConnectorsHandler(b)) - - connectorGroupAuthenticated := authGroup.PathPrefix("/connectors").Subrouter() - connectorGroupAuthenticated.Path("/configs").Handler(connectorConfigsHandler()) - - // Needed for webhooks - connectorGroupUnauthenticated := subRouter.PathPrefix("/connectors").Subrouter() - - for _, h := range connectorHandlers { - connectorGroupAuthenticated.PathPrefix("/" + h.Provider.String()).Handler( - http.StripPrefix("/connectors", h.Handler)) - - connectorGroupAuthenticated.PathPrefix("/" + h.Provider.StringLower()).Handler( - http.StripPrefix("/connectors", h.Handler)) - - if h.WebhookHandler != nil { - connectorGroupUnauthenticated.PathPrefix("/webhooks/" + h.Provider.String()).Handler( - http.StripPrefix("/connectors", h.WebhookHandler)) - - connectorGroupUnauthenticated.PathPrefix("/webhooks/" + h.Provider.StringLower()).Handler( - http.StripPrefix("/connectors", h.WebhookHandler)) - } - } - - return rootMux -} - -func connectorRouter[Config models.ConnectorConfigObject]( - provider models.ConnectorProvider, - b backend.ManagerBackend[Config], -) *mux.Router { - r := mux.NewRouter() - - addRoute(r, provider, "", http.MethodPost, install(b)) - addRoute(r, provider, "/{connectorID}", http.MethodDelete, uninstall(b, V1)) - addRoute(r, provider, "/{connectorID}/config", http.MethodGet, readConfig(b, V1)) - addRoute(r, provider, "/{connectorID}/config", http.MethodPost, updateConfig(b, V1)) - addRoute(r, provider, "/{connectorID}/reset", http.MethodPost, reset(b, V1)) - addRoute(r, provider, "/{connectorID}/tasks", http.MethodGet, listTasks(b, V1)) - addRoute(r, provider, "/{connectorID}/tasks/{taskID}", http.MethodGet, readTask(b, V1)) - - // Deprecated routes - addRoute(r, provider, "", http.MethodDelete, uninstall(b, V0)) - addRoute(r, provider, "/config", http.MethodGet, readConfig(b, V0)) - addRoute(r, provider, "/reset", http.MethodPost, reset(b, V0)) - addRoute(r, provider, "/tasks", http.MethodGet, listTasks(b, V0)) - addRoute(r, provider, "/tasks/{taskID}", http.MethodGet, readTask(b, V0)) - - return r -} - -func webhookConnectorRouter[Config models.ConnectorConfigObject]( - provider models.ConnectorProvider, - connectorRouter *mux.Router, - b backend.ManagerBackend[Config], -) *mux.Router { - if connectorRouter == nil { - return nil - } - - r := mux.NewRouter() - - group := r.PathPrefix("/webhooks/" + provider.String() + "/{connectorID}").Subrouter() - group.Use(webhooksMiddleware(b, V1)) - addWebhookRoute(group, connectorRouter) - - groupLower := r.PathPrefix("/webhooks/" + provider.StringLower() + "/{connectorID}").Subrouter() - groupLower.Use(webhooksMiddleware(b, V1)) - addWebhookRoute(groupLower, connectorRouter) - - return r -} - -func addRoute(r *mux.Router, provider models.ConnectorProvider, path, method string, handler http.Handler) { - r.Path("/" + provider.String() + path).Methods(method).Handler(handler) - r.Path("/" + provider.StringLower() + path).Methods(method).Handler(handler) -} - -func addWebhookRoute(r *mux.Router, subRouter *mux.Router) { - subRouter.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { - pathTemplate, err := route.GetPathTemplate() - if err != nil { - return err - } - - methods, err := route.GetMethods() - if err != nil { - return err - } - - for _, method := range methods { - r.Path(pathTemplate).Methods(method).Handler(route.GetHandler()) - } - - return nil - }) -} diff --git a/components/payments/cmd/connectors/internal/api/service/bank_account.go b/components/payments/cmd/connectors/internal/api/service/bank_account.go deleted file mode 100644 index 634877a35d..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/bank_account.go +++ /dev/null @@ -1,190 +0,0 @@ -package service - -import ( - "context" - "time" - - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -type CreateBankAccountRequest struct { - AccountNumber string `json:"accountNumber"` - IBAN string `json:"iban"` - SwiftBicCode string `json:"swiftBicCode"` - Country string `json:"country"` - ConnectorID string `json:"connectorID"` - Name string `json:"name"` - Metadata map[string]string `json:"metadata"` -} - -func (c *CreateBankAccountRequest) Validate() error { - if c.AccountNumber == "" && c.IBAN == "" { - return errors.New("either accountNumber or iban must be provided") - } - - if c.Name == "" { - return errors.New("name must be provided") - } - - if c.Country == "" { - return errors.New("country must be provided") - } - - return nil -} - -func (s *Service) CreateBankAccount(ctx context.Context, req *CreateBankAccountRequest) (*models.BankAccount, error) { - var handlers *ConnectorHandlers - var connectorID models.ConnectorID - if req.ConnectorID != "" { - var err error - connectorID, err = models.ConnectorIDFromString(req.ConnectorID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - connector, err := s.store.GetConnector(ctx, connectorID) - if err != nil && errors.Is(err, storage.ErrNotFound) { - return nil, errors.Wrap(ErrValidation, "connector not installed") - } else if err != nil { - return nil, newStorageError(err, "getting connector") - } - - var ok bool - handlers, ok = s.connectorHandlers[connector.Provider] - if !ok || handlers.BankAccountHandler == nil { - return nil, errors.Wrap(ErrValidation, "no bank account handler for connector") - } - } - - bankAccount := &models.BankAccount{ - CreatedAt: time.Now().UTC(), - AccountNumber: req.AccountNumber, - IBAN: req.IBAN, - SwiftBicCode: req.SwiftBicCode, - Country: req.Country, - Name: req.Name, - Metadata: req.Metadata, - } - err := s.store.CreateBankAccount(ctx, bankAccount) - if err != nil { - return nil, newStorageError(err, "creating bank account") - } - - if handlers != nil { - if err := handlers.BankAccountHandler(ctx, connectorID, bankAccount); err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return nil, errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return nil, errors.Wrap(ErrValidation, err.Error()) - default: - return nil, err - } - } - - relatedAccounts, err := s.store.GetBankAccountRelatedAccounts(ctx, bankAccount.ID) - if err != nil { - return nil, newStorageError(err, "fetching bank account") - } - - bankAccount.RelatedAccounts = relatedAccounts - } - - return bankAccount, nil -} - -type ForwardBankAccountToConnectorRequest struct { - ConnectorID string `json:"connectorID"` -} - -func (f *ForwardBankAccountToConnectorRequest) Validate() error { - if f.ConnectorID == "" { - return errors.New("connectorID must be provided") - } - - return nil -} - -func (s *Service) ForwardBankAccountToConnector(ctx context.Context, id string, req *ForwardBankAccountToConnectorRequest) (*models.BankAccount, error) { - bankAccountID, err := uuid.Parse(id) - if err != nil { - return nil, errors.Wrap(ErrInvalidID, err.Error()) - } - - connectorID, err := models.ConnectorIDFromString(req.ConnectorID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - connector, err := s.store.GetConnector(ctx, connectorID) - if err != nil && errors.Is(err, storage.ErrNotFound) { - return nil, errors.Wrap(ErrValidation, "connector not installed") - } else if err != nil { - return nil, newStorageError(err, "getting connector") - } - - handlers, ok := s.connectorHandlers[connector.Provider] - if !ok || handlers.BankAccountHandler == nil { - return nil, errors.Wrap(ErrValidation, "no bank account handler for connector") - } - - bankAccount, err := s.store.GetBankAccount(ctx, bankAccountID, true) - if err != nil { - return nil, newStorageError(err, "fetching bank account") - } - - for _, relatedAccount := range bankAccount.RelatedAccounts { - if relatedAccount.ConnectorID == connectorID { - return nil, errors.Wrap(ErrValidation, "bank account already forwarded to connector") - } - } - - if err := handlers.BankAccountHandler(ctx, connectorID, bankAccount); err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return nil, errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return nil, errors.Wrap(ErrValidation, err.Error()) - default: - return nil, err - } - } - - relatedAccounts, err := s.store.GetBankAccountRelatedAccounts(ctx, bankAccount.ID) - if err != nil { - return nil, newStorageError(err, "fetching bank account") - } - bankAccount.RelatedAccounts = relatedAccounts - - return bankAccount, err -} - -type UpdateBankAccountMetadataRequest struct { - Metadata map[string]string `json:"metadata"` -} - -func (u *UpdateBankAccountMetadataRequest) Validate() error { - if len(u.Metadata) == 0 { - return errors.New("metadata must be provided") - } - - return nil -} - -func (s *Service) UpdateBankAccountMetadata(ctx context.Context, id string, req *UpdateBankAccountMetadataRequest) error { - bankAccountID, err := uuid.Parse(id) - if err != nil { - return errors.Wrap(ErrInvalidID, err.Error()) - } - - if err := s.store.UpdateBankAccountMetadata(ctx, bankAccountID, req.Metadata); err != nil { - return newStorageError(err, "updating bank account metadata") - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/api/service/bank_account_test.go b/components/payments/cmd/connectors/internal/api/service/bank_account_test.go deleted file mode 100644 index 46a0bdbc24..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/bank_account_test.go +++ /dev/null @@ -1,411 +0,0 @@ -package service - -import ( - "context" - "testing" - "time" - - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" -) - -func TestCreateBankAccounts(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *CreateBankAccountRequest - expectedError error - noBankAccountCreateHandler bool - errorBankAccountCreateHandler error - } - - connectorNotFound := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderCurrencyCloud, - } - - var ErrOther = errors.New("other error") - testCases := []testCase{ - { - name: "nominal", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal", - }, - }, - { - name: "nominal with metadata", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal_metadata", - Metadata: map[string]string{"test": "metadata"}, - }, - }, - { - name: "nominal without connectorID", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - Name: "test_nominal", - }, - }, - { - name: "invalid connector id", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: "invalid", - Name: "test_nominal", - }, - expectedError: ErrValidation, - }, - { - name: "connector not found", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorNotFound.String(), - Name: "test_nominal", - }, - expectedError: ErrValidation, - }, - { - name: "no connector handler", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal", - }, - noBankAccountCreateHandler: true, - expectedError: ErrValidation, - }, - { - name: "connector handler error validation", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal", - }, - errorBankAccountCreateHandler: manager.ErrValidation, - expectedError: ErrValidation, - }, - { - name: "connector handler error connector not found", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal", - }, - errorBankAccountCreateHandler: manager.ErrConnectorNotFound, - expectedError: ErrValidation, - }, - { - name: "connector handler other error", - req: &CreateBankAccountRequest{ - AccountNumber: "0112345678", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - ConnectorID: connectorDummyPay.ID.String(), - Name: "test_nominal", - }, - errorBankAccountCreateHandler: ErrOther, - expectedError: ErrOther, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - if !tc.noBankAccountCreateHandler { - handlers = map[models.ConnectorProvider]*ConnectorHandlers{ - models.ConnectorProviderDummyPay: { - BankAccountHandler: func(ctx context.Context, connectorID models.ConnectorID, bankAccount *models.BankAccount) error { - if tc.errorBankAccountCreateHandler != nil { - return tc.errorBankAccountCreateHandler - } - - return nil - }, - }, - } - } - - service := New(&MockStore{}, &MockPublisher{}, messages.NewMessages(""), handlers) - - _, err := service.CreateBankAccount(context.Background(), tc.req) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestForwardBankAccountToConnector(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - bankAccountID string - req *ForwardBankAccountToConnectorRequest - withBankAccountRelatedAccounts []*models.BankAccountRelatedAccount - expectedError error - noBankAccountForwardHandler bool - errorBankAccountForwardHandler error - } - - connectorNotFound := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderCurrencyCloud, - } - - var ErrOther = errors.New("other error") - bankAccountID := uuid.New() - testCases := []testCase{ - { - name: "nominal", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - }, - { - name: "already forwarded to connector", - bankAccountID: bankAccountID.String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - withBankAccountRelatedAccounts: []*models.BankAccountRelatedAccount{ - { - ID: uuid.New(), - CreatedAt: time.Now().UTC(), - BankAccountID: bankAccountID, - ConnectorID: connectorDummyPay.ID, - AccountID: models.AccountID{ - Reference: "test", - ConnectorID: connectorDummyPay.ID, - }, - }, - }, - expectedError: ErrValidation, - }, - { - name: "empty bank account id", - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - expectedError: ErrInvalidID, - }, - { - name: "invalid bank account id", - bankAccountID: "invalid", - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - expectedError: ErrInvalidID, - }, - { - name: "missing connectorID", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{}, - expectedError: ErrValidation, - }, - { - name: "invalid connector id", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: "invalid", - }, - expectedError: ErrValidation, - }, - { - name: "connector not found", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorNotFound.String(), - }, - expectedError: ErrValidation, - }, - { - name: "no connector handler", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - noBankAccountForwardHandler: true, - expectedError: ErrValidation, - }, - { - name: "connector handler error validation", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - errorBankAccountForwardHandler: manager.ErrValidation, - expectedError: ErrValidation, - }, - { - name: "connector handler error connector not found", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - errorBankAccountForwardHandler: manager.ErrConnectorNotFound, - expectedError: ErrValidation, - }, - { - name: "connector handler other error", - bankAccountID: uuid.New().String(), - req: &ForwardBankAccountToConnectorRequest{ - ConnectorID: connectorDummyPay.ID.String(), - }, - errorBankAccountForwardHandler: ErrOther, - expectedError: ErrOther, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - if !tc.noBankAccountForwardHandler { - handlers = map[models.ConnectorProvider]*ConnectorHandlers{ - models.ConnectorProviderDummyPay: { - BankAccountHandler: func(ctx context.Context, connectorID models.ConnectorID, bankAccount *models.BankAccount) error { - if tc.errorBankAccountForwardHandler != nil { - return tc.errorBankAccountForwardHandler - } - - return nil - }, - }, - } - } - - store := &MockStore{} - service := New(store.WithBankAccountRelatedAccounts(tc.withBankAccountRelatedAccounts), &MockPublisher{}, messages.NewMessages(""), handlers) - - _, err := service.ForwardBankAccountToConnector(context.Background(), tc.bankAccountID, tc.req) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestUpdateBankAccountMetadata(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - bankAccountID string - req *UpdateBankAccountMetadataRequest - storageError error - expectedError error - } - - testCases := []testCase{ - { - name: "nominal", - bankAccountID: uuid.New().String(), - req: &UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "err not found from storage", - bankAccountID: uuid.New().String(), - req: &UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - storageError: storage.ErrNotFound, - expectedError: storage.ErrNotFound, - }, - { - name: "empty bank account id", - req: &UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedError: ErrInvalidID, - }, - { - name: "invalid bank account id", - bankAccountID: "invalid", - req: &UpdateBankAccountMetadataRequest{ - Metadata: map[string]string{ - "foo": "bar", - }, - }, - expectedError: ErrInvalidID, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - - store := &MockStore{} - if tc.storageError != nil { - store = store.WithError(tc.storageError) - } - service := New(store, &MockPublisher{}, messages.NewMessages(""), handlers) - - err := service.UpdateBankAccountMetadata(context.Background(), tc.bankAccountID, tc.req) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/service/connector.go b/components/payments/cmd/connectors/internal/api/service/connector.go deleted file mode 100644 index fac2548ced..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/connector.go +++ /dev/null @@ -1,12 +0,0 @@ -package service - -import ( - "context" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Service) ListConnectors(ctx context.Context) ([]*models.Connector, error) { - connectors, err := s.store.ListConnectors(ctx) - return connectors, newStorageError(err, "listing connectors") -} diff --git a/components/payments/cmd/connectors/internal/api/service/errors.go b/components/payments/cmd/connectors/internal/api/service/errors.go deleted file mode 100644 index fff30a8380..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/errors.go +++ /dev/null @@ -1,40 +0,0 @@ -package service - -import ( - "errors" - "fmt" -) - -var ( - ErrValidation = errors.New("validation error") - ErrPublish = errors.New("publish error") - ErrInvalidID = errors.New("invalid id") -) - -type storageError struct { - err error - msg string -} - -func (e *storageError) Error() string { - return fmt.Sprintf("%s: %s", e.msg, e.err) -} - -func (e *storageError) Is(err error) bool { - _, ok := err.(*storageError) - return ok -} - -func (e *storageError) Unwrap() error { - return e.err -} - -func newStorageError(err error, msg string) error { - if err == nil { - return nil - } - return &storageError{ - err: err, - msg: msg, - } -} diff --git a/components/payments/cmd/connectors/internal/api/service/ping.go b/components/payments/cmd/connectors/internal/api/service/ping.go deleted file mode 100644 index 8fbedef75f..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/ping.go +++ /dev/null @@ -1,5 +0,0 @@ -package service - -func (s *Service) Ping() error { - return newStorageError(s.store.Ping(), "ping") -} diff --git a/components/payments/cmd/connectors/internal/api/service/service.go b/components/payments/cmd/connectors/internal/api/service/service.go deleted file mode 100644 index 9e59868037..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/service.go +++ /dev/null @@ -1,66 +0,0 @@ -package service - -import ( - "context" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -type InitiatePaymentHandler func(ctx context.Context, transfer *models.TransferInitiation) error -type ReversePaymentHandler func(ctx context.Context, transfer *models.TransferReversal) error -type BankAccountHandler func(ctx context.Context, connectorID models.ConnectorID, bankAccount *models.BankAccount) error - -type Store interface { - Ping() error - - GetConnector(ctx context.Context, connectorID models.ConnectorID) (*models.Connector, error) - ListConnectors(ctx context.Context) ([]*models.Connector, error) - - UpsertAccounts(ctx context.Context, accounts []*models.Account) ([]models.AccountID, error) - GetAccount(ctx context.Context, id string) (*models.Account, error) - - CreateBankAccount(ctx context.Context, account *models.BankAccount) error - UpdateBankAccountMetadata(ctx context.Context, id uuid.UUID, metadata map[string]string) error - GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) - GetBankAccountRelatedAccounts(ctx context.Context, id uuid.UUID) ([]*models.BankAccountRelatedAccount, error) - - ListConnectorsByProvider(ctx context.Context, provider models.ConnectorProvider) ([]*models.Connector, error) - IsInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) - - CreateTransferInitiation(ctx context.Context, transferInitiation *models.TransferInitiation) error - ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) - UpdateTransferInitiationPaymentsStatus(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, adjustment *models.TransferInitiationAdjustment) error - DeleteTransferInitiation(ctx context.Context, id models.TransferInitiationID) error - - CreateTransferReversal(ctx context.Context, transferReversal *models.TransferReversal) error -} - -type Service struct { - store Store - publisher message.Publisher - messages *messages.Messages - connectorHandlers map[models.ConnectorProvider]*ConnectorHandlers -} - -type ConnectorHandlers struct { - InitiatePaymentHandler InitiatePaymentHandler - ReversePaymentHandler ReversePaymentHandler - BankAccountHandler BankAccountHandler -} - -func New( - store Store, - publisher message.Publisher, - messages *messages.Messages, - connectorHandlers map[models.ConnectorProvider]*ConnectorHandlers, -) *Service { - return &Service{ - store: store, - publisher: publisher, - connectorHandlers: connectorHandlers, - messages: messages, - } -} diff --git a/components/payments/cmd/connectors/internal/api/service/service_test.go b/components/payments/cmd/connectors/internal/api/service/service_test.go deleted file mode 100644 index b17269fe53..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/service_test.go +++ /dev/null @@ -1,284 +0,0 @@ -package service - -import ( - "context" - "math/big" - "time" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -var ( - connectorDummyPay = models.Connector{ - ID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - Name: "c1", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Provider: models.ConnectorProviderDummyPay, - } - - connectorBankingCircle = models.Connector{ - ID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderBankingCircle, - }, - Name: "c2", - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - Provider: models.ConnectorProviderBankingCircle, - } - - transferInitiationWaiting = models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorDummyPay.ID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: connectorDummyPay.ID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorDummyPay.ID, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: "EUR/2", - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - } - - transferInitiationFailed = models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref2", - ConnectorID: connectorDummyPay.ID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &models.AccountID{ - Reference: "acc1", - ConnectorID: connectorDummyPay.ID, - }, - DestinationAccountID: models.AccountID{ - Reference: "acc2", - ConnectorID: connectorDummyPay.ID, - }, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorDummyPay.ID, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: "EUR/2", - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref2", - ConnectorID: connectorDummyPay.ID, - }, - CreatedAt: time.Date(2023, 11, 22, 9, 0, 0, 0, time.UTC), - Status: models.TransferInitiationStatusFailed, - Error: "some error", - }, - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref2", - ConnectorID: connectorDummyPay.ID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - } - - sourceAccountID = models.AccountID{ - Reference: "acc1", - ConnectorID: connectorDummyPay.ID, - } - - destinationAccountID = models.AccountID{ - Reference: "acc2", - ConnectorID: connectorDummyPay.ID, - } - - destinationExternalAccountID = models.AccountID{ - Reference: "acc3", - ConnectorID: connectorDummyPay.ID, - } -) - -type MockStore struct { - errorToSend error - listConnectorsNB int - bankAccountRelatedAccounts []*models.BankAccountRelatedAccount -} - -func (m *MockStore) WithError(err error) *MockStore { - m.errorToSend = err - return m -} - -func (m *MockStore) WithListConnectorsNB(nb int) *MockStore { - m.listConnectorsNB = nb - return m -} - -func (m *MockStore) WithBankAccountRelatedAccounts(relatedAccounts []*models.BankAccountRelatedAccount) *MockStore { - m.bankAccountRelatedAccounts = relatedAccounts - return m -} - -func (m *MockStore) Ping() error { - return nil -} - -func (m *MockStore) GetConnector(ctx context.Context, connectorID models.ConnectorID) (*models.Connector, error) { - if connectorID == connectorDummyPay.ID { - return &connectorDummyPay, nil - } else if connectorID == connectorBankingCircle.ID { - return &connectorBankingCircle, nil - } - - return nil, storage.ErrNotFound -} - -func (m *MockStore) ListConnectors(ctx context.Context) ([]*models.Connector, error) { - return []*models.Connector{&connectorDummyPay, &connectorBankingCircle}, nil -} - -func (m *MockStore) UpsertAccounts(ctx context.Context, accounts []*models.Account) ([]models.AccountID, error) { - return nil, nil -} - -func (m *MockStore) GetAccount(ctx context.Context, id string) (*models.Account, error) { - switch id { - case sourceAccountID.String(): - return &models.Account{ - ID: sourceAccountID, - Type: models.AccountTypeInternal, - }, nil - case destinationAccountID.String(): - return &models.Account{ - ID: destinationAccountID, - Type: models.AccountTypeInternal, - }, nil - case destinationExternalAccountID.String(): - return &models.Account{ - ID: destinationAccountID, - Type: models.AccountTypeExternal, - }, nil - } - - return nil, nil -} - -func (m *MockStore) CreateBankAccount(ctx context.Context, account *models.BankAccount) error { - account.ID = uuid.New() - return nil -} - -func (m *MockStore) UpdateBankAccountMetadata(ctx context.Context, id uuid.UUID, metadata map[string]string) error { - return m.errorToSend -} - -func (m *MockStore) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - return &models.BankAccount{ - ID: id, - CreatedAt: time.Now().UTC(), - Name: "test", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "HBUKGB4B", - Country: "FR", - Metadata: map[string]string{}, - RelatedAccounts: m.bankAccountRelatedAccounts, - }, nil -} - -func (m *MockStore) GetBankAccountRelatedAccounts(ctx context.Context, id uuid.UUID) ([]*models.BankAccountRelatedAccount, error) { - return nil, nil -} - -func (m *MockStore) ListConnectorsByProvider(ctx context.Context, provider models.ConnectorProvider) ([]*models.Connector, error) { - switch m.listConnectorsNB { - case 0: - return []*models.Connector{}, nil - case 1: - return []*models.Connector{&connectorDummyPay}, nil - default: - return []*models.Connector{&connectorDummyPay, &connectorBankingCircle}, nil - } -} - -func (m *MockStore) IsInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - if connectorID == connectorDummyPay.ID { - return true, nil - } - - return false, nil -} - -func (m *MockStore) CreateTransferInitiation(ctx context.Context, transferInitiation *models.TransferInitiation) error { - return nil -} - -func (m *MockStore) ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - if id == transferInitiationWaiting.ID { - tc := transferInitiationWaiting - return &tc, nil - } else if id == transferInitiationFailed.ID { - tc := transferInitiationFailed - return &tc, nil - } - - return nil, storage.ErrNotFound -} - -func (m *MockStore) UpdateTransferInitiationPaymentsStatus(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, adjustment *models.TransferInitiationAdjustment) error { - return nil -} - -func (m *MockStore) DeleteTransferInitiation(ctx context.Context, id models.TransferInitiationID) error { - return nil -} - -func (m *MockStore) CreateTransferReversal(ctx context.Context, transferReversal *models.TransferReversal) error { - return nil -} - -type MockPublisher struct { - errorToSend error -} - -func (m *MockPublisher) WithError(err error) *MockPublisher { - m.errorToSend = err - return m -} - -func (m *MockPublisher) Publish(topic string, messages ...*message.Message) error { - return m.errorToSend -} - -func (m *MockPublisher) Close() error { - return nil -} diff --git a/components/payments/cmd/connectors/internal/api/service/transfer_initiation.go b/components/payments/cmd/connectors/internal/api/service/transfer_initiation.go deleted file mode 100644 index a487a99e98..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/transfer_initiation.go +++ /dev/null @@ -1,394 +0,0 @@ -package service - -import ( - "context" - "fmt" - "math/big" - "time" - - "github.com/formancehq/go-libs/publish" - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -type CreateTransferInitiationRequest struct { - Reference string `json:"reference"` - ScheduledAt time.Time `json:"scheduledAt"` - Description string `json:"description"` - SourceAccountID string `json:"sourceAccountID"` - DestinationAccountID string `json:"destinationAccountID"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` - Type string `json:"type"` - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` - Validated bool `json:"validated"` - Metadata map[string]string `json:"metadata"` -} - -func (r *CreateTransferInitiationRequest) Validate() error { - if r.Reference == "" { - return errors.New("reference is required") - } - - if r.SourceAccountID != "" { - _, err := models.AccountIDFromString(r.SourceAccountID) - if err != nil { - return err - } - } - - _, err := models.AccountIDFromString(r.DestinationAccountID) - if err != nil { - return err - } - - _, err = models.TransferInitiationTypeFromString(r.Type) - if err != nil { - return err - } - - if r.Amount == nil { - return errors.New("amount is required") - } - - if r.Asset == "" { - return errors.New("asset is required") - } - - return nil -} - -func (s *Service) CreateTransferInitiation(ctx context.Context, req *CreateTransferInitiationRequest) (*models.TransferInitiation, error) { - status := models.TransferInitiationStatusWaitingForValidation - if req.Validated { - status = models.TransferInitiationStatusValidated - } - - var connectorID models.ConnectorID - if req.ConnectorID == "" { - provider, err := models.ConnectorProviderFromString(req.Provider) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - connectors, err := s.store.ListConnectorsByProvider(ctx, provider) - if err != nil { - return nil, newStorageError(err, "listing connectors") - } - - if len(connectors) == 0 { - return nil, errors.Wrap(ErrValidation, fmt.Sprintf("no connector found for provider %s", provider)) - } - - if len(connectors) > 1 { - return nil, errors.Wrap(ErrValidation, fmt.Sprintf("multiple connectors found for provider %s", provider)) - } - - connectorID = connectors[0].ID - } else { - var err error - connectorID, err = models.ConnectorIDFromString(req.ConnectorID) - if err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - } - - isInstalled, _ := s.store.IsInstalledByConnectorID(ctx, connectorID) - if !isInstalled { - return nil, errors.Wrap(ErrValidation, fmt.Sprintf("connector %s is not installed", req.ConnectorID)) - } - - if req.SourceAccountID != "" { - _, err := s.store.GetAccount(ctx, req.SourceAccountID) - if err != nil { - return nil, newStorageError(err, "getting source account") - } - } - - destinationAccount, err := s.store.GetAccount(ctx, req.DestinationAccountID) - if err != nil { - return nil, newStorageError(err, "getting destination account") - } - - transferType := models.MustTransferInitiationTypeFromString(req.Type) - - switch transferType { - case models.TransferInitiationTypeTransfer: - if destinationAccount.Type != models.AccountTypeInternal { - // account should be internal when doing a transfer, return an error - return nil, errors.Wrap(ErrValidation, "destination account must be internal when doing a transfer") - } - case models.TransferInitiationTypePayout: - switch destinationAccount.Type { - case models.AccountTypeExternal, models.AccountTypeExternalFormance: - default: - // account should be external when doing a payout, return an error - return nil, errors.Wrap(ErrValidation, "destination account must be external when doing a payout") - } - } - - id := models.TransferInitiationID{ - Reference: req.Reference, - ConnectorID: connectorID, - } - - // Always insert timestamp as UTC - createdAt := time.Now().UTC() - tf := &models.TransferInitiation{ - ID: id, - CreatedAt: createdAt, - ScheduledAt: req.ScheduledAt, - Description: req.Description, - DestinationAccountID: models.MustAccountIDFromString(req.DestinationAccountID), - ConnectorID: connectorID, - Provider: connectorID.Provider, - Type: transferType, - Amount: req.Amount, - InitialAmount: req.Amount, - Asset: models.Asset(req.Asset), - Metadata: req.Metadata, - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: id, - CreatedAt: createdAt, - Status: status, - }, - }, - } - - if req.SourceAccountID != "" { - sID := models.MustAccountIDFromString(req.SourceAccountID) - tf.SourceAccountID = &sID - } - - if err := s.store.CreateTransferInitiation(ctx, tf); err != nil { - return nil, newStorageError(err, "creating transfer initiation") - } - - if err := s.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - s.messages.NewEventSavedTransferInitiations(tf), - ), - ); err != nil { - return nil, errors.Wrap(ErrPublish, err.Error()) - } - - if status == models.TransferInitiationStatusValidated { - connector, err := s.store.GetConnector(ctx, connectorID) - if err != nil { - return nil, newStorageError(err, "getting connector") - } - - handlers, ok := s.connectorHandlers[connector.Provider] - if !ok { - return nil, errors.Wrap(ErrValidation, fmt.Sprintf("no payment handler for provider %v", connector.Provider)) - } - - err = handlers.InitiatePaymentHandler(ctx, tf) - if err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return nil, errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return nil, errors.Wrap(ErrValidation, err.Error()) - default: - return nil, err - } - } - } - - return tf, nil -} - -type UpdateTransferInitiationStatusRequest struct { - Status string `json:"status"` -} - -func (r *UpdateTransferInitiationStatusRequest) Validate() error { - if r.Status == "" { - return errors.New("status is required") - } - - return nil -} - -func (s *Service) UpdateTransferInitiationStatus(ctx context.Context, id string, req *UpdateTransferInitiationStatusRequest) error { - status, err := models.TransferInitiationStatusFromString(req.Status) - if err != nil { - return errors.Wrap(ErrValidation, err.Error()) - } - - switch status { - case models.TransferInitiationStatusWaitingForValidation: - return errors.Wrap(ErrValidation, "cannot set back transfer initiation status to waiting for validation") - case models.TransferInitiationStatusFailed, - models.TransferInitiationStatusProcessed, - models.TransferInitiationStatusProcessing: - return errors.Wrap(ErrValidation, "either VALIDATED or REJECTED status can be set") - default: - } - - transferID, err := models.TransferInitiationIDFromString(id) - if err != nil { - return errors.Wrap(ErrInvalidID, err.Error()) - } - - previousTransferInitiation, err := s.store.ReadTransferInitiation(ctx, transferID) - if err != nil { - return newStorageError(err, "reading transfer initiation") - } - - // Check last status - if len(previousTransferInitiation.RelatedAdjustments) == 0 || - previousTransferInitiation.RelatedAdjustments[0].Status != models.TransferInitiationStatusWaitingForValidation { - return errors.Wrap(ErrValidation, "only waiting for validation transfer initiation can be updated") - } - - adjustment := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: transferID, - CreatedAt: time.Now(), - Status: status, - Error: "", - } - - previousTransferInitiation.RelatedAdjustments = append(previousTransferInitiation.RelatedAdjustments, adjustment) - previousTransferInitiation.SortRelatedAdjustments() - - err = s.store.UpdateTransferInitiationPaymentsStatus(ctx, transferID, nil, adjustment) - if err != nil { - return newStorageError(err, "updating transfer initiation payments status") - } - - if err := s.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - s.messages.NewEventSavedTransferInitiations(previousTransferInitiation), - ), - ); err != nil { - return errors.Wrap(ErrPublish, err.Error()) - } - - if status == models.TransferInitiationStatusValidated { - handlers, ok := s.connectorHandlers[previousTransferInitiation.Provider] - if !ok { - return errors.Wrap(ErrValidation, fmt.Sprintf("no payment handler for provider %v", previousTransferInitiation.Provider)) - } - - err = handlers.InitiatePaymentHandler(ctx, previousTransferInitiation) - if err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return errors.Wrap(ErrValidation, err.Error()) - default: - return err - } - } - } - - return nil -} - -func (s *Service) RetryTransferInitiation(ctx context.Context, id string) error { - transferID, err := models.TransferInitiationIDFromString(id) - if err != nil { - return errors.Wrap(ErrInvalidID, err.Error()) - } - - previousTransferInitiation, err := s.store.ReadTransferInitiation(ctx, transferID) - if err != nil { - return newStorageError(err, "reading transfer initiation") - } - - if len(previousTransferInitiation.RelatedAdjustments) == 0 || - previousTransferInitiation.RelatedAdjustments[0].Status != models.TransferInitiationStatusFailed { - return errors.Wrap(ErrValidation, "only failed transfer initiation can be retried") - } - - adjustment := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: transferID, - CreatedAt: time.Now(), - Status: models.TransferInitiationStatusAskRetried, - Error: "", - Metadata: map[string]string{}, - } - - err = s.store.UpdateTransferInitiationPaymentsStatus(ctx, transferID, nil, adjustment) - if err != nil { - return newStorageError(err, "updating transfer initiation payments status") - } - - if err := s.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - s.messages.NewEventSavedTransferInitiations(previousTransferInitiation), - ), - ); err != nil { - return errors.Wrap(ErrPublish, err.Error()) - } - - handlers, ok := s.connectorHandlers[previousTransferInitiation.Provider] - if !ok { - return errors.Wrap(ErrValidation, fmt.Sprintf("no payment handler for provider %v", previousTransferInitiation.Provider)) - } - - err = handlers.InitiatePaymentHandler(ctx, previousTransferInitiation) - if err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return errors.Wrap(ErrValidation, err.Error()) - default: - return err - } - } - - return nil -} - -func (s *Service) DeleteTransferInitiation(ctx context.Context, id string) error { - transferID, err := models.TransferInitiationIDFromString(id) - if err != nil { - return errors.Wrap(ErrInvalidID, err.Error()) - } - - tf, err := s.store.ReadTransferInitiation(ctx, transferID) - if err != nil { - return newStorageError(err, "reading transfer initiation") - } - - if len(tf.RelatedAdjustments) == 0 || - tf.RelatedAdjustments[0].Status != models.TransferInitiationStatusWaitingForValidation { - return errors.Wrap(ErrValidation, "only waiting for validation transfer initiation can be deleted") - } - - err = s.store.DeleteTransferInitiation(ctx, transferID) - if err != nil { - return newStorageError(err, "deleting transfer initiation") - } - - if err := s.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - s.messages.NewEventDeleteTransferInitiation(tf.ID), - ), - ); err != nil { - return errors.Wrap(ErrPublish, err.Error()) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/api/service/transfer_initiation_test.go b/components/payments/cmd/connectors/internal/api/service/transfer_initiation_test.go deleted file mode 100644 index 943547540d..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/transfer_initiation_test.go +++ /dev/null @@ -1,715 +0,0 @@ -package service - -import ( - "context" - "math/big" - "testing" - "time" - - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" -) - -func TestCreateTransferInitiation(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *CreateTransferInitiationRequest - expectedTF *models.TransferInitiation - listConnectorLength int - errorPublish bool - errorPaymentHandler error - noPaymentsHandler bool - expectedError error - } - - sourceAccountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorDummyPay.ID, - } - - destinationAccountID := models.AccountID{ - Reference: "acc2", - ConnectorID: connectorDummyPay.ID, - } - - testCases := []testCase{ - { - name: "nominal", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedTF: &models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - DestinationAccountID: destinationAccountID, - SourceAccountID: &sourceAccountID, - ConnectorID: connectorDummyPay.ID, - Provider: models.ConnectorProviderDummyPay, - Type: models.TransferInitiationTypeTransfer, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: models.Asset("EUR/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - }, - }, - { - name: "nominal without description", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedTF: &models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - DestinationAccountID: destinationAccountID, - SourceAccountID: &sourceAccountID, - ConnectorID: connectorDummyPay.ID, - Provider: models.ConnectorProviderDummyPay, - Type: models.TransferInitiationTypeTransfer, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: models.Asset("EUR/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - }, - }, - { - name: "nominal with status changed", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: true, - }, - expectedTF: &models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - DestinationAccountID: destinationAccountID, - SourceAccountID: &sourceAccountID, - ConnectorID: connectorDummyPay.ID, - Provider: models.ConnectorProviderDummyPay, - Type: models.TransferInitiationTypeTransfer, - Amount: big.NewInt(100), - InitialAmount: big.NewInt(100), - Asset: models.Asset("EUR/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorDummyPay.ID, - }, - Status: models.TransferInitiationStatusValidated, - }, - }, - }, - }, - { - name: "transfer with external account as destination", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationExternalAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedError: ErrValidation, - }, - { - name: "payout with internal account as destination", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypePayout.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedError: ErrValidation, - }, - { - name: "invalid connector id", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: "invalid", - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedError: ErrValidation, - }, - { - name: "invalid provider", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - Provider: "invalid", - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedError: ErrValidation, - }, - { - name: "too many connectors list", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - listConnectorLength: 2, - expectedError: ErrValidation, - }, - { - name: "no connectors in connectors list", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - listConnectorLength: 0, - expectedError: ErrValidation, - }, - { - name: "connector not installed", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorBankingCircle.ID.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedError: ErrValidation, - }, - { - name: "error publishing", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - errorPublish: true, - expectedError: ErrPublish, - }, - { - name: "no payments handler found", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: true, - }, - noPaymentsHandler: true, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: true, - }, - errorPaymentHandler: manager.ErrValidation, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - req: &CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), - Description: "test", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorDummyPay.ID.String(), - Provider: string(models.ConnectorProviderDummyPay), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: true, - }, - errorPaymentHandler: manager.ErrConnectorNotFound, - expectedError: ErrValidation, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - m := &MockPublisher{} - s := &MockStore{} - - var errPublish error - if tc.errorPublish { - errPublish = errors.New("publish error") - } - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - if !tc.noPaymentsHandler { - handlers = map[models.ConnectorProvider]*ConnectorHandlers{ - models.ConnectorProviderDummyPay: { - InitiatePaymentHandler: func(ctx context.Context, transfer *models.TransferInitiation) error { - if tc.errorPaymentHandler != nil { - return tc.errorPaymentHandler - } - - return nil - }, - }, - } - } - service := New(s.WithListConnectorsNB(tc.listConnectorLength), m.WithError(errPublish), messages.NewMessages(""), handlers) - - tf, err := service.CreateTransferInitiation(context.Background(), tc.req) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - tc.expectedTF.CreatedAt = tf.CreatedAt - require.Len(t, tf.RelatedAdjustments, 1) - tc.expectedTF.RelatedAdjustments[0].CreatedAt = tf.RelatedAdjustments[0].CreatedAt - tc.expectedTF.RelatedAdjustments[0].ID = tf.RelatedAdjustments[0].ID - require.Equal(t, tc.expectedTF, tf) - } - }) - } -} - -func TestUpdateTransferInitiationStatus(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - transferID string - req *UpdateTransferInitiationStatusRequest - errorPublish bool - errorPaymentHandler error - noPaymentsHandler bool - expectedError error - } - - tfNotFoundID := models.TransferInitiationID{ - Reference: "not_found", - ConnectorID: connectorDummyPay.ID, - } - - testCases := []testCase{ - { - name: "nominal validated", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationWaiting.ID.String(), - }, - { - name: "nominal rejected", - req: &UpdateTransferInitiationStatusRequest{ - Status: "REJECTED", - }, - transferID: transferInitiationWaiting.ID.String(), - }, - { - name: "unknown status", - req: &UpdateTransferInitiationStatusRequest{ - Status: "INVALID", - }, - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid status waiting for validation", - req: &UpdateTransferInitiationStatusRequest{ - Status: "WAITING_FOR_VALIDATION", - }, - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid status failed", - req: &UpdateTransferInitiationStatusRequest{ - Status: "FAILED", - }, - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid status processed", - req: &UpdateTransferInitiationStatusRequest{ - Status: "PROCESSED", - }, - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid status processing", - req: &UpdateTransferInitiationStatusRequest{ - Status: "PROCESSING", - }, - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "invalid transfer id", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: "invalid", - expectedError: ErrInvalidID, - }, - { - name: "transfer not found", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: tfNotFoundID.String(), - expectedError: storage.ErrNotFound, - }, - { - name: "previous transfer with wrong status", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationFailed.ID.String(), - expectedError: ErrValidation, - }, - { - name: "error publishing", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationWaiting.ID.String(), - errorPublish: true, - expectedError: ErrPublish, - }, - { - name: "error publishing", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationWaiting.ID.String(), - noPaymentsHandler: true, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationWaiting.ID.String(), - errorPaymentHandler: manager.ErrValidation, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - req: &UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferInitiationWaiting.ID.String(), - errorPaymentHandler: manager.ErrConnectorNotFound, - expectedError: ErrValidation, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - m := &MockPublisher{} - - var errPublish error - if tc.errorPublish { - errPublish = errors.New("publish error") - } - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - if !tc.noPaymentsHandler { - handlers = map[models.ConnectorProvider]*ConnectorHandlers{ - models.ConnectorProviderDummyPay: { - InitiatePaymentHandler: func(ctx context.Context, transfer *models.TransferInitiation) error { - if tc.errorPaymentHandler != nil { - return tc.errorPaymentHandler - } - - return nil - }, - }, - } - } - service := New(&MockStore{}, m.WithError(errPublish), messages.NewMessages(""), handlers) - - err := service.UpdateTransferInitiationStatus(context.Background(), tc.transferID, tc.req) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestRetryTransferInitiation(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - transferID string - errorPublish bool - errorPaymentHandler error - noPaymentsHandler bool - expectedError error - } - - testCases := []testCase{ - { - name: "nominal", - transferID: transferInitiationFailed.ID.String(), - }, - { - name: "invalid transfer id", - transferID: "invalid", - expectedError: ErrInvalidID, - }, - { - name: "invalid previous transfer status", - transferID: transferInitiationWaiting.ID.String(), - expectedError: ErrValidation, - }, - { - name: "error publishing", - transferID: transferInitiationFailed.ID.String(), - noPaymentsHandler: true, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - transferID: transferInitiationFailed.ID.String(), - errorPaymentHandler: manager.ErrValidation, - expectedError: ErrValidation, - }, - { - name: "error in payments handler", - transferID: transferInitiationFailed.ID.String(), - errorPaymentHandler: manager.ErrConnectorNotFound, - expectedError: ErrValidation, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - m := &MockPublisher{} - - var errPublish error - if tc.errorPublish { - errPublish = errors.New("publish error") - } - - var handlers map[models.ConnectorProvider]*ConnectorHandlers - if !tc.noPaymentsHandler { - handlers = map[models.ConnectorProvider]*ConnectorHandlers{ - models.ConnectorProviderDummyPay: { - InitiatePaymentHandler: func(ctx context.Context, transfer *models.TransferInitiation) error { - if tc.errorPaymentHandler != nil { - return tc.errorPaymentHandler - } - - return nil - }, - }, - } - } - service := New(&MockStore{}, m.WithError(errPublish), messages.NewMessages(""), handlers) - - err := service.RetryTransferInitiation(context.Background(), tc.transferID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestDeleteTransferInitiation(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - transferID string - errorPublish bool - expectedError error - } - - testCases := []testCase{ - { - name: "nominal", - transferID: transferInitiationWaiting.ID.String(), - }, - { - name: "invalid transfer id", - transferID: "invalid", - expectedError: ErrInvalidID, - }, - { - name: "invalid previous transfer initiation status", - transferID: transferInitiationFailed.ID.String(), - expectedError: ErrValidation, - }, - { - name: "error publishing", - transferID: transferInitiationWaiting.ID.String(), - errorPublish: true, - expectedError: ErrPublish, - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - m := &MockPublisher{} - - var errPublish error - if tc.errorPublish { - errPublish = errors.New("publish error") - } - - service := New(&MockStore{}, m.WithError(errPublish), messages.NewMessages(""), nil) - - err := service.DeleteTransferInitiation(context.Background(), tc.transferID) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError)) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/service/transfer_reversal.go b/components/payments/cmd/connectors/internal/api/service/transfer_reversal.go deleted file mode 100644 index 7bf526e500..0000000000 --- a/components/payments/cmd/connectors/internal/api/service/transfer_reversal.go +++ /dev/null @@ -1,122 +0,0 @@ -package service - -import ( - "context" - "fmt" - "math/big" - "time" - - manager "github.com/formancehq/payments/cmd/connectors/internal/api/connectors_manager" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" -) - -type ReverseTransferInitiationRequest struct { - Reference string `json:"reference"` - Description string `json:"description"` - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` - Metadata map[string]string `json:"metadata"` -} - -func (r *ReverseTransferInitiationRequest) Validate() error { - if r.Reference == "" { - return errors.New("reference is required") - } - - if r.Amount == nil { - return errors.New("amount is required") - } - - if r.Asset == "" { - return errors.New("asset is required") - } - - return nil -} - -func checkIfReversalIsValid(transfer *models.TransferInitiation, req *ReverseTransferInitiationRequest) error { - finalAmount := new(big.Int) - finalAmount.Sub(transfer.Amount, req.Amount) - switch finalAmount.Cmp(big.NewInt(0)) { - case 0, 1: - // Nothing to do, requested reversed amount if less than or equal to the transfer amount - case -1: - return errors.New("reversed amount is greater than the transfer amount") - } - - if transfer.Type == models.TransferInitiationTypePayout { - return errors.New("payouts cannot be reversed") - } - - foundProcessed := false - for _, adjustment := range transfer.RelatedAdjustments { - if adjustment.Status == models.TransferInitiationStatusProcessed { - foundProcessed = true - break - } - } - - if !foundProcessed { - // transfer was never processed, so we can't reverse it - return errors.New("transfer was never processed") - } - - return nil -} - -func (s *Service) ReverseTransferInitiation(ctx context.Context, transferID string, req *ReverseTransferInitiationRequest) (*models.TransferReversal, error) { - transferInitiationID, err := models.TransferInitiationIDFromString(transferID) - if err != nil { - return nil, ErrInvalidID - } - - transfer, err := s.store.ReadTransferInitiation(ctx, transferInitiationID) - if err != nil { - return nil, newStorageError(err, "fetching transfer initiation") - } - - if err := checkIfReversalIsValid(transfer, req); err != nil { - return nil, errors.Wrap(ErrValidation, err.Error()) - } - - now := time.Now().UTC() - reversal := &models.TransferReversal{ - ID: models.TransferReversalID{ - Reference: req.Reference, - ConnectorID: transfer.ConnectorID, - }, - TransferInitiationID: transferInitiationID, - CreatedAt: now, - UpdatedAt: now, - Description: req.Description, - ConnectorID: transfer.ConnectorID, - Amount: req.Amount, - Asset: models.Asset(req.Asset), - Status: models.TransferReversalStatusProcessing, - Error: "", - Metadata: req.Metadata, - } - - if err := s.store.CreateTransferReversal(ctx, reversal); err != nil { - return nil, newStorageError(err, "creating transfer reversal") - } - - handlers, ok := s.connectorHandlers[transfer.Provider] - if !ok { - return nil, errors.Wrap(ErrValidation, fmt.Sprintf("no reverse payment handler for provider %v", transfer.Provider)) - } - - if err := handlers.ReversePaymentHandler(ctx, reversal); err != nil { - switch { - case errors.Is(err, manager.ErrValidation): - return nil, errors.Wrap(ErrValidation, err.Error()) - case errors.Is(err, manager.ErrConnectorNotFound): - return nil, errors.Wrap(ErrValidation, err.Error()) - default: - return nil, err - } - } - - return nil, nil -} diff --git a/components/payments/cmd/connectors/internal/api/transfer_initiation.go b/components/payments/cmd/connectors/internal/api/transfer_initiation.go deleted file mode 100644 index 869f825bbe..0000000000 --- a/components/payments/cmd/connectors/internal/api/transfer_initiation.go +++ /dev/null @@ -1,206 +0,0 @@ -package api - -import ( - "encoding/json" - "math/big" - "net/http" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -type transferInitiationResponse struct { - ID string `json:"id"` - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - ScheduledAt time.Time `json:"scheduledAt"` - Description string `json:"description"` - SourceAccountID string `json:"sourceAccountID"` - DestinationAccountID string `json:"destinationAccountID"` - ConnectorID string `json:"connectorID"` - Type string `json:"type"` - Amount *big.Int `json:"amount"` - InitialAmount *big.Int `json:"initialAmount"` - Asset string `json:"asset"` - Status string `json:"status"` - Error string `json:"error"` - Metadata map[string]string `json:"metadata"` -} - -func createTransferInitiationHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "createTransferInitiationHandler") - defer span.End() - - w.Header().Set("Content-Type", "application/json") - - payload := &service.CreateTransferInitiationRequest{} - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - setSpanAttributesFromRequest(span, payload) - - if err := payload.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - tf, err := b.GetService().CreateTransferInitiation(ctx, payload) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - span.SetAttributes( - attribute.String("transfer.id", tf.ID.String()), - attribute.String("transfer.createdAt", tf.CreatedAt.String()), - attribute.String("connectorID", tf.ConnectorID.String()), - ) - - data := &transferInitiationResponse{ - ID: tf.ID.String(), - Reference: tf.ID.Reference, - CreatedAt: tf.CreatedAt, - ScheduledAt: tf.ScheduledAt, - Description: tf.Description, - SourceAccountID: tf.SourceAccountID.String(), - DestinationAccountID: tf.DestinationAccountID.String(), - ConnectorID: tf.ConnectorID.String(), - Type: tf.Type.String(), - Amount: tf.Amount, - InitialAmount: tf.InitialAmount, - Asset: tf.Asset.String(), - Metadata: tf.Metadata, - } - - if len(tf.RelatedAdjustments) > 0 { - // Take the status and error from the last adjustment - data.Status = tf.RelatedAdjustments[0].Status.String() - data.Error = tf.RelatedAdjustments[0].Error - } - - err = json.NewEncoder(w).Encode(api.BaseResponse[transferInitiationResponse]{ - Data: data, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func updateTransferInitiationStatusHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "updateTransferInitiationStatusHandler") - defer span.End() - - payload := &service.UpdateTransferInitiationStatusRequest{} - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - span.SetAttributes(attribute.String("request.status", payload.Status)) - - if err := payload.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - transferID, ok := mux.Vars(r)["transferID"] - if !ok { - otel.RecordError(span, errors.New("missing transferID")) - api.BadRequest(w, ErrInvalidID, errors.New("missing transferID")) - return - } - - span.SetAttributes(attribute.String("transfer.id", transferID)) - - if err := b.GetService().UpdateTransferInitiationStatus(ctx, transferID, payload); err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func retryTransferInitiationHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "retryTransferInitiationHandler") - defer span.End() - - transferID, ok := mux.Vars(r)["transferID"] - if !ok { - otel.RecordError(span, errors.New("missing transferID")) - api.BadRequest(w, ErrInvalidID, errors.New("missing transferID")) - return - } - - span.SetAttributes(attribute.String("transfer.id", transferID)) - - if err := b.GetService().RetryTransferInitiation(ctx, transferID); err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func deleteTransferInitiationHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "deleteTransferInitiationHandler") - defer span.End() - - transferID, ok := mux.Vars(r)["transferID"] - if !ok { - otel.RecordError(span, errors.New("missing transferID")) - api.BadRequest(w, ErrInvalidID, errors.New("missing transferID")) - return - } - - span.SetAttributes(attribute.String("transfer.id", transferID)) - - if err := b.GetService().DeleteTransferInitiation(ctx, transferID); err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - w.WriteHeader(http.StatusNoContent) - } -} - -func setSpanAttributesFromRequest(span trace.Span, transfer *service.CreateTransferInitiationRequest) { - span.SetAttributes( - attribute.String("request.reference", transfer.Reference), - attribute.String("request.scheduledAt", transfer.ScheduledAt.String()), - attribute.String("request.description", transfer.Description), - attribute.String("request.sourceAccountID", transfer.SourceAccountID), - attribute.String("request.destinationAccountID", transfer.DestinationAccountID), - attribute.String("request.connectorID", transfer.ConnectorID), - attribute.String("request.provider", transfer.Provider), - attribute.String("request.type", transfer.Type), - attribute.String("request.amount", transfer.Amount.String()), - attribute.String("request.asset", transfer.Asset), - attribute.String("request.validated", transfer.Asset), - ) -} diff --git a/components/payments/cmd/connectors/internal/api/transfer_initiation_test.go b/components/payments/cmd/connectors/internal/api/transfer_initiation_test.go deleted file mode 100644 index bfd49906d4..0000000000 --- a/components/payments/cmd/connectors/internal/api/transfer_initiation_test.go +++ /dev/null @@ -1,762 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "math/big" - "net/http" - "net/http/httptest" - "testing" - "time" - - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestCreateTransferInitiations(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.CreateTransferInitiationRequest - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - sourceAccountID := models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - - destinationAccountID := models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - } - - testCases := []testCase{ - { - name: "nominal", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "nominal without description", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - Metadata: map[string]string{ - "foo": "bar", - }, - }, - }, - { - name: "missing reference", - req: &service.CreateTransferInitiationRequest{ - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing destination account id", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing source account id, should not end in error", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - }, - { - name: "wrong transfer initiation type", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: "invalid", - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing amount", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Asset: "EUR/2", - Validated: false, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "missing asset", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Validated: false, - }, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "no body", - req: nil, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "service error duplicate key", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: storage.ErrDuplicateKeyValue, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: storage.ErrNotFound, - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: service.ErrValidation, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: service.ErrInvalidID, - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: service.ErrPublish, - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - req: &service.CreateTransferInitiationRequest{ - Reference: "ref1", - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - SourceAccountID: sourceAccountID.String(), - DestinationAccountID: destinationAccountID.String(), - ConnectorID: connectorID.String(), - Provider: models.ConnectorProviderDummyPay.String(), - Type: models.TransferInitiationTypeTransfer.String(), - Amount: big.NewInt(100), - Asset: "EUR/2", - Validated: false, - }, - serviceError: errors.New("some error"), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusOK - } - - createTransferInitiationResponse := models.TransferInitiation{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: "test_nominal", - Type: models.TransferInitiationTypeTransfer, - SourceAccountID: &sourceAccountID, - DestinationAccountID: destinationAccountID, - Provider: models.ConnectorProviderDummyPay, - ConnectorID: connectorID, - Amount: big.NewInt(100), - Asset: "EUR/2", - Metadata: map[string]string{ - "foo": "bar", - }, - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: uuid.New(), - TransferInitiationID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorID, - }, - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Status: models.TransferInitiationStatusProcessing, - }, - }, - } - - expectedCreateTransferInitiationResponse := &transferInitiationResponse{ - ID: models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: connectorID, - }.String(), - Reference: "ref1", - CreatedAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - ScheduledAt: time.Date(2023, 11, 22, 8, 0, 0, 0, time.UTC), - Description: createTransferInitiationResponse.Description, - SourceAccountID: createTransferInitiationResponse.SourceAccountID.String(), - DestinationAccountID: createTransferInitiationResponse.DestinationAccountID.String(), - ConnectorID: createTransferInitiationResponse.ConnectorID.String(), - Type: createTransferInitiationResponse.Type.String(), - Amount: createTransferInitiationResponse.Amount, - Asset: createTransferInitiationResponse.Asset.String(), - Status: models.TransferInitiationStatusProcessing.String(), - Metadata: createTransferInitiationResponse.Metadata, - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - CreateTransferInitiation(gomock.Any(), testCase.req). - Return(&createTransferInitiationResponse, nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - CreateTransferInitiation(gomock.Any(), testCase.req). - Return(nil, testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), nil, false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, "/transfer-initiations", bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - var resp sharedapi.BaseResponse[transferInitiationResponse] - sharedapi.Decode(t, rec.Body, &resp) - require.Equal(t, expectedCreateTransferInitiationResponse, resp.Data) - } else { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestUpdateTransferInitiationStatus(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - req *service.UpdateTransferInitiationStatusRequest - transferID string - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - transferID := models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - transferID: transferID.String(), - }, - { - name: "missing body", - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrMissingOrInvalidBody, - }, - { - name: "service error duplicate key", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: storage.ErrDuplicateKeyValue, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: storage.ErrNotFound, - transferID: transferID.String(), - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: service.ErrValidation, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: service.ErrInvalidID, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: service.ErrPublish, - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - req: &service.UpdateTransferInitiationStatusRequest{ - Status: "VALIDATED", - }, - serviceError: errors.New("some error"), - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - UpdateTransferInitiationStatus(gomock.Any(), testCase.transferID, testCase.req). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - UpdateTransferInitiationStatus(gomock.Any(), testCase.transferID, testCase.req). - Return(testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), nil, false) - - var body []byte - if testCase.req != nil { - var err error - body, err = json.Marshal(testCase.req) - require.NoError(t, err) - } - - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/transfer-initiations/%s/status", testCase.transferID), bytes.NewReader(body)) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestRetryTransferInitiation(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - transferID string - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - transferID := models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - transferID: transferID.String(), - }, - { - name: "service error duplicate key", - serviceError: storage.ErrDuplicateKeyValue, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - serviceError: storage.ErrNotFound, - transferID: transferID.String(), - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - serviceError: service.ErrValidation, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - serviceError: service.ErrInvalidID, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - serviceError: service.ErrPublish, - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - serviceError: errors.New("some error"), - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - RetryTransferInitiation(gomock.Any(), testCase.transferID). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - RetryTransferInitiation(gomock.Any(), testCase.transferID). - Return(testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), nil, false) - - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/transfer-initiations/%s/retry", testCase.transferID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} - -func TestDeleteTransferInitiation(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - transferID string - expectedStatusCode int - expectedErrorCode string - serviceError error - } - - transferID := models.TransferInitiationID{ - Reference: "ref1", - ConnectorID: models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, - } - - testCases := []testCase{ - { - name: "nominal", - transferID: transferID.String(), - }, - { - name: "service error duplicate key", - serviceError: storage.ErrDuplicateKeyValue, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrUniqueReference, - }, - { - name: "service error not found", - serviceError: storage.ErrNotFound, - transferID: transferID.String(), - expectedStatusCode: http.StatusNotFound, - expectedErrorCode: ErrNotFound, - }, - { - name: "service error validation", - serviceError: service.ErrValidation, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrValidation, - }, - { - name: "service error invalid ID", - serviceError: service.ErrInvalidID, - transferID: transferID.String(), - expectedStatusCode: http.StatusBadRequest, - expectedErrorCode: ErrInvalidID, - }, - { - name: "service error publish", - serviceError: service.ErrPublish, - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - { - name: "service error other errors", - serviceError: errors.New("some error"), - transferID: transferID.String(), - expectedStatusCode: http.StatusInternalServerError, - expectedErrorCode: sharedapi.ErrorInternal, - }, - } - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.name, func(t *testing.T) { - t.Parallel() - - if testCase.expectedStatusCode == 0 { - testCase.expectedStatusCode = http.StatusNoContent - } - - backend, mockService := newServiceTestingBackend(t) - if testCase.expectedStatusCode < 300 && testCase.expectedStatusCode >= 200 { - mockService.EXPECT(). - DeleteTransferInitiation(gomock.Any(), testCase.transferID). - Return(nil) - } - if testCase.serviceError != nil { - mockService.EXPECT(). - DeleteTransferInitiation(gomock.Any(), testCase.transferID). - Return(testCase.serviceError) - } - - router := httpRouter(logging.Testing(), backend, sharedapi.ServiceInfo{}, auth.NewNoAuth(), nil, false) - - req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/transfer-initiations/%s", testCase.transferID), nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - require.Equal(t, testCase.expectedStatusCode, rec.Code) - if testCase.expectedStatusCode >= 300 || testCase.expectedStatusCode < 200 { - err := sharedapi.ErrorResponse{} - sharedapi.Decode(t, rec.Body, &err) - require.EqualValues(t, testCase.expectedErrorCode, err.ErrorCode) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/api/transfer_reversal.go b/components/payments/cmd/connectors/internal/api/transfer_reversal.go deleted file mode 100644 index b49abbccd7..0000000000 --- a/components/payments/cmd/connectors/internal/api/transfer_reversal.go +++ /dev/null @@ -1,49 +0,0 @@ -package api - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/payments/cmd/connectors/internal/api/backend" - "github.com/formancehq/payments/cmd/connectors/internal/api/service" - "github.com/formancehq/payments/internal/otel" - "github.com/gorilla/mux" - "github.com/pkg/errors" -) - -func reverseTransferInitiationHandler(b backend.ServiceBackend) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx, span := otel.Tracer().Start(r.Context(), "reverseTransferInitiationHandler") - defer span.End() - - payload := &service.ReverseTransferInitiationRequest{} - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrMissingOrInvalidBody, err) - return - } - - if err := payload.Validate(); err != nil { - otel.RecordError(span, err) - api.BadRequest(w, ErrValidation, err) - return - } - - transferID, ok := mux.Vars(r)["transferID"] - if !ok { - otel.RecordError(span, errors.New("missing transferID")) - api.BadRequest(w, ErrInvalidID, errors.New("missing transferID")) - return - } - - _, err := b.GetService().ReverseTransferInitiation(ctx, transferID, payload) - if err != nil { - otel.RecordError(span, err) - handleServiceErrors(w, r, err) - return - } - - api.NoContent(w) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/adyen/client/accounts.go deleted file mode 100644 index 3d309b5249..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/client/accounts.go +++ /dev/null @@ -1,30 +0,0 @@ -package client - -import ( - "context" - "fmt" - "time" - - "github.com/adyen/adyen-go-api-library/v7/src/management" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -func (c *Client) GetMerchantAccounts(ctx context.Context, pageNumber, pageSize int32) ([]management.Merchant, error) { - f := connectors.ClientMetrics(ctx, "adyen", "list_merchant_accounts") - now := time.Now() - defer f(ctx, now) - - listMerchantsResponse, raw, err := c.client.Management().AccountMerchantLevelApi.ListMerchantAccounts( - ctx, - c.client.Management().AccountMerchantLevelApi.ListMerchantAccountsInput().PageNumber(pageNumber).PageSize(pageSize), - ) - if err != nil { - return nil, err - } - - if raw.StatusCode >= 300 { - return nil, fmt.Errorf("failed to get merchant accounts: %d", raw.StatusCode) - } - - return listMerchantsResponse.Data, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/client/client.go b/components/payments/cmd/connectors/internal/connectors/adyen/client/client.go deleted file mode 100644 index e9237b618f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/client/client.go +++ /dev/null @@ -1,37 +0,0 @@ -package client - -import ( - "github.com/adyen/adyen-go-api-library/v7/src/adyen" - "github.com/adyen/adyen-go-api-library/v7/src/common" - "github.com/formancehq/go-libs/logging" -) - -type Client struct { - client *adyen.APIClient - - HMACKey string - - logger logging.Logger -} - -func NewClient(apiKey, hmacKey, liveEndpointPrefix string, logger logging.Logger) (*Client, error) { - adyenConfig := &common.Config{ - ApiKey: apiKey, - Environment: common.TestEnv, - Debug: true, - } - - if liveEndpointPrefix != "" { - adyenConfig.Environment = common.LiveEnv - adyenConfig.LiveEndpointURLPrefix = liveEndpointPrefix - adyenConfig.Debug = false - } - - client := adyen.NewClient(adyenConfig) - - return &Client{ - client: client, - HMACKey: hmacKey, - logger: logger, - }, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/client/webhooks.go b/components/payments/cmd/connectors/internal/connectors/adyen/client/webhooks.go deleted file mode 100644 index c51b75aaee..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/client/webhooks.go +++ /dev/null @@ -1,7 +0,0 @@ -package client - -import "github.com/adyen/adyen-go-api-library/v7/src/webhook" - -func (c *Client) CreateWebhookForRequest(req string) (*webhook.Webhook, error) { - return webhook.HandleRequest(req) -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/config.go b/components/payments/cmd/connectors/internal/connectors/adyen/config.go deleted file mode 100644 index b854af218f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/config.go +++ /dev/null @@ -1,62 +0,0 @@ -package adyen - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - defaultPollingPeriod = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - HMACKey string `json:"hmacKey" yaml:"hmacKey" bson:"hmacKey"` - LiveEndpointPrefix string `json:"liveEndpointPrefix" yaml:"liveEndpointPrefix" bson:"liveEndpointPrefix"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -func (c Config) String() string { - return fmt.Sprintf("liveEndpointPrefix=%s, apiKey=****, hmacKey=****", c.LiveEndpointPrefix) -} - -func (c Config) Validate() error { - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.Name == "" { - return ErrMissingName - } - - if c.HMACKey == "" { - return ErrMissingHMACKey - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("hmacKey", configtemplate.TypeString, "", true) - cfg.AddParameter("liveEndpointPrefix", configtemplate.TypeString, "", false) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/connector.go b/components/payments/cmd/connectors/internal/connectors/adyen/connector.go deleted file mode 100644 index 353ad192bb..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/connector.go +++ /dev/null @@ -1,112 +0,0 @@ -package adyen - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderAdyen - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch users and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - // Restart the main task to use the new polling period. - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/currencies.go b/components/payments/cmd/connectors/internal/connectors/adyen/currencies.go deleted file mode 100644 index 31e5bef7c1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/currencies.go +++ /dev/null @@ -1,7 +0,0 @@ -package adyen - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - supportedCurrenciesWithDecimal = currency.ISO4217Currencies -) diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/errors.go b/components/payments/cmd/connectors/internal/connectors/adyen/errors.go deleted file mode 100644 index 2b27cc229b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/errors.go +++ /dev/null @@ -1,20 +0,0 @@ -package adyen - -import "errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingAPIKey is returned when the apiKey is missing. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingEndpoint is returned when the endpoint is missing. - ErrMissingLiveEndpointPrefix = errors.New("missing live endpoint prefix from config") - - // ErrMissingName is returned when the name is missing. - ErrMissingName = errors.New("missing name from config") - - // ErrMissingHMACKey is returned when the hmacKey is missing. - ErrMissingHMACKey = errors.New("missing hmacKey from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/loader.go b/components/payments/cmd/connectors/internal/connectors/adyen/loader.go deleted file mode 100644 index ab284974e2..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/loader.go +++ /dev/null @@ -1,54 +0,0 @@ -package adyen - -import ( - "net/http" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // storage is not used in this connector - - r := mux.NewRouter() - - r.Path("/").Methods(http.MethodPost).Handler(handleStandardWebhooks()) - - return r -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/task_fetch_merchants_accounts.go b/components/payments/cmd/connectors/internal/connectors/adyen/task_fetch_merchants_accounts.go deleted file mode 100644 index c96cc11ae8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/task_fetch_merchants_accounts.go +++ /dev/null @@ -1,114 +0,0 @@ -package adyen - -import ( - "context" - "encoding/json" - "time" - - "github.com/adyen/adyen-go-api-library/v7/src/management" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/adyen/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -const ( - pageSize = 100 -) - -func taskFetchAccounts(client *client.Client) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "adyen.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - if err := fetchAccounts(ctx, client, connectorID, ingester, scheduler); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchAccounts( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, -) error { - for page := 1; ; page++ { - pagedAccounts, err := client.GetMerchantAccounts(ctx, int32(page), pageSize) - if err != nil { - return err - } - - if err := ingestAccountsBatch(ctx, connectorID, ingester, pagedAccounts); err != nil { - return err - } - - if len(pagedAccounts) < pageSize { - break - } - } - - return nil -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []management.Merchant, -) error { - if len(accounts) == 0 { - return nil - } - - batch := ingestion.AccountBatch{} - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - a := &models.Account{ - ID: models.AccountID{ - Reference: *account.Id, - ConnectorID: connectorID, - }, - // Moneycorp does not send the opening date of the account - CreatedAt: time.Now(), - Reference: *account.Id, - ConnectorID: connectorID, - Type: models.AccountTypeInternal, - RawData: raw, - } - - if account.Name != nil { - a.AccountName = *account.Name - } - - batch = append(batch, a) - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/task_main.go b/components/payments/cmd/connectors/internal/connectors/adyen/task_main.go deleted file mode 100644 index 54a1e51410..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/task_main.go +++ /dev/null @@ -1,49 +0,0 @@ -package adyen - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "adyen.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/adyen/task_resolve.go deleted file mode 100644 index e95385f26b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/task_resolve.go +++ /dev/null @@ -1,57 +0,0 @@ -package adyen - -import ( - "fmt" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/adyen/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/google/uuid" -) - -const ( - taskNameMain = "main" - taskNameFetchAccounts = "fetch-accounts" - taskNameHandleWebhook = "handle-webhook" -) - -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - PollingPeriod int `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` - WebhookID uuid.UUID `json:"webhookId" yaml:"webhookId" bson:"webhookId"` -} - -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - adyenClient, err := client.NewClient( - config.APIKey, - config.HMACKey, - config.LiveEndpointPrefix, - logger, - ) - if err != nil { - logger.Error(err) - - return func(taskDescriptor TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("cannot build adyen client: %w", err) - } - } - } - - return func(taskDescriptor TaskDescriptor) task.Task { - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchAccounts: - return taskFetchAccounts(adyenClient) - case taskNameHandleWebhook: - return taskHandleStandardWebhooks(adyenClient, taskDescriptor.WebhookID) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/adyen/task_standard_webhooks.go b/components/payments/cmd/connectors/internal/connectors/adyen/task_standard_webhooks.go deleted file mode 100644 index 4f80f50974..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/adyen/task_standard_webhooks.go +++ /dev/null @@ -1,619 +0,0 @@ -package adyen - -import ( - "context" - "encoding/json" - "errors" - "math/big" - "net/http" - "strings" - - "github.com/adyen/adyen-go-api-library/v7/src/hmacvalidator" - "github.com/adyen/adyen-go-api-library/v7/src/webhook" - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/adyen/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -func handleStandardWebhooks() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - connectorContext := task.ConnectorContextFromContext(r.Context()) - webhookID := connectors.WebhookIDFromContext(r.Context()) - span := trace.SpanFromContext(r.Context()) - - // Detach the context since we're launching an async task and we're mostly - // coming from a HTTP request. - detachedCtx, _ := contextutil.Detached(r.Context()) - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "handle webhook", - Key: taskNameHandleWebhook, - WebhookID: webhookID, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - err = connectorContext.Scheduler().Schedule(detachedCtx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte("[accepted]")) - } -} - -func taskHandleStandardWebhooks(client *client.Client, webhookID uuid.UUID) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "adyen.taskHandleStandardWebhooks", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("webhookID", webhookID.String()), - ) - defer span.End() - - w, err := storageReader.GetWebhook(ctx, webhookID) - if err != nil { - otel.RecordError(span, err) - return err - } - - webhooks, err := client.CreateWebhookForRequest(string(w.RequestBody)) - if err != nil { - otel.RecordError(span, err) - return err - } - - for _, item := range *webhooks.NotificationItems { - if !hmacvalidator.ValidateHmac(item.NotificationRequestItem, client.HMACKey) { - // Record error without setting the status to error since we - // continue the execution. - span.RecordError(err) - continue - } - - if err := handleNotificationRequestItem( - ctx, - connectorID, - storageReader, - ingester, - item.NotificationRequestItem, - ); err != nil { - otel.RecordError(span, err) - return err - } - } - - return nil - } -} - -func handleNotificationRequestItem( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - switch item.EventCode { - case webhook.EventCodeAuthorisation: - return handleAuthorisation(ctx, connectorID, ingester, item) - case webhook.EventCodeAuthorisationAdjustment: - return handleAuthorisationAdjustment(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeCancellation: - return handleCancellation(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeCapture: - return handleCapture(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeCaptureFailed: - return handleCaptureFailed(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeRefund: - return handleRefund(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeRefundFailed: - return handleRefundFailed() - case webhook.EventCodeRefundedReversed: - return handleRefundedReversed(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodeRefundWithData: - return handleRefundWithData(ctx, connectorID, storageReader, ingester, item) - case webhook.EventCodePayoutThirdparty: - return handlePayoutThirdparty(ctx, connectorID, ingester, item) - case webhook.EventCodePayoutDecline: - return handlePayoutDecline(ctx, connectorID, ingester, item) - case webhook.EventCodePayoutExpire: - return handlePayoutExpire(ctx, connectorID, ingester, item) - } - - return nil -} - -func handleAuthorisation( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - raw, err := json.Marshal(item) - if err != nil { - return err - } - - status := models.PaymentStatusPending - if item.Success == "false" { - status = models.PaymentStatusFailed - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.PspReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: *item.EventDate, - Reference: item.PspReference, - Amount: big.NewInt(item.Amount.Value), - InitialAmount: big.NewInt(item.Amount.Value), - Type: models.PaymentTypePayIn, - Status: status, - Scheme: parseScheme(item.PaymentMethod), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, item.Amount.Currency), - RawData: raw, - DestinationAccountID: &models.AccountID{ - Reference: item.MerchantAccountCode, - ConnectorID: connectorID, - }, - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - - return nil -} - -func handleAuthorisationAdjustment( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Amount = big.NewInt(item.Amount.Value) - payment.InitialAmount = big.NewInt(item.Amount.Value) - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleCancellation( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Status = models.PaymentStatusCancelled - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleCapture( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Status = models.PaymentStatusSucceeded - payment.Amount = big.NewInt(item.Amount.Value) - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleCaptureFailed( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Status = models.PaymentStatusFailed - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleRefund( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Amount = payment.Amount.Sub(payment.Amount, big.NewInt(item.Amount.Value)) - if payment.Amount.Cmp(big.NewInt(0)) == 0 { - payment.Status = models.PaymentStatusRefunded - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleRefundFailed() error { - // Nothing to do for now (while waiting to enhance the payment adjustment model) - return nil -} - -func handleRefundedReversed( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.PspReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Amount = payment.Amount.Add(payment.Amount, big.NewInt(item.Amount.Value)) - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handleRefundWithData( - ctx context.Context, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success == "true" { - payment, err := storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.OriginalReference, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Amount = payment.Amount.Sub(payment.Amount, big.NewInt(item.Amount.Value)) - if payment.Amount.Cmp(big.NewInt(0)) == 0 { - payment.Status = models.PaymentStatusRefunded - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - } - - return nil -} - -func handlePayoutThirdparty( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - raw, err := json.Marshal(item) - if err != nil { - return err - } - - status := models.PaymentStatusSucceeded - if item.Success == "false" { - status = models.PaymentStatusFailed - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.PspReference, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: *item.EventDate, - Reference: item.PspReference, - Amount: big.NewInt(item.Amount.Value), - InitialAmount: big.NewInt(item.Amount.Value), - Type: models.PaymentTypePayOut, - Status: status, - Scheme: parseScheme(item.PaymentMethod), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, item.Amount.Currency), - RawData: raw, - SourceAccountID: &models.AccountID{ - Reference: item.MerchantAccountCode, - ConnectorID: connectorID, - }, - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - - return nil -} - -func handlePayoutDecline( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success != "true" { - return nil - } - - raw, err := json.Marshal(item) - if err != nil { - return err - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.PspReference, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: *item.EventDate, - Reference: item.PspReference, - Amount: big.NewInt(item.Amount.Value), - InitialAmount: big.NewInt(item.Amount.Value), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusCancelled, - Scheme: parseScheme(item.PaymentMethod), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, item.Amount.Currency), - RawData: raw, - SourceAccountID: &models.AccountID{ - Reference: item.MerchantAccountCode, - ConnectorID: connectorID, - }, - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - - return nil -} - -func handlePayoutExpire( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - item webhook.NotificationRequestItem, -) error { - if item.Success != "true" { - return nil - } - - raw, err := json.Marshal(item) - if err != nil { - return err - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: item.PspReference, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: *item.EventDate, - Reference: item.PspReference, - Amount: big.NewInt(item.Amount.Value), - InitialAmount: big.NewInt(item.Amount.Value), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusExpired, - Scheme: parseScheme(item.PaymentMethod), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, item.Amount.Currency), - RawData: raw, - SourceAccountID: &models.AccountID{ - Reference: item.MerchantAccountCode, - ConnectorID: connectorID, - }, - } - - if err := ingester.IngestPayments( - ctx, - ingestion.PaymentBatch{{Payment: payment}}, - ); err != nil { - return err - } - - return nil -} - -func parseScheme(scheme string) models.PaymentScheme { - switch { - case strings.HasPrefix(scheme, "visa"): - return models.PaymentSchemeCardVisa - case strings.HasPrefix(scheme, "electron"): - return models.PaymentSchemeCardVisa - case strings.HasPrefix(scheme, "amex"): - return models.PaymentSchemeCardAmex - case strings.HasPrefix(scheme, "alipay"): - return models.PaymentSchemeCardAlipay - case strings.HasPrefix(scheme, "cup"): - return models.PaymentSchemeCardCUP - case strings.HasPrefix(scheme, "discover"): - return models.PaymentSchemeCardDiscover - case strings.HasPrefix(scheme, "doku"): - return models.PaymentSchemeDOKU - case strings.HasPrefix(scheme, "dragonpay"): - return models.PaymentSchemeDragonPay - case strings.HasPrefix(scheme, "jcb"): - return models.PaymentSchemeCardJCB - case strings.HasPrefix(scheme, "maestro"): - return models.PaymentSchemeMaestro - case strings.HasPrefix(scheme, "mc"): - return models.PaymentSchemeCardMasterCard - case strings.HasPrefix(scheme, "molpay"): - return models.PaymentSchemeMolPay - case strings.HasPrefix(scheme, "diners"): - return models.PaymentSchemeCardDiners - default: - return models.PaymentSchemeUnknown - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/Insomnium.json b/components/payments/cmd/connectors/internal/connectors/atlar/Insomnium.json deleted file mode 100644 index 65c54cc39b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/Insomnium.json +++ /dev/null @@ -1 +0,0 @@ -{"_type":"export","__export_format":4,"__export_date":"2023-11-28T15:46:25.856Z","__export_source":"insomnia.desktop.app:v0.2.3","resources":[{"_id":"req_e9545a9d7ffd4e44b982e2ddb8b15e83","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756567874,"created":1700665329960,"url":"{{ _.baseUrlApi }}/_info","name":"Info","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700665329960,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"wrk_9ca49e64d481426cb9e1831e73b552ba","parentId":null,"modified":1700663660087,"created":1700663635161,"name":"Local Formance Payments Atlar","description":"","scope":"collection","_type":"workspace"},{"_id":"req_05b0176074ef4c37a7a1be0926635e79","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700732400128,"created":1700732368940,"url":"{{ _.baseUrl }}/connectors/configs","name":"List the configs of each available connector","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700664496984.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_545f3ec751574e399e31b9ff899cf77d","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756760593,"created":1700755772447,"url":"{{ _.baseUrlApi }}/accounts","name":"List accounts","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700664392862.5625,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_c133874ab7094fcf85ecdc255b3f138b","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1701094404853,"created":1700755638397,"url":"{{ _.baseUrlApi }}/payments","name":"List payments","description":"","method":"GET","body":{},"parameters":[{"id":"pair_629ff5e5c1be4c54a90b8664ea656b0a","name":"","value":"","description":""}],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700664288740.625,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_6088705ca6e44f9693b07dea3d0aad11","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756584625,"created":1700745901467,"url":"{{ _.baseUrlConnectors }}/connectors","name":"List all installed connectors","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700664080496.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_4da5e386b56141889cb7769e8846f3cd","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756591790,"created":1700746001449,"url":"{{ _.baseUrlConnectors }}/connectors/configs","name":"List the configs of each available connector","description":"","method":"GET","body":{},"parameters":[],"headers":[{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700663872252.875,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_a20a27621407489cb8998b4e3238af6e","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756596678,"created":1700663664009,"url":"{{ _.baseUrlConnectors }}/connectors/atlar","name":"Install Atlar Connector","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Test\",\n\t\"pollingPeriod\": \"10s\",\n\t\"baseUrl\": \"https://api.atlar.com\",\n\t\"accessKey\": \"{{ _.atlar_accessKey }}\",\n\t\"secret\": \"{{ _.atlar_secret }}\"\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700663664009,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[],"_type":"request"},{"_id":"req_a678810335464e0bba10f57ee3bc9f95","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756602079,"created":1700743647460,"url":"{{ _.baseUrlConnectors }}/connectors/atlar/:connectorID/reset","name":"Reset Atlar Connector","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Test\",\n\t\"pollingPeriod\": \"10s\",\n\t\"baseUrl\": \"https://api.atlar.com\",\n\t\"accessKey\": \"{{ _.atlar_accessKey }}\",\n\t\"secret\": \"{{ _.atlar_secret }}\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700534131609,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[{"name":"connectorID","value":"{% response 'body', 'req_a20a27621407489cb8998b4e3238af6e', 'b64::JC5kYXRhLmNvbm5lY3RvcklE::46b', 'never', 60 %}","disabled":false,"id":"pair_519cdcc16fad41d8afccf27bd428eb3aending0","fileName":""}],"_type":"request"},{"_id":"req_4419fe631b61412d9ecbb80ea3ecd29c","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756607072,"created":1700736368735,"url":"{{ _.baseUrlConnectors }}/connectors/atlar/:connectorID","name":"Uninstall Atlar Connector","description":"","method":"DELETE","body":{"mimeType":"application/json","text":""},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700501748509,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[{"name":"connectorID","value":"{% response 'body', 'req_a20a27621407489cb8998b4e3238af6e', 'b64::JC5kYXRhLmNvbm5lY3RvcklE::46b', 'never', 60 %}","disabled":false,"id":"pair_519cdcc16fad41d8afccf27bd428eb3aending0","fileName":""}],"_type":"request"},{"_id":"req_e8c035af9b6e48fda8410b089764c8f6","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756613381,"created":1700747923012,"url":"{{ _.baseUrlConnectors }}/connectors/atlar/:connectorID/tasks","name":"List tasks from a connector","description":"","method":"GET","body":{"mimeType":"application/json","text":""},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"User-Agent","value":"insomnium/0.2.3"}],"authentication":{},"metaSortKey":-1700469365409,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","segmentParams":[{"name":"connectorID","value":"{% response 'body', 'req_a20a27621407489cb8998b4e3238af6e', 'b64::JC5kYXRhLmNvbm5lY3RvcklE::46b', 'never', 60 %}","disabled":false,"id":"pair_519cdcc16fad41d8afccf27bd428eb3aending0","fileName":""}],"_type":"request"},{"_id":"env_90638f53ef3039b5d60b8dfc5744952b4e2de8c7","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700756537786,"created":1700663635163,"name":"Base Environment","data":{"baseUrlApi":"http://localhost:8080","baseUrlConnectors":"http://localhost:8081","atlar_accessKey":"","atlar_secret":""},"dataPropertyOrder":{"&":["baseUrlApi","baseUrlConnectors","atlar_accessKey","atlar_secret"]},"color":null,"isPrivate":false,"metaSortKey":1700663635163,"_type":"environment"},{"_id":"jar_90638f53ef3039b5d60b8dfc5744952b4e2de8c7","parentId":"wrk_9ca49e64d481426cb9e1831e73b552ba","modified":1700663635163,"created":1700663635163,"name":"Default Jar","cookies":[],"_type":"cookie_jar"}]} \ No newline at end of file diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/account_utils.go b/components/payments/cmd/connectors/internal/connectors/atlar/account_utils.go deleted file mode 100644 index 82e2fbefd6..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/account_utils.go +++ /dev/null @@ -1,93 +0,0 @@ -package atlar - -import ( - "encoding/json" - "fmt" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/payments/internal/models" - atlar_models "github.com/get-momo/atlar-v1-go-client/models" -) - -type AtlarExternalAccountAndCounterparty struct { - ExternalAccount atlar_models.ExternalAccount `json:"externalAccount" yaml:"externalAccount" bson:"externalAccount"` - Counterparty atlar_models.Counterparty `json:"counterparty" yaml:"counterparty" bson:"counterparty"` -} - -func ExternalAccountFromAtlarData( - connectorID models.ConnectorID, - externalAccount *atlar_models.ExternalAccount, - counterparty *atlar_models.Counterparty, -) (*models.Account, error) { - raw, err := json.Marshal(AtlarExternalAccountAndCounterparty{ExternalAccount: *externalAccount, Counterparty: *counterparty}) - if err != nil { - return nil, err - } - - createdAt, err := ParseAtlarTimestamp(externalAccount.Created) - if err != nil { - return nil, fmt.Errorf("failed to parse opening date: %w", err) - } - - return &models.Account{ - ID: models.AccountID{ - Reference: externalAccount.ID, - ConnectorID: connectorID, - }, - CreatedAt: createdAt, - Reference: externalAccount.ID, - ConnectorID: connectorID, - // DefaultAsset: left empty because the information is not provided by Atlar, - AccountName: counterparty.Name, // TODO: is that okay? External accounts do not have a name at Atlar. - Type: models.AccountTypeExternal, - Metadata: extractExternalAccountAndCounterpartyMetadata(externalAccount, counterparty), - RawData: raw, - }, nil -} - -func ExtractAccountMetadata(account *atlar_models.Account, bank *atlar_models.ThirdParty) metadata.Metadata { - result := metadata.Metadata{} - result = result.Merge(ComputeAccountMetadataBool("fictive", account.Fictive)) - result = result.Merge(ComputeAccountMetadata("bank/id", bank.ID)) - result = result.Merge(ComputeAccountMetadata("bank/name", bank.Name)) - result = result.Merge(ComputeAccountMetadata("bank/bic", account.Bank.Bic)) - result = result.Merge(IdentifiersToMetadata(account.Identifiers)) - result = result.Merge(ComputeAccountMetadata("alias", account.Alias)) - result = result.Merge(ComputeAccountMetadata("owner/name", account.Owner.Name)) - return result -} - -func IdentifiersToMetadata(identifiers []*atlar_models.AccountIdentifier) metadata.Metadata { - result := metadata.Metadata{} - for _, i := range identifiers { - result = result.Merge(ComputeAccountMetadata( - fmt.Sprintf("identifier/%s/%s", *i.Market, *i.Type), - *i.Number, - )) - if *i.Type == "IBAN" { - result = result.Merge(ComputeAccountMetadata( - fmt.Sprintf("identifier/%s", *i.Type), - *i.Number, - )) - } - } - return result -} - -func extractExternalAccountAndCounterpartyMetadata(externalAccount *atlar_models.ExternalAccount, counterparty *atlar_models.Counterparty) metadata.Metadata { - result := metadata.Metadata{} - result = result.Merge(ComputeAccountMetadata("bank/id", externalAccount.Bank.ID)) - result = result.Merge(ComputeAccountMetadata("bank/name", externalAccount.Bank.Name)) - result = result.Merge(ComputeAccountMetadata("bank/bic", externalAccount.Bank.Bic)) - result = result.Merge(IdentifiersToMetadata(externalAccount.Identifiers)) - result = result.Merge(ComputeAccountMetadata("owner/name", counterparty.Name)) - result = result.Merge(ComputeAccountMetadata("owner/type", counterparty.PartyType)) - result = result.Merge(ComputeAccountMetadata("owner/contact/email", counterparty.ContactDetails.Email)) - result = result.Merge(ComputeAccountMetadata("owner/contact/phone", counterparty.ContactDetails.Phone)) - result = result.Merge(ComputeAccountMetadata("owner/contact/address/streetName", counterparty.ContactDetails.Address.StreetName)) - result = result.Merge(ComputeAccountMetadata("owner/contact/address/streetNumber", counterparty.ContactDetails.Address.StreetNumber)) - result = result.Merge(ComputeAccountMetadata("owner/contact/address/city", counterparty.ContactDetails.Address.City)) - result = result.Merge(ComputeAccountMetadata("owner/contact/address/postalCode", counterparty.ContactDetails.Address.PostalCode)) - result = result.Merge(ComputeAccountMetadata("owner/contact/address/country", counterparty.ContactDetails.Address.Country)) - return result -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/accounts.go deleted file mode 100644 index 07ee30f461..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/accounts.go +++ /dev/null @@ -1,36 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/get-momo/atlar-v1-go-client/client/accounts" -) - -func (c *Client) GetV1AccountsID(ctx context.Context, id string) (*accounts.GetV1AccountsIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_accounts") - now := time.Now() - defer f(ctx, now) - - accountsParams := accounts.GetV1AccountsIDParams{ - Context: ctx, - ID: id, - } - - return c.client.Accounts.GetV1AccountsID(&accountsParams) -} - -func (c *Client) GetV1Accounts(ctx context.Context, token string, pageSize int64) (*accounts.GetV1AccountsOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_accounts") - now := time.Now() - defer f(ctx, now) - - accountsParams := accounts.GetV1AccountsParams{ - Limit: &pageSize, - Context: ctx, - Token: &token, - } - - return c.client.Accounts.GetV1Accounts(&accountsParams) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/client.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/client.go deleted file mode 100644 index 887a33219f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/client.go +++ /dev/null @@ -1,33 +0,0 @@ -package client - -import ( - "net/url" - - "github.com/go-openapi/strfmt" - - atlar_client "github.com/get-momo/atlar-v1-go-client/client" - - httptransport "github.com/go-openapi/runtime/client" -) - -type Client struct { - client *atlar_client.Rest -} - -func NewClient(baseURL url.URL, accessKey, secret string) *Client { - return &Client{ - client: createAtlarClient(baseURL, accessKey, secret), - } -} - -func createAtlarClient(baseURL url.URL, accessKey, secret string) *atlar_client.Rest { - transport := httptransport.New( - baseURL.Host, - baseURL.Path, - []string{baseURL.Scheme}, - ) - basicAuth := httptransport.BasicAuth(accessKey, secret) - transport.DefaultAuthentication = basicAuth - client := atlar_client.New(transport, strfmt.Default) - return client -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/counter_parties.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/counter_parties.go deleted file mode 100644 index 0ac1b199a8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/counter_parties.go +++ /dev/null @@ -1,110 +0,0 @@ -package client - -import ( - "context" - "errors" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/internal/models" - "github.com/get-momo/atlar-v1-go-client/client/counterparties" - atlar_models "github.com/get-momo/atlar-v1-go-client/models" -) - -func (c *Client) GetV1CounterpartiesID(ctx context.Context, counterPartyID string) (*counterparties.GetV1CounterpartiesIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "get_counterparty") - now := time.Now() - defer f(ctx, now) - - getCounterpartyParams := counterparties.GetV1CounterpartiesIDParams{ - Context: ctx, - ID: counterPartyID, - } - counterpartyResponse, err := c.client.Counterparties.GetV1CounterpartiesID(&getCounterpartyParams) - if err != nil { - return nil, err - } - - return counterpartyResponse, nil -} - -func (c *Client) CreateCounterParty(ctx context.Context, newExternalBankAccount *models.BankAccount) (*string, error) { - f := connectors.ClientMetrics(ctx, "atlar", "create_counterparty") - now := time.Now() - defer f(ctx, now) - - // TODO: make sure an account with that IBAN does not already exist (Atlar API v2 needed, v1 lacks the filters) - // alternatively we could query the local DB - - createCounterpartyRequest := atlar_models.CreateCounterpartyRequest{ - Name: ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/name"), - PartyType: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/type"), - ContactDetails: &atlar_models.ContactDetails{ - Email: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/email"), - Phone: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/phone"), - Address: &atlar_models.Address{ - StreetName: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/address/streetName"), - StreetNumber: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/address/streetNumber"), - City: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/address/city"), - PostalCode: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/address/postalCode"), - Country: *ExtractNamespacedMetadataIgnoreEmpty(newExternalBankAccount.Metadata, "owner/contact/address/country"), - }, - }, - ExternalAccounts: []*atlar_models.CreateEmbeddedExternalAccountRequest{ - { - // ExternalID could cause problems when synchronizing with Accounts[type=external] - Bank: &atlar_models.UpdatableBank{ - Bic: newExternalBankAccount.SwiftBicCode, - }, - Identifiers: extractAtlarAccountIdentifiersFromBankAccount(newExternalBankAccount), - }, - }, - } - postCounterpartiesParams := counterparties.PostV1CounterpartiesParams{ - Context: ctx, - Counterparty: &createCounterpartyRequest, - } - postCounterpartiesResponse, err := c.client.Counterparties.PostV1Counterparties(&postCounterpartiesParams) - if err != nil { - return nil, err - } - - if len(postCounterpartiesResponse.Payload.ExternalAccounts) != 1 { - // should never occur, but when in case it happens it's nice to have an error to search for - return nil, errors.New("counterparty was not created with exactly one account") - } - - externalAccountID := postCounterpartiesResponse.Payload.ExternalAccounts[0].ID - - return &externalAccountID, nil -} - -func extractAtlarAccountIdentifiersFromBankAccount(bankAccount *models.BankAccount) []*atlar_models.AccountIdentifier { - ownerName := bankAccount.Metadata[atlarMetadataSpecNamespace+"owner/name"] - ibanType := "IBAN" - accountIdentifiers := []*atlar_models.AccountIdentifier{{ - HolderName: &ownerName, - Market: &bankAccount.Country, - Type: &ibanType, - Number: &bankAccount.IBAN, - }} - for k := range bankAccount.Metadata { - // check whether the key has format com.atlar.spec/identifier// - identifierData, err := metadataToIdentifierData(k, bankAccount.Metadata[k]) - if err != nil { - // matadata does not describe an identifier - continue - } - if identifierData.Market == bankAccount.Country && identifierData.Type == "IBAN" { - // avoid duplicate identifiers - continue - } - accountIdentifiers = append(accountIdentifiers, &atlar_models.AccountIdentifier{ - HolderName: &ownerName, - Market: &identifierData.Market, - Type: &identifierData.Type, - Number: &identifierData.Number, - }) - } - return accountIdentifiers -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/external_accounts.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/external_accounts.go deleted file mode 100644 index fab92c05ab..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/external_accounts.go +++ /dev/null @@ -1,41 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/get-momo/atlar-v1-go-client/client/external_accounts" -) - -func (c *Client) GetV1ExternalAccountsID(ctx context.Context, externalAccountID string) (*external_accounts.GetV1ExternalAccountsIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "get_external_account") - now := time.Now() - defer f(ctx, now) - - getExternalAccountParams := external_accounts.GetV1ExternalAccountsIDParams{ - Context: ctx, - ID: externalAccountID, - } - - externalAccountResponse, err := c.client.ExternalAccounts.GetV1ExternalAccountsID(&getExternalAccountParams) - if err != nil { - return nil, err - } - - return externalAccountResponse, nil -} - -func (c *Client) GetV1ExternalAccounts(ctx context.Context, token string, pageSize int64) (*external_accounts.GetV1ExternalAccountsOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_external_accounts") - now := time.Now() - defer f(ctx, now) - - externalAccountsParams := external_accounts.GetV1ExternalAccountsParams{ - Limit: &pageSize, - Context: ctx, - Token: &token, - } - - return c.client.ExternalAccounts.GetV1ExternalAccounts(&externalAccountsParams) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/third_parties.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/third_parties.go deleted file mode 100644 index 8a04ce0213..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/third_parties.go +++ /dev/null @@ -1,22 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/get-momo/atlar-v1-go-client/client/third_parties" -) - -func (c *Client) GetV1BetaThirdPartiesID(ctx context.Context, id string) (*third_parties.GetV1betaThirdPartiesIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_third_parties") - now := time.Now() - defer f(ctx, now) - - params := third_parties.GetV1betaThirdPartiesIDParams{ - Context: ctx, - ID: id, - } - - return c.client.ThirdParties.GetV1betaThirdPartiesID(¶ms) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/transactions.go deleted file mode 100644 index 733d6c1fb6..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/transactions.go +++ /dev/null @@ -1,36 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/get-momo/atlar-v1-go-client/client/transactions" -) - -func (c *Client) GetV1Transactions(ctx context.Context, token string, pageSize int64) (*transactions.GetV1TransactionsOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_transactions") - now := time.Now() - defer f(ctx, now) - - params := transactions.GetV1TransactionsParams{ - Limit: &pageSize, - Context: ctx, - Token: &token, - } - - return c.client.Transactions.GetV1Transactions(¶ms) -} - -func (c *Client) GetV1TransactionsID(ctx context.Context, id string) (*transactions.GetV1TransactionsIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "list_transactions") - now := time.Now() - defer f(ctx, now) - - params := transactions.GetV1TransactionsIDParams{ - Context: ctx, - ID: id, - } - - return c.client.Transactions.GetV1TransactionsID(¶ms) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/transfers.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/transfers.go deleted file mode 100644 index 5aa3f92489..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/transfers.go +++ /dev/null @@ -1,37 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/get-momo/atlar-v1-go-client/client/credit_transfers" - atlar_models "github.com/get-momo/atlar-v1-go-client/models" -) - -func (c *Client) PostV1CreditTransfers(ctx context.Context, req *atlar_models.CreatePaymentRequest) (*credit_transfers.PostV1CreditTransfersCreated, error) { - f := connectors.ClientMetrics(ctx, "atlar", "create_credit_transfer") - now := time.Now() - defer f(ctx, now) - - postCreditTransfersParams := credit_transfers.PostV1CreditTransfersParams{ - Context: ctx, - CreditTransfer: req, - } - - return c.client.CreditTransfers.PostV1CreditTransfers(&postCreditTransfersParams) - -} - -func (c *Client) GetV1CreditTransfersGetByExternalIDExternalID(ctx context.Context, externalID string) (*credit_transfers.GetV1CreditTransfersGetByExternalIDExternalIDOK, error) { - f := connectors.ClientMetrics(ctx, "atlar", "get_credit_transfer") - now := time.Now() - defer f(ctx, now) - - getCreditTransferParams := credit_transfers.GetV1CreditTransfersGetByExternalIDExternalIDParams{ - Context: ctx, - ExternalID: externalID, - } - - return c.client.CreditTransfers.GetV1CreditTransfersGetByExternalIDExternalID(&getCreditTransferParams) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/utils.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/utils.go deleted file mode 100644 index 761b572aa5..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/utils.go +++ /dev/null @@ -1,38 +0,0 @@ -package client - -import ( - "errors" - "regexp" -) - -const ( - atlarMetadataSpecNamespace = "com.atlar.spec/" -) - -func ExtractNamespacedMetadataIgnoreEmpty(metadata map[string]string, key string) *string { - value := metadata[atlarMetadataSpecNamespace+key] - return &value -} - -type IdentifierData struct { - Market string - Type string - Number string -} - -var identifierMetadataRegex = regexp.MustCompile(`^com\.atlar\.spec/identifier/([^/]+)/([^/]+)$`) - -func metadataToIdentifierData(key, value string) (*IdentifierData, error) { - // Find matches in the input string - matches := identifierMetadataRegex.FindStringSubmatch(key) - if matches == nil { - return nil, errors.New("input does not match the expected format") - } - - // Extract values from the matched groups - return &IdentifierData{ - Market: matches[1], - Type: matches[2], - Number: value, - }, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/client/utils_test.go b/components/payments/cmd/connectors/internal/connectors/atlar/client/utils_test.go deleted file mode 100644 index a5b73791ad..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/client/utils_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package client - -import ( - "errors" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestMetadataToIdentifierData(t *testing.T) { - t.Parallel() - - _, err := metadataToIdentifierData("not_valid", "test") - if assert.Error(t, err) { - assert.Equal(t, errors.New("input does not match the expected format"), err) - } - _, err = metadataToIdentifierData(atlarMetadataSpecNamespace+"not_valid", "test") - if assert.Error(t, err) { - assert.Equal(t, errors.New("input does not match the expected format"), err) - } - _, err = metadataToIdentifierData(atlarMetadataSpecNamespace+"identifier/not_valid", "test") - if assert.Error(t, err) { - assert.Equal(t, errors.New("input does not match the expected format"), err) - } - identifier, err := metadataToIdentifierData(atlarMetadataSpecNamespace+"identifier/DE/IBAN", "DE02700100800030876808") - if assert.Nil(t, err) { - assert.Equal(t, IdentifierData{ - Market: "DE", - Type: "IBAN", - Number: "DE02700100800030876808", - }, *identifier) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/config.go b/components/payments/cmd/connectors/internal/connectors/atlar/config.go deleted file mode 100644 index 7f9ea4c79e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/config.go +++ /dev/null @@ -1,113 +0,0 @@ -package atlar - -import ( - "encoding/json" - "errors" - "fmt" - "net/url" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -var ( - //"https://api.atlar.com" - defaultURLValue = url.URL{ - Scheme: "https", - Host: "api.atlar.com", - } - defaultPollingPeriod = 2 * time.Minute - defaultPageSize uint64 = 25 -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` - TransferInitiationStatusPollingPeriod connectors.Duration `json:"transferInitiationStatusPollingPeriod" yaml:"transferInitiationStatusPollingPeriod" bson:"transferInitiationStatusPollingPeriod"` - BaseUrl url.URL `json:"-" yaml:"-" bson:"-"` // Already marshalled as string in the MarshalJson function - AccessKey string `json:"accessKey" yaml:"accessKey" bson:"accessKey"` - Secret string `json:"secret" yaml:"secret" bson:"secret"` - ApiConfig `bson:",inline"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return fmt.Sprintf("baseUrl=%s, pollingPeriod=%s, transferInitiationStatusPollingPeriod=%s, pageSize=%d, accessKey=%s, secret=****", - c.BaseUrl.String(), c.PollingPeriod, c.TransferInitiationStatusPollingPeriod, c.PageSize, c.AccessKey) -} - -func (c Config) Validate() error { - if c.AccessKey == "" { - return errors.New("missing api access key") - } - - if c.Secret == "" { - return errors.New("missing api secret") - } - - return nil -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) Marshal() ([]byte, error) { - type CopyType Config - - basicConfig := struct { - BaseUrl string `json:"baseUrl"` - CopyType - }{ - BaseUrl: c.BaseUrl.String(), - CopyType: (CopyType)(c), - } - - return json.Marshal(basicConfig) -} - -func (c *Config) UnmarshalJSON(data []byte) error { - type CopyType Config - - tmp := struct { - BaseUrl string `json:"baseUrl"` - *CopyType - }{ - CopyType: (*CopyType)(c), - } - - err := json.Unmarshal(data, &tmp) - if err != nil { - return err - } - - baseUrl, err := url.Parse(tmp.BaseUrl) - if err != nil { - return err - } - c.BaseUrl = *baseUrl - - return nil -} - -type ApiConfig struct { - PageSize uint64 `json:"pageSize" yaml:"pageSize" bson:"pageSize"` -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("baseUrl", configtemplate.TypeString, defaultURLValue.String(), false) - cfg.AddParameter("accessKey", configtemplate.TypeString, "", true) - cfg.AddParameter("secret", configtemplate.TypeString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - cfg.AddParameter("transferInitiationStatusPollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - cfg.AddParameter("pageSize", configtemplate.TypeDurationUnsignedInteger, strconv.Itoa(int(defaultPageSize)), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/connector.go b/components/payments/cmd/connectors/internal/connectors/atlar/connector.go deleted file mode 100644 index 056131c413..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/connector.go +++ /dev/null @@ -1,151 +0,0 @@ -package atlar - -import ( - "context" - "errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderAtlar - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch transactions", - Main: true, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - err := ValidateTransferInitiation(transfer) - if err != nil { - return err - } - - descriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - if err := ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - descriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Create external bank account", - Key: taskNameCreateExternalBankAccount, - BankAccount: bankAccount, - }) - if err != nil { - return err - } - if err := ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - }); err != nil { - return err - } - - // TODO: it might make sense to return the external account ID so the client can use it for initiating a payment - return nil -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/currencies.go b/components/payments/cmd/connectors/internal/connectors/atlar/currencies.go deleted file mode 100644 index 9f6470890c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/currencies.go +++ /dev/null @@ -1,10 +0,0 @@ -package atlar - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - supportedCurrenciesWithDecimal = map[string]int{ - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "DKK": currency.ISO4217Currencies["DKK"], - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/loader.go b/components/payments/cmd/connectors/internal/connectors/atlar/loader.go deleted file mode 100644 index 77be83a7f9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/loader.go +++ /dev/null @@ -1,63 +0,0 @@ -package atlar - -import ( - "net/url" - - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - emptyUrl := url.URL{} - if cfg.BaseUrl == emptyUrl { - //"https://api.atlar.com" - cfg.BaseUrl = defaultURLValue - } - - if cfg.PageSize == 0 { - cfg.PageSize = defaultPageSize - } - - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod = connectors.Duration{Duration: defaultPollingPeriod} - } - - if cfg.TransferInitiationStatusPollingPeriod.Duration == 0 { - cfg.TransferInitiationStatusPollingPeriod = connectors.Duration{Duration: defaultPollingPeriod} - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/loader_test.go b/components/payments/cmd/connectors/internal/connectors/atlar/loader_test.go deleted file mode 100644 index 144dd323c3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/loader_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package atlar - -import ( - "context" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/stretchr/testify/assert" -) - -// TestLoader tests the loader. -func TestLoader(t *testing.T) { - t.Parallel() - - config := Config{} - logger := logging.FromContext(context.TODO()) - - loader := NewLoader() - - assert.Equal(t, name, loader.Name()) - assert.Equal(t, 50, loader.AllowTasks()) - - baseUrl, err := url.Parse("https://api.atlar.com") - assert.Nil(t, err) - - assert.Equal(t, Config{ - Name: "ATLAR", - BaseUrl: *baseUrl, - PollingPeriod: connectors.Duration{Duration: 2 * time.Minute}, - TransferInitiationStatusPollingPeriod: connectors.Duration{Duration: 2 * time.Minute}, - ApiConfig: ApiConfig{PageSize: 25}, - }, loader.ApplyDefaults(config)) - - assert.EqualValues(t, newConnector(logger, config), loader.Load(logger, config)) -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/metadata.go b/components/payments/cmd/connectors/internal/connectors/atlar/metadata.go deleted file mode 100644 index cd1a8398d7..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/metadata.go +++ /dev/null @@ -1,56 +0,0 @@ -package atlar - -import ( - "fmt" - "time" - - "github.com/formancehq/go-libs/metadata" - "github.com/formancehq/payments/internal/models" -) - -const ( - atlarMetadataSpecNamespace = "com.atlar.spec/" - valueTRUE = "TRUE" - valueFALSE = "FALSE" -) - -func ComputeAccountMetadata(key, value string) metadata.Metadata { - namespacedKey := fmt.Sprintf("%s%s", atlarMetadataSpecNamespace, key) - return metadata.Metadata{ - namespacedKey: value, - } -} - -func ComputeAccountMetadataBool(key string, value bool) metadata.Metadata { - computedValue := valueFALSE - if value { - computedValue = valueTRUE - } - return ComputeAccountMetadata(key, computedValue) -} - -func ComputePaymentMetadata(paymentId models.PaymentID, key, value string) *models.PaymentMetadata { - namespacedKey := fmt.Sprintf("%s%s", atlarMetadataSpecNamespace, key) - return &models.PaymentMetadata{ - PaymentID: paymentId, - CreatedAt: time.Now(), - Key: namespacedKey, - Value: value, - } -} - -func ComputePaymentMetadataBool(paymentId models.PaymentID, key string, value bool) *models.PaymentMetadata { - computedValue := valueFALSE - if value { - computedValue = valueTRUE - } - return ComputePaymentMetadata(paymentId, key, computedValue) -} - -func ExtractNamespacedMetadata(metadata map[string]string, key string) (*string, error) { - value, ok := metadata[atlarMetadataSpecNamespace+key] - if !ok { - return nil, fmt.Errorf("unable to find metadata with key %s%s", atlarMetadataSpecNamespace, key) - } - return &value, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_create_external_bank_account.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_create_external_bank_account.go deleted file mode 100644 index 1570ba7f60..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_create_external_bank_account.go +++ /dev/null @@ -1,126 +0,0 @@ -package atlar - -import ( - "context" - "errors" - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func CreateExternalBankAccountTask(config Config, client *client.Client, newExternalBankAccount *models.BankAccount) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskCreateExternalBankAccount", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("bankAccount.name", newExternalBankAccount.Name), - attribute.String("bankAccount.id", newExternalBankAccount.ID.String()), - ) - defer span.End() - - err := validateExternalBankAccount(newExternalBankAccount) - if err != nil { - otel.RecordError(span, err) - return err - } - - externalAccountID, err := createExternalBankAccount(ctx, client, newExternalBankAccount) - if err != nil { - otel.RecordError(span, err) - return err - } - if externalAccountID == nil { - err := errors.New("no external account id returned") - otel.RecordError(span, err) - return err - } - - err = ingestExternalAccountFromAtlar( - ctx, - connectorID, - ingester, - client, - newExternalBankAccount, - *externalAccountID, - ) - if err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -// TODO: validation (also metadata) needs to return a 400 -func validateExternalBankAccount(newExternalBankAccount *models.BankAccount) error { - _, err := ExtractNamespacedMetadata(newExternalBankAccount.Metadata, "owner/name") - if err != nil { - return fmt.Errorf("required metadata field %sowner/name is missing", atlarMetadataSpecNamespace) - } - ownerType, err := ExtractNamespacedMetadata(newExternalBankAccount.Metadata, "owner/type") - if err != nil { - return fmt.Errorf("required metadata field %sowner/type is missing", atlarMetadataSpecNamespace) - } - if *ownerType != "INDIVIDUAL" && *ownerType != "COMPANY" { - return fmt.Errorf("metadata field %sowner/type needs to be one of [ INDIVIDUAL COMPANY ]", atlarMetadataSpecNamespace) - } - - return nil -} - -func createExternalBankAccount(ctx context.Context, client *client.Client, newExternalBankAccount *models.BankAccount) (*string, error) { - return client.CreateCounterParty(ctx, newExternalBankAccount) -} - -func ingestExternalAccountFromAtlar( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - client *client.Client, - formanceBankAccount *models.BankAccount, - externalAccountID string, -) error { - accountsBatch := ingestion.AccountBatch{} - - externalAccountResponse, err := client.GetV1ExternalAccountsID(ctx, externalAccountID) - if err != nil { - return err - } - - counterpartyResponse, err := client.GetV1CounterpartiesID(ctx, externalAccountResponse.Payload.CounterpartyID) - if err != nil { - return err - } - - newAccount, err := ExternalAccountFromAtlarData(connectorID, externalAccountResponse.Payload, counterpartyResponse.Payload) - if err != nil { - return err - } - - accountsBatch = append(accountsBatch, newAccount) - - err = ingester.IngestAccounts(ctx, accountsBatch) - if err != nil { - return err - } - - if err := ingester.LinkBankAccountWithAccount(ctx, formanceBankAccount, &newAccount.ID); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_accounts.go deleted file mode 100644 index c2174ccf02..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_accounts.go +++ /dev/null @@ -1,220 +0,0 @@ -package atlar - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/big" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/get-momo/atlar-v1-go-client/client/accounts" - "github.com/get-momo/atlar-v1-go-client/client/external_accounts" - "go.opentelemetry.io/otel/attribute" -) - -func FetchAccountsTask(config Config, client *client.Client) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - // Pagination works by cursor token. - for token := ""; ; { - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - pagedAccounts, err := client.GetV1Accounts(requestCtx, token, int64(config.PageSize)) - if err != nil { - otel.RecordError(span, err) - return err - } - - token = pagedAccounts.Payload.NextToken - - if err := ingestAccountsBatch(ctx, connectorID, taskID, ingester, pagedAccounts, client); err != nil { - otel.RecordError(span, err) - return err - } - - if token == "" { - break - } - } - - // Pagination works by cursor token. - for token := ""; ; { - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - pagedExternalAccounts, err := client.GetV1ExternalAccounts(requestCtx, token, int64(config.PageSize)) - if err != nil { - otel.RecordError(span, err) - return err - } - - token = pagedExternalAccounts.Payload.NextToken - - if err := ingestExternalAccountsBatch(ctx, connectorID, ingester, pagedExternalAccounts, client); err != nil { - otel.RecordError(span, err) - return err - } - - if token == "" { - break - } - } - - // Fetch payments after inserting all accounts in order to link them correctly - taskPayments, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch payments from Atlar", - Key: taskNameFetchTransactions, - }) - if err != nil { - otel.RecordError(span, err) - return err - } - - err = scheduler.Schedule(ctx, taskPayments, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - taskID models.TaskID, - ingester ingestion.Ingester, - pagedAccounts *accounts.GetV1AccountsOK, - client *client.Client, -) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskFetchAccounts.ingestAccountsBatch", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - accountsBatch := ingestion.AccountBatch{} - balanceBatch := ingestion.BalanceBatch{} - - for _, account := range pagedAccounts.Payload.Items { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - createdAt, err := ParseAtlarTimestamp(account.Created) - if err != nil { - return fmt.Errorf("failed to parse opening date: %w", err) - } - - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - thirdPartyResponse, err := client.GetV1BetaThirdPartiesID(requestCtx, account.ThirdPartyID) - if err != nil { - otel.RecordError(span, err) - return err - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: *account.ID, - ConnectorID: connectorID, - }, - CreatedAt: createdAt, - Reference: *account.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, account.Currency), - AccountName: account.Name, - Type: models.AccountTypeInternal, - Metadata: ExtractAccountMetadata(account, thirdPartyResponse.Payload), - RawData: raw, - }) - - balance := account.Balance - balanceTimestamp, err := ParseAtlarTimestamp(balance.Timestamp) - if err != nil { - return err - } - balanceBatch = append(balanceBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: *account.ID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, *balance.Amount.Currency), - Balance: big.NewInt(*balance.Amount.Value), - CreatedAt: balanceTimestamp, - LastUpdatedAt: time.Now().UTC(), - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, balanceBatch, false); err != nil { - return err - } - - return nil -} - -func ingestExternalAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - pagedExternalAccounts *external_accounts.GetV1ExternalAccountsOK, - client *client.Client, -) error { - accountsBatch := ingestion.AccountBatch{} - - for _, externalAccount := range pagedExternalAccounts.Payload.Items { - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - counterparty_response, err := client.GetV1CounterpartiesID(requestCtx, externalAccount.CounterpartyID) - if err != nil { - return err - } - counterparty := counterparty_response.Payload - - newAccount, err := ExternalAccountFromAtlarData(connectorID, externalAccount, counterparty) - if err != nil { - return err - } - - accountsBatch = append(accountsBatch, newAccount) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions.go deleted file mode 100644 index ec2f3dd4fe..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions.go +++ /dev/null @@ -1,297 +0,0 @@ -package atlar - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/get-momo/atlar-v1-go-client/client/transactions" - atlar_models "github.com/get-momo/atlar-v1-go-client/models" - "go.opentelemetry.io/otel/attribute" -) - -func FetchTransactionsTask(config Config, client *client.Client) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - resolver task.StateResolver, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - // Pagination works by cursor token. - for token := ""; ; { - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - pagedTransactions, err := client.GetV1Transactions(requestCtx, token, int64(config.PageSize)) - if err != nil { - otel.RecordError(span, err) - return err - } - - token = pagedTransactions.Payload.NextToken - - if err := ingestPaymentsBatch(ctx, connectorID, taskID, ingester, client, pagedTransactions); err != nil { - otel.RecordError(span, err) - return err - } - - if token == "" { - break - } - } - - return nil - } -} - -func ingestPaymentsBatch( - ctx context.Context, - connectorID models.ConnectorID, - taskID models.TaskID, - ingester ingestion.Ingester, - client *client.Client, - pagedTransactions *transactions.GetV1TransactionsOK, -) error { - batch := ingestion.PaymentBatch{} - - for _, item := range pagedTransactions.Payload.Items { - batchElement, err := atlarTransactionToPaymentBatchElement(ctx, connectorID, taskID, item, client) - if err != nil { - return err - } - if batchElement == nil { - continue - } - - batch = append(batch, *batchElement) - } - - if err := ingester.IngestPayments(ctx, batch); err != nil { - return err - } - - return nil -} - -func atlarTransactionToPaymentBatchElement( - ctx context.Context, - connectorID models.ConnectorID, - taskID models.TaskID, - transaction *atlar_models.Transaction, - client *client.Client, -) (*ingestion.PaymentBatchElement, error) { - ctx, span := connectors.StartSpan( - ctx, - "atlar.atlarTransactionToPaymentBatchElement", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - if _, ok := supportedCurrenciesWithDecimal[*transaction.Amount.Currency]; !ok { - // Discard transactions with unsupported currencies - return nil, nil - } - - raw, err := json.Marshal(transaction) - if err != nil { - return nil, err - } - - paymentType := determinePaymentType(transaction) - - itemAmount := transaction.Amount - amount, err := atlarTransactionAmountToPaymentAbsoluteAmount(*itemAmount.Value) - if err != nil { - return nil, err - } - - createdAt, err := ParseAtlarTimestamp(transaction.Created) - if err != nil { - return nil, err - } - - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - accountResponse, err := client.GetV1AccountsID(requestCtx, *transaction.Account.ID) - if err != nil { - otel.RecordError(span, err) - return nil, err - } - - requestCtx, cancel = contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - thirdPartyResponse, err := client.GetV1BetaThirdPartiesID(requestCtx, *&accountResponse.Payload.ThirdPartyID) - if err != nil { - otel.RecordError(span, err) - return nil, err - } - - paymentId := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transaction.ID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: paymentId, - Reference: transaction.ID, - Type: paymentType, - ConnectorID: connectorID, - CreatedAt: createdAt, - Status: determinePaymentStatus(transaction), - Scheme: determinePaymentScheme(transaction), - Amount: amount, - InitialAmount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, *transaction.Amount.Currency), - Metadata: ExtractPaymentMetadata(paymentId, transaction, accountResponse.Payload, thirdPartyResponse.Payload), - RawData: raw, - }, - } - - if *itemAmount.Value >= 0 { - // DEBIT - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: *transaction.Account.ID, - ConnectorID: connectorID, - } - } else { - // CREDIT - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: *transaction.Account.ID, - ConnectorID: connectorID, - } - } - - return &batchElement, nil -} - -func determinePaymentType(item *atlar_models.Transaction) models.PaymentType { - if *item.Amount.Value >= 0 { - return models.PaymentTypePayIn - } else { - return models.PaymentTypePayOut - } -} - -func determinePaymentStatus(item *atlar_models.Transaction) models.PaymentStatus { - if item.Reconciliation.Status == atlar_models.ReconciliationDetailsStatusEXPECTED { - // A payment initiated by the owner of the accunt through the Atlar API, - // which was not yet reconciled with a payment from the statement - return models.PaymentStatusPending - } - if item.Reconciliation.Status == atlar_models.ReconciliationDetailsStatusBOOKED { - // A payment comissioned with the bank, which was not yet reconciled with a - // payment from the statement - return models.PaymentStatusSucceeded - } - if item.Reconciliation.Status == atlar_models.ReconciliationDetailsStatusRECONCILED { - return models.PaymentStatusSucceeded - } - return models.PaymentStatusOther -} - -func determinePaymentScheme(item *atlar_models.Transaction) models.PaymentScheme { - // item.Characteristics.BankTransactionCode.Domain - // item.Characteristics.BankTransactionCode.Family - // TODO: fees and interest -> models.PaymentSchemeOther with additional info on metadata. Will need example transactions for that - - if *item.Amount.Value > 0 { - return models.PaymentSchemeSepaDebit - } else if *item.Amount.Value < 0 { - return models.PaymentSchemeSepaCredit - } - return models.PaymentSchemeSepa -} - -func ExtractPaymentMetadata(paymentId models.PaymentID, transaction *atlar_models.Transaction, account *atlar_models.Account, bank *atlar_models.ThirdParty) []*models.PaymentMetadata { - result := []*models.PaymentMetadata{} - if transaction.Date != "" { - result = append(result, ComputePaymentMetadata(paymentId, "date", transaction.Date)) - } - if transaction.ValueDate != "" { - result = append(result, ComputePaymentMetadata(paymentId, "valueDate", transaction.ValueDate)) - } - result = append(result, ComputePaymentMetadata(paymentId, "remittanceInformation/type", *transaction.RemittanceInformation.Type)) - result = append(result, ComputePaymentMetadata(paymentId, "remittanceInformation/value", *transaction.RemittanceInformation.Value)) - result = append(result, ComputePaymentMetadata(paymentId, "bank/id", bank.ID)) - result = append(result, ComputePaymentMetadata(paymentId, "bank/name", bank.Name)) - result = append(result, ComputePaymentMetadata(paymentId, "bank/bic", account.Bank.Bic)) - result = append(result, ComputePaymentMetadata(paymentId, "btc/domain", transaction.Characteristics.BankTransactionCode.Domain)) - result = append(result, ComputePaymentMetadata(paymentId, "btc/family", transaction.Characteristics.BankTransactionCode.Family)) - result = append(result, ComputePaymentMetadata(paymentId, "btc/subfamily", transaction.Characteristics.BankTransactionCode.Subfamily)) - result = append(result, ComputePaymentMetadata(paymentId, "btc/description", transaction.Characteristics.BankTransactionCode.Description)) - result = append(result, ComputePaymentMetadataBool(paymentId, "returned", transaction.Characteristics.Returned)) - if transaction.CounterpartyDetails != nil && transaction.CounterpartyDetails.Name != "" { - result = append(result, ComputePaymentMetadata(paymentId, "counterparty/name", transaction.CounterpartyDetails.Name)) - if transaction.CounterpartyDetails.ExternalAccount != nil && transaction.CounterpartyDetails.ExternalAccount.Identifier != nil { - result = append(result, ComputePaymentMetadata(paymentId, "counterparty/bank/bic", transaction.CounterpartyDetails.ExternalAccount.Bank.Bic)) - result = append(result, ComputePaymentMetadata(paymentId, "counterparty/bank/name", transaction.CounterpartyDetails.ExternalAccount.Bank.Name)) - result = append(result, ComputePaymentMetadata(paymentId, - fmt.Sprintf("counterparty/identifier/%s", transaction.CounterpartyDetails.ExternalAccount.Identifier.Type), - transaction.CounterpartyDetails.ExternalAccount.Identifier.Number)) - } - } - if transaction.Characteristics.Returned { - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/code", transaction.Characteristics.ReturnReason.Code)) - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/description", transaction.Characteristics.ReturnReason.Description)) - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/btc/domain", transaction.Characteristics.ReturnReason.OriginalBankTransactionCode.Domain)) - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/btc/family", transaction.Characteristics.ReturnReason.OriginalBankTransactionCode.Family)) - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/btc/subfamily", transaction.Characteristics.ReturnReason.OriginalBankTransactionCode.Subfamily)) - result = append(result, ComputePaymentMetadata(paymentId, "returnReason/btc/description", transaction.Characteristics.ReturnReason.OriginalBankTransactionCode.Description)) - } - if transaction.Characteristics.VirtualAccount != nil { - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/market", transaction.Characteristics.VirtualAccount.Market)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/rawIdentifier", transaction.Characteristics.VirtualAccount.RawIdentifier)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/bank/id", transaction.Characteristics.VirtualAccount.Bank.ID)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/bank/name", transaction.Characteristics.VirtualAccount.Bank.Name)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/bank/bic", transaction.Characteristics.VirtualAccount.Bank.Bic)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/identifier/holderName", *transaction.Characteristics.VirtualAccount.Identifier.HolderName)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/identifier/market", transaction.Characteristics.VirtualAccount.Identifier.Market)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/identifier/type", transaction.Characteristics.VirtualAccount.Identifier.Type)) - result = append(result, ComputePaymentMetadata(paymentId, "virtualAccount/identifier/number", transaction.Characteristics.VirtualAccount.Identifier.Number)) - } - result = append(result, ComputePaymentMetadata(paymentId, "reconciliation/status", transaction.Reconciliation.Status)) - result = append(result, ComputePaymentMetadata(paymentId, "reconciliation/transactableId", transaction.Reconciliation.TransactableID)) - result = append(result, ComputePaymentMetadata(paymentId, "reconciliation/transactableType", transaction.Reconciliation.TransactableType)) - if transaction.Characteristics.CurrencyExchange != nil { - result = append(result, ComputePaymentMetadata(paymentId, "currencyExchange/sourceCurrency", transaction.Characteristics.CurrencyExchange.SourceCurrency)) - result = append(result, ComputePaymentMetadata(paymentId, "currencyExchange/targetCurrency", transaction.Characteristics.CurrencyExchange.TargetCurrency)) - result = append(result, ComputePaymentMetadata(paymentId, "currencyExchange/exchangeRate", transaction.Characteristics.CurrencyExchange.ExchangeRate)) - result = append(result, ComputePaymentMetadata(paymentId, "currencyExchange/unitCurrency", transaction.Characteristics.CurrencyExchange.UnitCurrency)) - } - if transaction.CounterpartyDetails.MandateReference != "" { - result = append(result, ComputePaymentMetadata(paymentId, "mandateReference", transaction.CounterpartyDetails.MandateReference)) - } - - return result -} - -func atlarTransactionAmountToPaymentAbsoluteAmount(atlarAmount int64) (*big.Int, error) { - var amount big.Int - amountInt := amount.SetInt64(atlarAmount) - amountInt = amountInt.Abs(amountInt) - return amountInt, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions_test.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions_test.go deleted file mode 100644 index 20599a183c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_fetch_transactions_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package atlar - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAtlarTransactionAmountToPaymentAbsoluteAmount(t *testing.T) { - t.Parallel() - var result *big.Int - var err error - - result, err = atlarTransactionAmountToPaymentAbsoluteAmount(30) - if assert.Nil(t, err) { - assert.Equal(t, *big.NewInt(30), *result) - } - - result, err = atlarTransactionAmountToPaymentAbsoluteAmount(330) - if assert.Nil(t, err) { - assert.Equal(t, *big.NewInt(330), *result) - } - - result, err = atlarTransactionAmountToPaymentAbsoluteAmount(330) - if assert.Nil(t, err) { - assert.Equal(t, *big.NewInt(330), *result) - } - - result, err = atlarTransactionAmountToPaymentAbsoluteAmount(-30) - if assert.Nil(t, err) { - assert.Equal(t, *big.NewInt(30), *result) - } - - result, err = atlarTransactionAmountToPaymentAbsoluteAmount(-330) - if assert.Nil(t, err) { - assert.Equal(t, *big.NewInt(330), *result) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_main.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_main.go deleted file mode 100644 index 35a2d87868..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_main.go +++ /dev/null @@ -1,52 +0,0 @@ -package atlar - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// Launch accounts and payments tasks. -// Period between runs dictated by config.PollingPeriod. -func MainTask(logger logging.Logger) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_ALWAYS, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_payments.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_payments.go deleted file mode 100644 index d9969c896b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_payments.go +++ /dev/null @@ -1,394 +0,0 @@ -package atlar - -import ( - "context" - "errors" - "fmt" - "math/big" - "regexp" - "strings" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/get-momo/atlar-v1-go-client/client/credit_transfers" - atlar_models "github.com/get-momo/atlar-v1-go-client/models" - "go.opentelemetry.io/otel/attribute" -) - -func InitiatePaymentTask(config Config, client *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - return err - } - - var paymentID *models.PaymentID - defer func() { - if err != nil { - otel.RecordError(span, err) - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - if err := ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()); err != nil { - otel.RecordError(span, err) - } - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount != nil { - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - } - - currency, precision, err := currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - paymentSchemeType := "SCT" // SEPA Credit Transfer - remittanceInformationType := "UNSTRUCTURED" - remittanceInformationValue := transfer.Description - amount := atlar_models.AmountInput{ - Currency: ¤cy, - Value: transfer.Amount.Int64(), - StringValue: amountToString(*transfer.Amount, precision), - } - date := transfer.ScheduledAt - if date.IsZero() { - date = time.Now() - } - dateString := date.Format(time.DateOnly) - - createPaymentRequest := atlar_models.CreatePaymentRequest{ - SourceAccountID: &transfer.SourceAccount.Reference, - DestinationExternalAccountID: &transfer.DestinationAccount.Reference, - Amount: &amount, - Date: &dateString, - ExternalID: serializeAtlarPaymentExternalID(transfer.ID.Reference, transfer.CountRetries()), - PaymentSchemeType: &paymentSchemeType, - RemittanceInformation: &atlar_models.RemittanceInformation{ - Type: &remittanceInformationType, - Value: &remittanceInformationValue, - }, - } - - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - var postCreditTransferResponse *credit_transfers.PostV1CreditTransfersCreated - postCreditTransferResponse, err = client.PostV1CreditTransfers(requestCtx, &createPaymentRequest) - if err != nil { - return err - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: postCreditTransferResponse.Payload.Reconciliation.ExpectedTransactionID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - - var taskDescriptor models.TaskDescriptor - taskDescriptor, err = models.EncodeTaskDescriptor(TaskDescriptor{ - Name: fmt.Sprintf("Update transfer initiation status of transfer %s", transfer.ID.String()), - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil - } -} - -func ValidateTransferInitiation(transfer *models.TransferInitiation) error { - if transfer == nil { - return errors.New("transfer cannot be nil") - } - if transfer.Type.String() != "PAYOUT" { - return errors.New("this connector only supports type PAYOUT") - } - return nil -} - -func UpdatePaymentStatusTask( - config Config, - client *client.Client, - transferID string, - stringPaymentID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - paymentID := models.MustPaymentIDFromString(stringPaymentID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", stringPaymentID), - attribute.Int("attempt", attempt), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - getCreditTransferResponse, err := client.GetV1CreditTransfersGetByExternalIDExternalID( - requestCtx, - serializeAtlarPaymentExternalID(transfer.ID.Reference, transfer.CountRetries()), - ) - if err != nil { - otel.RecordError(span, err) - return err - } - - status := getCreditTransferResponse.Payload.Status - // Status docs: https://docs.atlar.com/docs/payment-details#payment-states--events - switch status { - case "CREATED", "APPROVED", "PENDING_SUBMISSION", "SENT", "PENDING_AT_BANK", "ACCEPTED", "EXECUTED": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: fmt.Sprintf("Update transfer initiation status of transfer %s", transfer.ID.String()), - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - otel.RecordError(span, err) - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: config.TransferInitiationStatusPollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return err - } - return nil - - case "RECONCILED": - err = ingestAtlarTransaction(ctx, - ingester, - connectorID, - taskID, - client, - getCreditTransferResponse.Payload.Reconciliation.BookedTransactionID, - ) - if err != nil { - otel.RecordError(span, err) - return err - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: getCreditTransferResponse.Payload.Reconciliation.BookedTransactionID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - - err = ingester.UpdateTransferInitiationPayment(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - otel.RecordError(span, err) - return err - } - - return nil - - case "REJECTED", "FAILED", "RETURNED": - err = ingester.UpdateTransferInitiationPaymentsStatus( - ctx, transfer, paymentID, models.TransferInitiationStatusFailed, - fmt.Sprintf("paymant initiation status is \"%s\"", status), time.Now(), - ) - if err != nil { - otel.RecordError(span, err) - return err - } - - return nil - - default: - err := fmt.Errorf( - "unknown status \"%s\" encountered while fetching payment initiation status of payment \"%s\"", - status, getCreditTransferResponse.Payload.ID, - ) - otel.RecordError(span, err) - return err - } - } -} - -func amountToString(amount big.Int, precision int) string { - raw := amount.String() - if precision < 0 { - precision = 0 - } - insertPosition := len(raw) - precision - if insertPosition <= 0 { - return "0." + strings.Repeat("0", -insertPosition) + raw - } - return raw[:insertPosition] + "." + raw[insertPosition:] -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} - -func serializeAtlarPaymentExternalID(ID string, attempts int) string { - return fmt.Sprintf("%s_%d", ID, attempts) -} - -var deserializeAtlarPaymentExternalIDRegex = regexp.MustCompile(`^([^\_]+)_([0-9]+)$`) - -func deserializeAtlarPaymentExternalID(serialized string) (string, int, error) { - var attempts int - - // Find matches in the input string - matches := deserializeAtlarPaymentExternalIDRegex.FindStringSubmatch(serialized) - if matches == nil || len(matches) != 3 { - return "", 0, errors.New("cannot deserialize malformed externalID") - } - - parsed, err := fmt.Sscanf(matches[2], "%d", &attempts) - if err != nil { - return "", 0, errors.New("cannot deserialize malformed externalID") - } - if parsed != 1 { - return "", 0, errors.New("cannot deserialize malformed externalID") - } - return matches[1], attempts, nil -} - -func ingestAtlarTransaction( - ctx context.Context, - ingester ingestion.Ingester, - connectorID models.ConnectorID, - taskID models.TaskID, - client *client.Client, - transactionId string, -) error { - ctx, span := connectors.StartSpan( - ctx, - "atlar.taskUpdatePaymentStatus.ingestAtlarTransaction", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transactionID", transactionId), - ) - defer span.End() - - requestCtx, cancel := contextutil.DetachedWithTimeout(ctx, 30*time.Second) - defer cancel() - transactionResponse, err := client.GetV1TransactionsID(requestCtx, transactionId) - if err != nil { - otel.RecordError(span, err) - return err - } - - batchElement, err := atlarTransactionToPaymentBatchElement(ctx, connectorID, taskID, transactionResponse.Payload, client) - if err != nil { - otel.RecordError(span, err) - return err - } - if batchElement == nil { - return nil - } - - batch := ingestion.PaymentBatch{*batchElement} - - err = ingester.IngestPayments(ctx, batch) - if err != nil { - otel.RecordError(span, err) - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_payments_test.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_payments_test.go deleted file mode 100644 index b01621f5a1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_payments_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package atlar - -import ( - "errors" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAmountToString(t *testing.T) { - t.Parallel() - - assert.EqualValues(t, "0.032", amountToString(*big.NewInt(32), 3)) - assert.EqualValues(t, "0.32", amountToString(*big.NewInt(32), 2)) - assert.EqualValues(t, "3.2", amountToString(*big.NewInt(32), 1)) - assert.EqualValues(t, "5.432", amountToString(*big.NewInt(5432), 3)) - assert.EqualValues(t, "54.32", amountToString(*big.NewInt(5432), 2)) - assert.EqualValues(t, "543.2", amountToString(*big.NewInt(5432), 1)) -} - -func TestSerializeAtlarPaymentExternalID(t *testing.T) { - t.Parallel() - - assert.EqualValues(t, "testID_1", serializeAtlarPaymentExternalID("testID", 1)) - assert.EqualValues(t, "tqmbAGgV4S2pHics57BT5tV2_682", serializeAtlarPaymentExternalID("tqmbAGgV4S2pHics57BT5tV2", 682)) -} - -func TestDeserializeAtlarPaymentExternalID(t *testing.T) { - t.Parallel() - - var ID string - var attempts int - var err error - - ID, attempts, err = deserializeAtlarPaymentExternalID("testID_1") - if assert.Nil(t, err) { - assert.EqualValues(t, "testID", ID) - assert.EqualValues(t, 1, attempts) - } - - ID, attempts, err = deserializeAtlarPaymentExternalID("tqmbAGgV4S2pHics57BT5tV2_682") - if assert.Nil(t, err) { - assert.EqualValues(t, "tqmbAGgV4S2pHics57BT5tV2", ID) - assert.EqualValues(t, 682, attempts) - } - - _, _, err = deserializeAtlarPaymentExternalID("tqmbAGgV4S2pHics57BT5tV2_682_432") - if assert.Error(t, err) { - assert.Equal(t, errors.New("cannot deserialize malformed externalID"), err) - } - - _, _, err = deserializeAtlarPaymentExternalID("tqmbAGgV4S2pHics57BT5tV2_") - if assert.Error(t, err) { - assert.Equal(t, errors.New("cannot deserialize malformed externalID"), err) - } - - _, _, err = deserializeAtlarPaymentExternalID("tqmbAGgV4S2pHics57BT5tV2") - if assert.Error(t, err) { - assert.Equal(t, errors.New("cannot deserialize malformed externalID"), err) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/atlar/task_resolve.go deleted file mode 100644 index 1483d257a7..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/task_resolve.go +++ /dev/null @@ -1,52 +0,0 @@ -package atlar - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/atlar/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const ( - taskNameFetchAccounts = "fetch_accounts" - taskNameFetchTransactions = "fetch_transactions" - taskNameCreateExternalBankAccount = "create_external_bank_account" - taskNameInitiatePayment = "initiate_payment" - taskNameUpdatePaymentStatus = "update_payment_status" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - Main bool `json:"main,omitempty" yaml:"main" bson:"main"` - BankAccount *models.BankAccount `json:"bankAccount,omitempty" yaml:"bankAccount" bson:"bankAccount"` - TransferID string `json:"transferId,omitempty" yaml:"transferId" bson:"transferId"` - PaymentID string `json:"paymentId,omitempty" yaml:"paymentId" bson:"paymentId"` - Attempt int `json:"attempt,omitempty" yaml:"attempt" bson:"attempt"` -} - -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - client := client.NewClient(config.BaseUrl, config.AccessKey, config.Secret) - - return func(taskDescriptor TaskDescriptor) task.Task { - if taskDescriptor.Main { - return MainTask(logger) - } - - switch taskDescriptor.Key { - case taskNameFetchAccounts: - return FetchAccountsTask(config, client) - case taskNameFetchTransactions: - return FetchTransactionsTask(config, client) - case taskNameCreateExternalBankAccount: - return CreateExternalBankAccountTask(config, client, taskDescriptor.BankAccount) - case taskNameInitiatePayment: - return InitiatePaymentTask(config, client, taskDescriptor.TransferID) - case taskNameUpdatePaymentStatus: - return UpdatePaymentStatusTask(config, client, taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt) - default: - return nil - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/atlar/time.go b/components/payments/cmd/connectors/internal/connectors/atlar/time.go deleted file mode 100644 index 8a90de7c9e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/atlar/time.go +++ /dev/null @@ -1,16 +0,0 @@ -package atlar - -import "time" - -func ParseAtlarTimestamp(value string) (time.Time, error) { - return time.Parse(time.RFC3339Nano, value) -} - -func ParseAtlarDate(value string) (time.Time, error) { - return time.Parse(time.DateOnly, value) -} - -func TimeToAtlarTimestamp(input *time.Time) *string { - atlarTimestamp := input.Format(time.RFC3339Nano) - return &atlarTimestamp -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/accounts.go deleted file mode 100644 index 2e1ded4476..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/accounts.go +++ /dev/null @@ -1,131 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Account struct { - AccountID string `json:"accountId"` - AccountDescription string `json:"accountDescription"` - AccountIdentifiers []struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` - } `json:"accountIdentifiers"` - Status string `json:"status"` - Currency string `json:"currency"` - OpeningDate string `json:"openingDate"` - ClosingDate string `json:"closingDate"` - OwnedByCompanyID string `json:"ownedByCompanyId"` - ProtectionType string `json:"protectionType"` - Balances []struct { - Type string `json:"type"` - Currency string `json:"currency"` - BeginOfDayAmount json.Number `json:"beginOfDayAmount"` - FinancialDate string `json:"financialDate"` - IntraDayAmount json.Number `json:"intraDayAmount"` - LastTransactionTimestamp string `json:"lastTransactionTimestamp"` - } `json:"balances"` -} - -func (c *Client) GetAccounts(ctx context.Context, page int) ([]*Account, error) { - if err := c.ensureAccessTokenIsValid(ctx); err != nil { - return nil, err - } - - f := connectors.ClientMetrics(ctx, "bankingcircle", "list_accounts") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.endpoint+"/api/v1/accounts", http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create account request: %w", err) - } - - q := req.URL.Query() - q.Add("PageSize", "100") - q.Add("PageNumber", fmt.Sprint(page)) - - req.URL.RawQuery = q.Encode() - - req.Header.Set("Authorization", "Bearer "+c.accessToken) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read accounts response body: %w", err) - } - - type response struct { - Result []*Account `json:"result"` - PageInfo struct { - CurrentPage int `json:"currentPage"` - PageSize int `json:"pageSize"` - } `json:"pageInfo"` - } - - var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal accounts response: %w", err) - } - - return res.Result, nil -} - -func (c *Client) GetAccount(ctx context.Context, accountID string) (*Account, error) { - if err := c.ensureAccessTokenIsValid(ctx); err != nil { - return nil, err - } - - f := connectors.ClientMetrics(ctx, "bankingcircle", "get_account") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/accounts/%s", c.endpoint, accountID), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create account request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.accessToken) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("wrong status code: %d", resp.StatusCode) - } - - var account Account - if err := json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to decode account response: %w", err) - } - - return &account, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/auth.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/auth.go deleted file mode 100644 index 7374edd9e0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/auth.go +++ /dev/null @@ -1,94 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -func (c *Client) login(ctx context.Context) error { - f := connectors.ClientMetrics(ctx, "bankingcircle", "authorize") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, - c.authorizationEndpoint+"/api/v1/authorizations/authorize", http.NoBody) - if err != nil { - return fmt.Errorf("failed to create login request: %w", err) - } - - req.SetBasicAuth(c.username, c.password) - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("failed to login: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read login response body: %w", err) - } - - if resp.StatusCode != http.StatusOK { - type responseError struct { - ErrorCode string `json:"errorCode"` - ErrorText string `json:"errorText"` - } - var errors []responseError - if err = json.Unmarshal(responseBody, &errors); err != nil { - return fmt.Errorf("failed to unmarshal login response: %w", err) - } - if len(errors) > 0 { - return fmt.Errorf("failed to login: %s %s", errors[0].ErrorCode, errors[0].ErrorText) - } - return fmt.Errorf("failed to login: %s", resp.Status) - } - - //nolint:tagliatelle // allow for client-side structures - type response struct { - AccessToken string `json:"access_token"` - ExpiresIn string `json:"expires_in"` - } - - var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return fmt.Errorf("failed to unmarshal login response: %w", err) - } - - c.accessToken = res.AccessToken - - expiresIn, err := strconv.Atoi(res.ExpiresIn) - if err != nil { - return fmt.Errorf("failed to convert expires_in to int: %w", err) - } - - c.accessTokenExpiresAt = time.Now().Add(time.Duration(expiresIn) * time.Second) - - return nil -} - -func (c *Client) ensureAccessTokenIsValid(ctx context.Context) error { - if c.accessToken == "" { - return c.login(ctx) - } - - if c.accessTokenExpiresAt.After(time.Now().Add(5 * time.Second)) { - return nil - } - - return c.login(ctx) -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/client.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/client.go deleted file mode 100644 index 319833bff0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/client.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - "crypto/tls" - "net/http" - "time" - - "github.com/formancehq/go-libs/logging" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -type Client struct { - httpClient *http.Client - - username string - password string - - endpoint string - authorizationEndpoint string - - logger logging.Logger - - accessToken string - accessTokenExpiresAt time.Time -} - -func newHTTPClient(userCertificate, userCertificateKey string) (*http.Client, error) { - cert, err := tls.X509KeyPair([]byte(userCertificate), []byte(userCertificateKey)) - if err != nil { - return nil, err - } - - tr := http.DefaultTransport.(*http.Transport).Clone() - tr.TLSClientConfig = &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - - return &http.Client{ - Timeout: 10 * time.Second, - Transport: otelhttp.NewTransport(tr), - }, nil -} - -func NewClient( - username, password, - endpoint, authorizationEndpoint, - uCertificate, uCertificateKey string, - logger logging.Logger) (*Client, error) { - httpClient, err := newHTTPClient(uCertificate, uCertificateKey) - if err != nil { - return nil, err - } - - c := &Client{ - httpClient: httpClient, - - username: username, - password: password, - endpoint: endpoint, - authorizationEndpoint: authorizationEndpoint, - - logger: logger, - } - - return c, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/payments.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/payments.go deleted file mode 100644 index 544790096d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/payments.go +++ /dev/null @@ -1,181 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -//nolint:tagliatelle // allow for client-side structures -type Payment struct { - PaymentID string `json:"paymentId"` - TransactionReference string `json:"transactionReference"` - ConcurrencyToken string `json:"concurrencyToken"` - Classification string `json:"classification"` - Status string `json:"status"` - Errors interface{} `json:"errors"` - LastChangedTimestamp time.Time `json:"lastChangedTimestamp"` - DebtorInformation struct { - PaymentBulkID interface{} `json:"paymentBulkId"` - AccountID string `json:"accountId"` - Account struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` - } `json:"account"` - VibanID interface{} `json:"vibanId"` - Viban struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` - } `json:"viban"` - InstructedDate interface{} `json:"instructedDate"` - DebitAmount struct { - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - } `json:"debitAmount"` - DebitValueDate time.Time `json:"debitValueDate"` - FxRate interface{} `json:"fxRate"` - Instruction interface{} `json:"instruction"` - } `json:"debtorInformation"` - Transfer struct { - DebtorAccount interface{} `json:"debtorAccount"` - DebtorName interface{} `json:"debtorName"` - DebtorAddress interface{} `json:"debtorAddress"` - Amount struct { - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - } `json:"amount"` - ValueDate interface{} `json:"valueDate"` - ChargeBearer interface{} `json:"chargeBearer"` - RemittanceInformation interface{} `json:"remittanceInformation"` - CreditorAccount interface{} `json:"creditorAccount"` - CreditorName interface{} `json:"creditorName"` - CreditorAddress interface{} `json:"creditorAddress"` - } `json:"transfer"` - CreditorInformation struct { - AccountID string `json:"accountId"` - Account struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` - } `json:"account"` - VibanID interface{} `json:"vibanId"` - Viban struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` - } `json:"viban"` - CreditAmount struct { - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - } `json:"creditAmount"` - CreditValueDate time.Time `json:"creditValueDate"` - FxRate interface{} `json:"fxRate"` - } `json:"creditorInformation"` -} - -func (c *Client) GetPayments(ctx context.Context, page int) ([]*Payment, error) { - if err := c.ensureAccessTokenIsValid(ctx); err != nil { - return nil, err - } - - f := connectors.ClientMetrics(ctx, "bankingcircle", "list_payments") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.endpoint+"/api/v1/payments/singles", http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create payments request: %w", err) - } - - q := req.URL.Query() - q.Add("PageSize", "100") - q.Add("PageNumber", fmt.Sprint(page)) - - req.URL.RawQuery = q.Encode() - - req.Header.Set("Authorization", "Bearer "+c.accessToken) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payments: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read payments response body: %w", err) - } - - type response struct { - Result []*Payment `json:"result"` - PageInfo struct { - CurrentPage int `json:"currentPage"` - PageSize int `json:"pageSize"` - } `json:"pageInfo"` - } - - var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal payments response: %w", err) - } - - return res.Result, nil -} - -type StatusResponse struct { - Status string `json:"status"` -} - -func (c *Client) GetPaymentStatus(ctx context.Context, paymentID string) (*StatusResponse, error) { - if err := c.ensureAccessTokenIsValid(ctx); err != nil { - return nil, err - } - - f := connectors.ClientMetrics(ctx, "bankingcircle", "get_payment_status") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/payments/singles/%s/status", c.endpoint, paymentID), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create payments request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.accessToken) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payments: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read payments response body: %w", err) - } - - var res StatusResponse - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal payments response: %w", err) - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/transfer_payouts.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/transfer_payouts.go deleted file mode 100644 index 77ab1434c6..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/client/transfer_payouts.go +++ /dev/null @@ -1,82 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type PaymentAccount struct { - Account string `json:"account"` - FinancialInstitution string `json:"financialInstitution"` - Country string `json:"country"` -} - -type PaymentRequest struct { - IdempotencyKey string `json:"idempotencyKey"` - RequestedExecutionDate time.Time `json:"requestedExecutionDate"` - DebtorAccount PaymentAccount `json:"debtorAccount"` - DebtorReference string `json:"debtorReference"` - CurrencyOfTransfer string `json:"currencyOfTransfer"` - Amount struct { - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - } `json:"amount"` - ChargeBearer string `json:"chargeBearer"` - CreditorAccount *PaymentAccount `json:"creditorAccount"` -} - -type PaymentResponse struct { - PaymentID string `json:"paymentId"` - Status string `json:"status"` -} - -func (c *Client) InitiateTransferOrPayouts(ctx context.Context, transferRequest *PaymentRequest) (*PaymentResponse, error) { - if err := c.ensureAccessTokenIsValid(ctx); err != nil { - return nil, err - } - - f := connectors.ClientMetrics(ctx, "bankingcircle", "create_transfers_payouts") - now := time.Now() - defer f(ctx, now) - - body, err := json.Marshal(transferRequest) - if err != nil { - return nil, fmt.Errorf("failed to marshal transfer request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint+"/api/v1/payments/singles", bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create payments request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+c.accessToken) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to make transfer: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusCreated { - return nil, fmt.Errorf("failed to make transfer: %w", err) - } - - var transferResponse PaymentResponse - if err := json.NewDecoder(resp.Body).Decode(&transferResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return &transferResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/config.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/config.go deleted file mode 100644 index c80b79a63e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/config.go +++ /dev/null @@ -1,94 +0,0 @@ -package bankingcircle - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - defaultPollingPeriod = 2 * time.Minute -) - -// PFX is not handle very well in Go if we have more than one certificate -// in the pfx data. -// To be safe for every user to pass the right data to the connector, let's -// use two config parameters instead of one: the user certificate and the key -// associated. -// To extract them for a pfx file, you can use the following commands: -// openssl pkcs12 -in PC20230412293693.pfx -clcerts -nokeys | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > clientcert.cer -// openssl pkcs12 -in PC20230412293693.pfx -nocerts -nodes | sed -ne '/-BEGIN PRIVATE KEY-/,/-END PRIVATE KEY-/p' > clientcert.key -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - Username string `json:"username" yaml:"username" bson:"username"` - Password string `json:"password" yaml:"password" bson:"password"` - Endpoint string `json:"endpoint" yaml:"endpoint" bson:"endpoint"` - AuthorizationEndpoint string `json:"authorizationEndpoint" yaml:"authorizationEndpoint" bson:"authorizationEndpoint"` - UserCertificate string `json:"userCertificate" yaml:"userCertificate" bson:"userCertificate"` - UserCertificateKey string `json:"userCertificateKey" yaml:"userCertificateKey" bson:"userCertificateKey"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return fmt.Sprintf("username=%s, password=****, endpoint=%s, authorizationEndpoint=%s", c.Username, c.Endpoint, c.AuthorizationEndpoint) -} - -func (c Config) Validate() error { - if c.Username == "" { - return ErrMissingUsername - } - - if c.Password == "" { - return ErrMissingPassword - } - - if c.Endpoint == "" { - return ErrMissingEndpoint - } - - if c.AuthorizationEndpoint == "" { - return ErrMissingAuthorizationEndpoint - } - - if c.UserCertificate == "" { - return ErrMissingUserCertificate - } - - if c.UserCertificateKey == "" { - return ErrMissingUserCertificatePassphrase - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("username", configtemplate.TypeString, "", true) - cfg.AddParameter("password", configtemplate.TypeString, "", true) - cfg.AddParameter("endpoint", configtemplate.TypeString, "", true) - cfg.AddParameter("authorizationEndpoint", configtemplate.TypeString, "", true) - cfg.AddParameter("userCertificate", configtemplate.TypeLongString, "", true) - cfg.AddParameter("userCertificateKey", configtemplate.TypeLongString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/connector.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/connector.go deleted file mode 100644 index 6491a4b0da..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/connector.go +++ /dev/null @@ -1,152 +0,0 @@ -package bankingcircle - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" -) - -const name = models.ConnectorProviderBankingCircle - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch payments", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} -func (c *Connector) Install(ctx task.ConnectorContext) error { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate bank account creation", - Key: taskNameCreateExternalAccount, - BankAccountID: bankAccount.ID, - }) - if err != nil { - return err - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/currencies.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/currencies.go deleted file mode 100644 index 9ffb3e4de4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/currencies.go +++ /dev/null @@ -1,38 +0,0 @@ -package bankingcircle - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // All supported BankingCircle currencies and decimal are on par with - // ISO4217Currencies. - supportedCurrenciesWithDecimal = map[string]int{ - "AED": currency.ISO4217Currencies["AED"], // UAE Dirham - "AUD": currency.ISO4217Currencies["AUD"], // Australian Dollar - "CAD": currency.ISO4217Currencies["CAD"], // Canadian Dollar - "CHF": currency.ISO4217Currencies["CHF"], // Swiss Franc - "CNY": currency.ISO4217Currencies["CNY"], // China Yuan Renminbi - "CZK": currency.ISO4217Currencies["CZK"], // Czech Koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish Krone - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "GBP": currency.ISO4217Currencies["GBP"], // Pound Sterling - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong Dollar - "ILS": currency.ISO4217Currencies["ILS"], // Israeli Shekel - "JPY": currency.ISO4217Currencies["JPY"], // Japanese Yen - "MXN": currency.ISO4217Currencies["MXN"], // Mexican Peso - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian Krone - "NZD": currency.ISO4217Currencies["NZD"], // New Zealand Dollar - "PLN": currency.ISO4217Currencies["PLN"], // Polish Zloty - "RON": currency.ISO4217Currencies["RON"], // Romanian Leu - "SAR": currency.ISO4217Currencies["SAR"], // Saudi Riyal - "SEK": currency.ISO4217Currencies["SEK"], // Swedish Krona - "SGD": currency.ISO4217Currencies["SGD"], // Singapore Dollar - "TRY": currency.ISO4217Currencies["TRY"], // Turkish Lira - "USD": currency.ISO4217Currencies["USD"], // US Dollar - "ZAR": currency.ISO4217Currencies["ZAR"], // South African Rand - - // Unsupported currencies - // Since we're not sure about decimals for these currencies, we prefer - // to not support them for now. - // "HUF": 2, // Hungarian Forint - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/errors.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/errors.go deleted file mode 100644 index 641171001a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -package bankingcircle - -import "github.com/pkg/errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingUsername is returned when the username is missing. - ErrMissingUsername = errors.New("missing username from config") - - // ErrMissingPassword is returned when the password is missing. - ErrMissingPassword = errors.New("missing password from config") - - // ErrMissingEndpoint is returned when the endpoint is missing. - ErrMissingEndpoint = errors.New("missing endpoint from config") - - // ErrMissingAuthorizationEndpoint is returned when the authorization endpoint is missing. - ErrMissingAuthorizationEndpoint = errors.New("missing authorization endpoint from config") - - // ErrMissingUserCertificate is returned when the user certificate is missing. - ErrMissingUserCertificate = errors.New("missing user certificate from config") - - // ErrMissingUserCertificatePassphrase is returned when the user certificate passphrase is missing. - ErrMissingUserCertificatePassphrase = errors.New("missing user certificate passphrase from config") - - // ErrMissingClientCertificate is returned when the client certificate is missing. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/loader.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/loader.go deleted file mode 100644 index 8df156b5fb..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/loader.go +++ /dev/null @@ -1,47 +0,0 @@ -package bankingcircle - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_create_external_account.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_create_external_account.go deleted file mode 100644 index 46e175b346..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_create_external_account.go +++ /dev/null @@ -1,88 +0,0 @@ -package bankingcircle - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "go.opentelemetry.io/otel/attribute" -) - -// No need to call any API for banking circle since it does not support it. -// We will just create an external accounts on our side linked to the -// bank account object. -func taskCreateExternalAccount( - client *client.Client, - bankAccountID uuid.UUID, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - storageReader storage.Reader, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskCreateExternalAccount", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("bankAccount.id", bankAccountID.String()), - ) - defer span.End() - - bankAccount, err := storageReader.GetBankAccount(ctx, bankAccountID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - span.SetAttributes(attribute.String("bankAccount.name", bankAccount.Name)) - - if err := createExternalAccount(ctx, connectorID, ingester, storageReader, bankAccount); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func createExternalAccount( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - storageReader storage.Reader, - bankAccount *models.BankAccount, -) error { - accountID := models.AccountID{ - Reference: bankAccount.ID.String(), - ConnectorID: connectorID, - } - - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch{ - { - ID: accountID, - CreatedAt: time.Now(), - Reference: bankAccount.ID.String(), - ConnectorID: connectorID, - AccountName: bankAccount.Name, - Type: models.AccountTypeExternalFormance, - }, - }); err != nil { - return err - } - - if err := ingester.LinkBankAccountWithAccount(ctx, bankAccount, &accountID); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_accounts.go deleted file mode 100644 index e3959b52a1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_accounts.go +++ /dev/null @@ -1,158 +0,0 @@ -package bankingcircle - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchAccounts( - client *client.Client, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - if err := fetchAccount(ctx, client, connectorID, scheduler, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchAccount( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, -) error { - for page := 1; ; page++ { - pagedAccounts, err := client.GetAccounts(ctx, page) - if err != nil { - return err - } - - if len(pagedAccounts) == 0 { - break - } - - if err := ingestAccountsBatch(ctx, connectorID, ingester, pagedAccounts); err != nil { - return err - } - } - - // We want to fetch payments after inserting all accounts in order to - // ling them correctly - taskPayments, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch payments from client", - Key: taskNameFetchPayments, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskPayments, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []*client.Account, -) error { - accountsBatch := ingestion.AccountBatch{} - balanceBatch := ingestion.BalanceBatch{} - - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - openingDate, err := time.Parse("2006-01-02T15:04:05.999999999+00:00", account.OpeningDate) - if err != nil { - return fmt.Errorf("failed to parse opening date: %w", err) - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: account.AccountID, - ConnectorID: connectorID, - }, - CreatedAt: openingDate, - Reference: account.AccountID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, account.Currency), - AccountName: account.AccountDescription, - Type: models.AccountTypeInternal, - RawData: raw, - }) - - for _, balance := range account.Balances { - // No need to check if the currency is supported for accounts and - // balances. - precision := supportedCurrenciesWithDecimal[balance.Currency] - - amount, err := currency.GetAmountWithPrecisionFromString(balance.IntraDayAmount.String(), precision) - if err != nil { - return err - } - - now := time.Now() - balanceBatch = append(balanceBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: account.AccountID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Currency), - Balance: amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, balanceBatch, false); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_payments.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_payments.go deleted file mode 100644 index 3974e61f50..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_fetch_payments.go +++ /dev/null @@ -1,165 +0,0 @@ -package bankingcircle - -import ( - "context" - "encoding/json" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchPayments( - client *client.Client, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskFetchPayments", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - if err := fetchPayments(ctx, client, connectorID, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchPayments( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, -) error { - for page := 1; ; page++ { - pagedPayments, err := client.GetPayments(ctx, page) - if err != nil { - return err - } - - if len(pagedPayments) == 0 { - break - } - - if err := ingestBatch(ctx, connectorID, ingester, pagedPayments); err != nil { - return err - } - } - - return nil -} - -func ingestBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - payments []*client.Payment, -) error { - batch := ingestion.PaymentBatch{} - - for _, paymentEl := range payments { - raw, err := json.Marshal(paymentEl) - if err != nil { - return err - } - - paymentType := matchPaymentType(paymentEl.Classification) - - precision, ok := supportedCurrenciesWithDecimal[paymentEl.Transfer.Amount.Currency] - if !ok { - continue - } - - amount, err := currency.GetAmountWithPrecisionFromString(paymentEl.Transfer.Amount.Amount.String(), precision) - if err != nil { - return err - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: paymentEl.PaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - Reference: paymentEl.PaymentID, - Type: paymentType, - ConnectorID: connectorID, - Status: matchPaymentStatus(paymentEl.Status), - Scheme: models.PaymentSchemeOther, - Amount: amount, - InitialAmount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, paymentEl.Transfer.Amount.Currency), - RawData: raw, - }, - } - - if paymentEl.DebtorInformation.AccountID != "" { - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: paymentEl.DebtorInformation.AccountID, - ConnectorID: connectorID, - } - } - - if paymentEl.CreditorInformation.AccountID != "" { - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: paymentEl.CreditorInformation.AccountID, - ConnectorID: connectorID, - } - } - - batch = append(batch, batchElement) - } - - if err := ingester.IngestPayments(ctx, batch); err != nil { - return err - } - - return nil -} - -func matchPaymentStatus(paymentStatus string) models.PaymentStatus { - switch paymentStatus { - case "Processed": - return models.PaymentStatusSucceeded - // On MissingFunding - the payment is still in progress. - // If there will be funds available within 10 days - the payment will be processed. - // Otherwise - it will be cancelled. - case "PendingProcessing", "MissingFunding": - return models.PaymentStatusPending - case "Rejected", "Cancelled", "Reversed", "Returned": - return models.PaymentStatusFailed - } - - return models.PaymentStatusOther -} - -func matchPaymentType(paymentType string) models.PaymentType { - switch paymentType { - case "Incoming": - return models.PaymentTypePayIn - case "Outgoing": - return models.PaymentTypePayOut - case "Own": - return models.PaymentTypeTransfer - } - - return models.PaymentTypeOther -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_main.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_main.go deleted file mode 100644 index b4ea3fc865..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_main.go +++ /dev/null @@ -1,50 +0,0 @@ -package bankingcircle - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_payments.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_payments.go deleted file mode 100644 index 043217c176..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_payments.go +++ /dev/null @@ -1,377 +0,0 @@ -package bankingcircle - -import ( - "context" - "encoding/json" - "errors" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle/client" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskInitiatePayment(bankingCircleClient *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, bankingCircleClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - bankingCircleClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("no source account provided") - return err - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - var curr string - var precision int - curr, precision, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - amount, err := currency.GetStringAmountFromBigIntWithPrecision(transfer.Amount, precision) - if err != nil { - return err - } - - var sourceAccount *client.Account - sourceAccount, err = bankingCircleClient.GetAccount(ctx, transfer.SourceAccountID.Reference) - if err != nil { - return err - } - if len(sourceAccount.AccountIdentifiers) == 0 { - err = errors.New("no source account identifiers provided") - return err - } - - var destinationAccount *client.Account - destinationAccount, err = bankingCircleClient.GetAccount(ctx, transfer.DestinationAccountID.Reference) - if err != nil { - return err - } - if len(destinationAccount.AccountIdentifiers) == 0 { - err = errors.New("no destination account identifiers provided") - return err - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *client.PaymentResponse - resp, err = bankingCircleClient.InitiateTransferOrPayouts(ctx, &client.PaymentRequest{ - IdempotencyKey: transfer.ID.Reference, - RequestedExecutionDate: transfer.ScheduledAt, - DebtorAccount: client.PaymentAccount{ - Account: sourceAccount.AccountIdentifiers[0].Account, - FinancialInstitution: sourceAccount.AccountIdentifiers[0].FinancialInstitution, - Country: sourceAccount.AccountIdentifiers[0].Country, - }, - DebtorReference: transfer.Description, - CurrencyOfTransfer: curr, - Amount: struct { - Currency string "json:\"currency\"" - Amount json.Number "json:\"amount\"" - }{ - Currency: curr, - Amount: json.Number(amount), - }, - ChargeBearer: "SHA", - CreditorAccount: &client.PaymentAccount{ - Account: destinationAccount.AccountIdentifiers[0].Account, - FinancialInstitution: destinationAccount.AccountIdentifiers[0].FinancialInstitution, - Country: destinationAccount.AccountIdentifiers[0].Country, - }, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.PaymentID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - var resp *client.PaymentResponse - resp, err = bankingCircleClient.InitiateTransferOrPayouts(ctx, &client.PaymentRequest{ - IdempotencyKey: transfer.ID.Reference, - RequestedExecutionDate: transfer.ScheduledAt, - DebtorAccount: client.PaymentAccount{ - Account: sourceAccount.AccountIdentifiers[0].Account, - FinancialInstitution: sourceAccount.AccountIdentifiers[0].FinancialInstitution, - Country: sourceAccount.AccountIdentifiers[0].Country, - }, - DebtorReference: transfer.Description, - CurrencyOfTransfer: curr, - Amount: struct { - Currency string "json:\"currency\"" - Amount json.Number "json:\"amount\"" - }{ - Currency: curr, - Amount: json.Number(amount), - }, - ChargeBearer: "SHA", - CreditorAccount: &client.PaymentAccount{ - Account: destinationAccount.AccountIdentifiers[0].Account, - FinancialInstitution: destinationAccount.AccountIdentifiers[0].FinancialInstitution, - Country: destinationAccount.AccountIdentifiers[0].Country, - }, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.PaymentID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - var taskDescriptor models.TaskDescriptor - taskDescriptor, err = models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - bankingCircleClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "bankingcircle.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", pID), - attribute.Int("attempt", attempt), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, bankingCircleClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - bankingCircleClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - var resp *client.StatusResponse - resp, err = bankingCircleClient.GetPaymentStatus(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - case models.TransferInitiationTypePayout: - var resp *client.StatusResponse - resp, err = bankingCircleClient.GetPaymentStatus(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - } - - switch status { - case "PendingApproval", "PendingProcessing", "Hold", "Approved", "ScaPending": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "Processed": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - case "Unknown", "ScaExpired", "ScaFailed", "MissingFunding", - "PendingCancellation", "PendingCancellationApproval", "DeclinedByApprover", - "Rejected", "Cancelled", "Reversed", "ScaDeclined": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, "", time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_resolve.go deleted file mode 100644 index 7d82466fe8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/bankingcircle/task_resolve.go +++ /dev/null @@ -1,72 +0,0 @@ -package bankingcircle - -import ( - "fmt" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/bankingcircle/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/google/uuid" -) - -const ( - taskNameMain = "main" - taskNameFetchPayments = "fetch-payments" - taskNameFetchAccounts = "fetch-accounts" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" - taskNameCreateExternalAccount = "create-external-account" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - BankAccountID uuid.UUID `json:"bankAccountID" yaml:"bankAccountID" bson:"bankAccountID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` -} - -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - bankingCircleClient, err := client.NewClient( - config.Username, - config.Password, - config.Endpoint, - config.AuthorizationEndpoint, - config.UserCertificate, - config.UserCertificateKey, - logger, - ) - if err != nil { - logger.Error(err) - - return func(taskDescriptor TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("cannot build banking circle client: %w", err) - } - } - } - - return func(taskDescriptor TaskDescriptor) task.Task { - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchPayments: - return taskFetchPayments(bankingCircleClient) - case taskNameFetchAccounts: - return taskFetchAccounts(bankingCircleClient) - case taskNameInitiatePayment: - return taskInitiatePayment(bankingCircleClient, taskDescriptor.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(bankingCircleClient, taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt) - case taskNameCreateExternalAccount: - return taskCreateExternalAccount(bankingCircleClient, taskDescriptor.BankAccountID) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/configtemplate/template.go b/components/payments/cmd/connectors/internal/connectors/configtemplate/template.go deleted file mode 100644 index 188dca93b2..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/configtemplate/template.go +++ /dev/null @@ -1,37 +0,0 @@ -package configtemplate - -type Configs map[string]Config - -type Config map[string]Parameter - -type Parameter struct { - DataType Type `json:"dataType"` - Required bool `json:"required"` - DefaultValue string `json:"defaultValue"` -} - -type TemplateBuilder interface { - BuildTemplate() (string, Config) -} - -func BuildConfigs(builders ...TemplateBuilder) Configs { - configs := make(map[string]Config) - for _, builder := range builders { - name, config := builder.BuildTemplate() - configs[name] = config - } - - return configs -} - -func NewConfig() Config { - return make(map[string]Parameter) -} - -func (c *Config) AddParameter(name string, dataType Type, defaultValue string, required bool) { - (*c)[name] = Parameter{ - DataType: dataType, - Required: required, - DefaultValue: defaultValue, - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/configtemplate/types.go b/components/payments/cmd/connectors/internal/connectors/configtemplate/types.go deleted file mode 100644 index ba1181459a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/configtemplate/types.go +++ /dev/null @@ -1,11 +0,0 @@ -package configtemplate - -type Type string - -const ( - TypeLongString Type = "long string" - TypeString Type = "string" - TypeDurationNs Type = "duration ns" - TypeDurationUnsignedInteger Type = "unsigned integer" - TypeBoolean Type = "boolean" -) diff --git a/components/payments/cmd/connectors/internal/connectors/connector.go b/components/payments/cmd/connectors/internal/connectors/connector.go deleted file mode 100644 index 64f3332bd1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/connector.go +++ /dev/null @@ -1,28 +0,0 @@ -package connectors - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -// Connector provide entry point to a payment provider. -type Connector interface { - // Install is used to start the connector. The implementation if in charge of scheduling all required resources. - Install(ctx task.ConnectorContext) error - // Uninstall is used to uninstall the connector. It has to close all related resources opened by the connector. - Uninstall(ctx context.Context) error - // UpdateConfig is used to update the configuration of the connector. - UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error - // Resolve is used to recover state of a failed or restarted task - Resolve(descriptor models.TaskDescriptor) task.Task - // InitiateTransfer is used to initiate a transfer from the connector to a bank account. - InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error - // ReverssePayment is used to reverse a transfer from the connector. - ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error - // CreateExternalBankAccount is used to create a bank account on the connector side. - CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error - // GetSupportedCurrenciesAndDecimals returns a map of supported currencies and their decimals. - SupportedCurrenciesAndDecimals() map[string]int -} diff --git a/components/payments/cmd/connectors/internal/connectors/context.go b/components/payments/cmd/connectors/internal/connectors/context.go deleted file mode 100644 index f685b71822..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/context.go +++ /dev/null @@ -1,19 +0,0 @@ -package connectors - -import ( - "context" - - "github.com/google/uuid" -) - -type webhookIDKey struct{} - -var _webhookIDKey = webhookIDKey{} - -func ContextWithWebhookID(ctx context.Context, id uuid.UUID) context.Context { - return context.WithValue(ctx, _webhookIDKey, id) -} - -func WebhookIDFromContext(ctx context.Context) uuid.UUID { - return ctx.Value(_webhookIDKey).(uuid.UUID) -} diff --git a/components/payments/cmd/connectors/internal/connectors/currency/amount.go b/components/payments/cmd/connectors/internal/connectors/currency/amount.go deleted file mode 100644 index 66342168dd..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currency/amount.go +++ /dev/null @@ -1,120 +0,0 @@ -package currency - -import ( - "bytes" - "fmt" - "math/big" - "strings" - - "github.com/pkg/errors" -) - -// This package provides a way to convert a string amount to a big.Int -// with a given precision. The precision is the number of decimals that -// the amount has. For example, if the amount is 123.45 and the precision is 2, -// the amount will be 12345. -// We also provide a way to convert a big.Int to a string amount with a given -// precision. -// We developed this package because we need to convert amounts from strings, -// and apply the precision to them to have minor units. If we do that with -// the big package using floats and divisions/multiplcations, we will -// sometimes loose precision, which is unacceptable. -// Here is an example of loosing precision with the big package: - -var ( - // ErrInvalidAmount is returned when the amount is invalid - ErrInvalidAmount = fmt.Errorf("invalid amount") - // ErrInvalidPrecision is returned when the precision is inferior to the - // number of decimals in the amount or negative - ErrInvalidPrecision = fmt.Errorf("invalid precision") -) - -func GetAmountWithPrecisionFromString(amountString string, precision int) (*big.Int, error) { - if precision < 0 { - return nil, errors.Wrap(ErrInvalidPrecision, fmt.Sprintf("precision is negative: %d", precision)) - } - - parts := strings.Split(amountString, ".") - - lengthParts := len(parts) - - if lengthParts > 2 || lengthParts == 0 { - // More than one dot, invalid amount - return nil, errors.Wrap(ErrInvalidAmount, fmt.Sprintf("got multiple dots in amount: %s", amountString)) - } - - if lengthParts == 1 { - // No dot, which means it's an integer - for p := 0; p < precision; p++ { - amountString += "0" - } - res, ok := new(big.Int).SetString(amountString, 10) - if !ok { - return nil, errors.Wrap(ErrInvalidAmount, fmt.Sprintf("invalid amount: %s", amountString)) - } - return res, nil - } - - // Here we are in the case where we have one dot, which means we have a - // decimal amount - decimalPart := parts[1] - lengthDecimalPart := len(decimalPart) - switch { - case lengthDecimalPart == precision: - // The decimal part has the same length as the precision, we can - // concatenate the two parts and return the result - res, ok := new(big.Int).SetString(parts[0]+decimalPart, 10) - if !ok { - return nil, errors.Wrap(ErrInvalidAmount, fmt.Sprintf("invalid amount computed: %s from amount %s", parts[0]+decimalPart, amountString)) - } - return res, nil - - case lengthDecimalPart < precision: - // The decimal part is shorter than the precision, we need to add - // some zeros at the end of the decimal part - for p := 0; p < precision-lengthDecimalPart; p++ { - decimalPart += "0" - } - res, ok := new(big.Int).SetString(parts[0]+decimalPart, 10) - if !ok { - return nil, errors.Wrap(ErrInvalidAmount, fmt.Sprintf("invalid amount computed: %s from amount %s", parts[0]+decimalPart, amountString)) - } - return res, nil - - default: - // The decimal part is longer than the precision, we have to send an - // error because we don't want to loose the precision - return nil, ErrInvalidPrecision - } -} - -func GetStringAmountFromBigIntWithPrecision(amount *big.Int, precision int) (string, error) { - if precision < 0 { - return "", errors.Wrap(ErrInvalidPrecision, fmt.Sprintf("precision is negative: %d", precision)) - } - - amountString := amount.String() - amountStringLength := len(amountString) - - if precision == 0 { - // Nothing to do - return amountString, nil - } - - decimalPart := bytes.NewBufferString("") - for p := precision; p > 0; p-- { - if amountStringLength < p { - decimalPart.WriteByte('0') - continue - } - decimalPart.WriteByte(amountString[amountStringLength-p]) - } - - if amountStringLength < precision || amountStringLength == precision { - return "0." + decimalPart.String(), nil - } - - // Here we are in the case where the amount has more digits than the - // precision, we need to add a dot at the right place - return amountString[:amountStringLength-precision] + "." + decimalPart.String(), nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currency/amount_test.go b/components/payments/cmd/connectors/internal/connectors/currency/amount_test.go deleted file mode 100644 index 2967fbae30..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currency/amount_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package currency - -import ( - "errors" - "math/big" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetAmountWithPrecisionFromString(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - amountString string - precision int - expected *big.Int - expecterErr error - } - - testCases := []testCase{ - { - name: "nominal float string with precision 2", - amountString: "123.45", - precision: 2, - expected: big.NewInt(12345), - expecterErr: nil, - }, - { - name: "nominal float string with precision 2 (2)", - amountString: "0.00", - precision: 2, - expected: big.NewInt(0), - expecterErr: nil, - }, - { - name: "nominal float string with precision 0", - amountString: "123.", - precision: 0, - expected: big.NewInt(123), - expecterErr: nil, - }, - { - name: "nominal float string with precision 0 and decimals = precision", - amountString: "123.", - precision: 0, - expected: big.NewInt(123), - expecterErr: nil, - }, - { - name: "nominal float string with precision 0 and decimals = precision", - amountString: "123.4567", - precision: 4, - expected: big.NewInt(1234567), - expecterErr: nil, - }, - { - name: "nominal float string with precision 2 and decimals < precision", - amountString: "123.", - precision: 2, - expected: big.NewInt(12300), - expecterErr: nil, - }, - { - name: "nominal float string with precision 2 and decimals < precision (2)", - amountString: "123.1", - precision: 2, - expected: big.NewInt(12310), - expecterErr: nil, - }, - { - name: "nominal integer string with precision 2", - amountString: "123", - precision: 2, - expected: big.NewInt(12300), - expecterErr: nil, - }, - { - name: "nominal integer string with precision 0", - amountString: "123", - precision: 0, - expected: big.NewInt(123), - expecterErr: nil, - }, - - // Error cases - { - name: "negative precision", - precision: -1, - expected: nil, - expecterErr: ErrInvalidPrecision, - }, - { - name: "invalid amount multiple dots", - amountString: "123.45.67", - precision: 2, - expected: nil, - expecterErr: ErrInvalidAmount, - }, - { - name: "invalid float amount", - amountString: "123.4a", - precision: 2, - expected: nil, - expecterErr: ErrInvalidAmount, - }, - { - name: "invalid float amount (2)", - amountString: "12a3.4", - precision: 2, - expected: nil, - expecterErr: ErrInvalidAmount, - }, - { - name: "invalid integer amount", - amountString: "123a", - precision: 2, - expected: nil, - expecterErr: ErrInvalidAmount, - }, - { - name: "float number with decimal part > precision", - amountString: "123.456", - precision: 2, - expected: nil, - expecterErr: ErrInvalidPrecision, - }, - { - name: "float number with decimal part > precision (2)", - amountString: "123.456", - precision: 0, - expected: nil, - expecterErr: ErrInvalidPrecision, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - amount, err := GetAmountWithPrecisionFromString(tc.amountString, tc.precision) - if tc.expecterErr != nil { - require.True(t, errors.Is(err, tc.expecterErr), "expected error %v, got %v", tc.expecterErr, err) - return - } - - if amount.Cmp(tc.expected) != 0 { - t.Errorf("expected %v, got %v", tc.expected, amount) - } - }) - } -} - -func TestGetStringAmountFromBigIntWithPrecision(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - amount *big.Int - precision int - expectedAmount string - expectedError error - } - - testCases := []testCase{ - { - name: "precision 0", - amount: big.NewInt(12345), - precision: 0, - expectedAmount: "12345", - expectedError: nil, - }, - { - name: "precision 0 (2)", - amount: big.NewInt(0), - precision: 0, - expectedAmount: "0", - expectedError: nil, - }, - { - name: "precision 0 (3)", - amount: big.NewInt(123), - precision: 0, - expectedAmount: "123", - expectedError: nil, - }, - { - name: "precision 2", - amount: big.NewInt(12345), - precision: 2, - expectedAmount: "123.45", - expectedError: nil, - }, - { - name: "precision 2 (2)", - amount: big.NewInt(123), - precision: 2, - expectedAmount: "1.23", - expectedError: nil, - }, - { - name: "precision 2 (3)", - amount: big.NewInt(12), - precision: 2, - expectedAmount: "0.12", - expectedError: nil, - }, - { - name: "precision 2 (4)", - amount: big.NewInt(0), - precision: 2, - expectedAmount: "0.00", - expectedError: nil, - }, - { - name: "precision > length of amount", - amount: big.NewInt(123), - precision: 6, - expectedAmount: "0.000123", - expectedError: nil, - }, - - // Error cases - { - name: "negative precision", - amount: big.NewInt(123), - precision: -1, - expectedAmount: "", - expectedError: ErrInvalidPrecision, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - amount, err := GetStringAmountFromBigIntWithPrecision(tc.amount, tc.precision) - if tc.expectedError != nil { - require.True(t, errors.Is(err, tc.expectedError), "expected error %v, got %v", tc.expectedError, err) - return - } - - if amount != tc.expectedAmount { - t.Errorf("expected %v, got %v", tc.expectedAmount, amount) - } - }) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/currency/currency.go b/components/payments/cmd/connectors/internal/connectors/currency/currency.go deleted file mode 100644 index 3ec4e7ce67..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currency/currency.go +++ /dev/null @@ -1,227 +0,0 @@ -package currency - -import ( - "errors" - "fmt" - "strings" - - "github.com/formancehq/payments/internal/models" -) - -var ( - UnsupportedCurrencies = map[string]struct{}{ - "HUF": {}, - "ISK": {}, - "TWD": {}, - } - - ISO4217Currencies = map[string]int{ - "AFN": 2, // Afghan afghani - "EUR": 2, // Euro - "ALL": 2, // Albanian lek - "DZD": 2, // Algerian dinar - "USD": 2, // United States dollar - "AOA": 2, // Angolan kwanza - "XCD": 2, // East Caribbean dollar - "ARS": 2, // Argentine peso - "AMD": 2, // Armenian dram - "AWG": 2, // Aruban florin - "AUD": 2, // Australian dollar - "AZN": 2, // Azerbaijani manat - "BSD": 2, // Bahamian dollar - "BHD": 3, // Bahraini dinar - "BDT": 2, // Bangladeshi taka - "BBD": 2, // Barbados dollar - "BYN": 2, // Belarusian ruble - "BZD": 2, // Belize dollar - "XOF": 0, // West African CFA franc - "BMD": 2, // Bermudian dollar - "INR": 2, // Indian rupee - "BTN": 2, // Bhutanese ngultrum - "BOB": 2, // Bolivian boliviano - "BOV": 2, // Bolivian Mvdol (funds code) - "BAM": 2, // Bosnia and Herzegovina convertible mark - "BWP": 2, // Botswana pula - "NOK": 2, // Norwegian krone - "BRL": 2, // Brazilian real - "BND": 2, // Brunei dollar - "BGN": 2, // Bulgarian lev - "BIF": 0, // Burundian franc - "CVE": 2, // Cape Verdean escudo - "KHR": 2, // Cambodian riel - "XAF": 0, // Central African CFA franc - "CAD": 2, // Canadian dollar - "KYD": 2, // Cayman Islands dollar - "CLP": 0, // Chilean peso - "CLF": 4, // Unidad de Fomento (funds code) - "CNY": 2, // Chinese yuan - "COP": 2, // Colombian peso - "COU": 2, // Unidad de Valor Real (UVR) (funds code)[7] - "KMF": 0, // Comoro franc - "CDF": 2, // Congolese franc - "NZD": 2, // New Zealand dollar - "CRC": 2, // Costa Rican colon - "HRK": 2, // Croatian kuna - "CUP": 2, // Cuban peso - "CUC": 2, // Cuban convertible peso - "ANG": 2, // Netherlands Antillean guilder - "CZK": 2, // Czech koruna - "DKK": 2, // Danish krone - "DJF": 0, // Djiboutian franc - "DOP": 2, // Dominican peso - "EGP": 2, // Egyptian pound - "SVC": 2, // Salvadoran colón - "ERN": 2, // Eritrean nakfa - "SZL": 2, // Swazi lilangeni - "ETB": 2, // Ethiopian birr - "FKP": 2, // Falkland Islands pound - "FJD": 2, // Fiji dollar - "XPF": 0, // CFP franc - "GMD": 2, // Gambian dalasi - "GEL": 2, // Georgian lari - "GHS": 2, // Ghanaian cedi - "GIP": 2, // Gibraltar pound - "GTQ": 2, // Guatemalan quetzal - "GBP": 2, // Pound sterling - "GNF": 0, // Guinean franc - "GYD": 2, // Guyanese dollar - "HTG": 2, // Haitian gourde - "HNL": 2, // Honduran lempira - "HKD": 2, // Hong Kong dollar - "HUF": 2, // Hungarian forint - "ISK": 0, // Icelandic króna - "IDR": 2, // Indonesian rupiah - "IRR": 2, // Iranian rial - "IQD": 3, // Iraqi dinar - "ILS": 2, // Israeli new shekel - "JMD": 2, // Jamaican dollar - "JPY": 0, // Japanese yen - "JOD": 3, // Jordanian dinar - "KZT": 2, // Kazakhstani tenge - "KES": 2, // Kenyan shilling - "KPW": 2, // North Korean won - "KRW": 0, // South Korean won - "KWD": 3, // Kuwaiti dinar - "KGS": 2, // Kyrgyzstani som - "LAK": 2, // Lao kip - "LBP": 2, // Lebanese pound - "LSL": 2, // Lesotho loti - "ZAR": 2, // South African rand - "LRD": 2, // Liberian dollar - "LYD": 3, // Libyan dinar - "CHF": 2, // Swiss franc - "MOP": 2, // Macanese pataca - "MKD": 2, // Macedonian denar - "MGA": 2, // Malagasy ariary - "MWK": 2, // Malawian kwacha - "MYR": 2, // Malaysian ringgit - "MVR": 2, // Maldivian rufiyaa - "MRU": 2, // Mauritanian ouguiya - "MUR": 2, // Mauritian rupee - "MXN": 2, // Mexican peso - "MXV": 2, // Mexican Unidad de Inversion (UDI) (funds code) - "MDL": 2, // Moldovan leu - "MNT": 2, // Mongolian tögrög - "MAD": 2, // Moroccan dirham - "MZN": 2, // Mozambican metical - "MMK": 2, // Burmese kyat - "NAD": 2, // Namibian dollar - "NPR": 2, // Nepalese rupee - "NIO": 2, // Nicaraguan córdoba - "NGN": 2, // Nigerian naira - "OMR": 3, // Omani rial - "PKR": 2, // Pakistani rupee - "PAB": 2, // Panamanian balboa - "PGK": 2, // Papua New Guinean kina - "PYG": 0, // Paraguayan guaraní - "PEN": 2, // Peruvian sol - "PHP": 2, // Philippine peso - "PLN": 2, // Polish złoty - "QAR": 2, // Qatari riyal - "RON": 2, // Romanian leu - "RUB": 2, // Russian ruble - "RWF": 0, // Rwandan franc - "SHP": 2, // Saint Helena pound - "WST": 2, // Samoan tala - "STN": 2, // São Tomé and Príncipe dobra - "SAR": 2, // Saudi riyal - "RSD": 2, // Serbian dinar - "SCR": 2, // Seychelles rupee - "SLL": 2, // Sierra Leonean leone - "SGD": 2, // Singapore dollar - "SBD": 2, // Solomon Islands dollar - "SOS": 2, // Somali shilling - "SSP": 2, // South Sudanese pound - "LKR": 2, // Sri Lankan rupee - "SDG": 2, // Sudanese pound - "SRD": 2, // Surinamese dollar - "SEK": 2, // Swedish krona/kronor - "CHE": 2, // WIR Euro (complementary currency) - "CHW": 2, // WIR Franc (complementary currency) - "SYP": 2, // Syrian pound - "TWD": 2, // New Taiwan dollar - "TJS": 2, // Tajikistani somoni - "TZS": 2, // Tanzanian shilling - "THB": 2, // Thai baht - "TOP": 2, // Tongan paʻanga - "TTD": 2, // Trinidad and Tobago dollar - "TND": 3, // Tunisian dinar - "TRY": 2, // Turkish lira - "TMT": 2, // Turkmenistan manat - "UGX": 0, // Ugandan shilling - "UAH": 2, // Ukrainian hryvnia - "AED": 2, // United Arab Emirates dirham - "USN": 2, // United States dollar (next day) (funds code) - "UYU": 2, // Uruguayan peso - "UYI": 0, // Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code) - "UYW": 4, // Unidad previsional[9] - "UZS": 2, // Uzbekistan som - "VUV": 0, // Vanuatu vatu - "VES": 2, // Venezuelan bolívar soberano - "VND": 0, // Vietnamese đồng - "YER": 2, // Yemeni rial - "ZMW": 2, // Zambian kwacha - "ZWL": 2, // Zimbabwean dollar A/10 - } -) - -func FormatAsset(currencies map[string]int, cur string) models.Asset { - asset := strings.ToUpper(string(cur)) - - def, ok := currencies[asset] - if !ok { - return models.Asset(asset) - } - - if def == 0 { - return models.Asset(asset) - } - - return models.Asset(fmt.Sprintf("%s/%d", asset, def)) -} - -func GetPrecision(currencies map[string]int, cur string) (int, error) { - asset := strings.ToUpper(string(cur)) - - def, ok := currencies[asset] - if !ok { - return 0, errors.New("missing currencies") - } - - return def, nil -} - -func GetCurrencyAndPrecisionFromAsset(currencies map[string]int, asset models.Asset) (string, int, error) { - parts := strings.Split(asset.String(), "/") - if len(parts) != 2 { - return "", 0, errors.New("invalid asset") - } - - currency := parts[0] - precision, err := GetPrecision(currencies, currency) - if err != nil { - return "", 0, err - } - - return currency, precision, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/accounts.go deleted file mode 100644 index 0d2a2ae609..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/accounts.go +++ /dev/null @@ -1,64 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Account struct { - ID string `json:"id"` - AccountName string `json:"account_name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func (c *Client) GetAccounts(ctx context.Context, page int) ([]*Account, int, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "list_accounts") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - c.buildEndpoint("v2/accounts/find"), http.NoBody) - if err != nil { - return nil, 0, fmt.Errorf("failed to create request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", "25") - q.Add("page", fmt.Sprint(page)) - q.Add("order", "updated_at") - q.Add("order_asc_desc", "asc") - req.URL.RawQuery = q.Encode() - - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - //nolint:tagliatelle // allow for client code - type response struct { - Accounts []*Account `json:"accounts"` - Pagination struct { - NextPage int `json:"next_page"` - } `json:"pagination"` - } - - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err - } - - return res.Accounts, res.Pagination.NextPage, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/auth.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/auth.go deleted file mode 100644 index e846f67876..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/auth.go +++ /dev/null @@ -1,57 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -func (c *Client) authenticate(ctx context.Context) (string, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "authenticate") - now := time.Now() - defer f(ctx, now) - - form := make(url.Values) - - form.Add("login_id", c.loginID) - form.Add("api_key", c.apiKey) - - req, err := http.NewRequest(http.MethodPost, - c.buildEndpoint("v2/authenticate/api"), strings.NewReader(form.Encode())) - if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("failed to do get request: %w", err) - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", unmarshalError(resp.StatusCode, resp.Body).Error() - } - - //nolint:tagliatelle // allow for client code - type response struct { - AuthToken string `json:"auth_token"` - } - - var res response - - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return "", fmt.Errorf("failed to decode response body: %w", err) - } - - return res.AuthToken, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/balances.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/balances.go deleted file mode 100644 index fc0527519a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/balances.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Balance struct { - ID string `json:"id"` - AccountID string `json:"account_id"` - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -func (c *Client) GetBalances(ctx context.Context, page int) ([]*Balance, int, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "list_balances") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, - c.buildEndpoint("v2/balances/find"), http.NoBody) - if err != nil { - return nil, 0, fmt.Errorf("failed to create request: %w", err) - } - - q := req.URL.Query() - q.Add("page", fmt.Sprint(page)) - q.Add("per_page", "25") - q.Add("order", "created_at") - q.Add("order_asc_desc", "asc") - req.URL.RawQuery = q.Encode() - - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - //nolint:tagliatelle // allow for client code - type response struct { - Balances []*Balance `json:"balances"` - Pagination struct { - NextPage int `json:"next_page"` - } `json:"pagination"` - } - - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err - } - - return res.Balances, res.Pagination.NextPage, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/beneficiaries.go deleted file mode 100644 index ea498f5fb9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/beneficiaries.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Beneficiary struct { - ID string `json:"id"` - BankAccountHolderName string `json:"bank_account_holder_name"` - Name string `json:"name"` - Currency string `json:"currency"` - CreatedAt time.Time `json:"created_at"` - // Contains a lot more fields that will be not used on our side for now -} - -func (c *Client) GetBeneficiaries(ctx context.Context, page int) ([]*Beneficiary, int, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "list_beneficiaries") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - c.buildEndpoint("v2/beneficiaries/find"), http.NoBody) - if err != nil { - return nil, 0, fmt.Errorf("failed to create request: %w", err) - } - - q := req.URL.Query() - q.Add("page", fmt.Sprint(page)) - q.Add("per_page", "25") - q.Add("order", "created_at") - q.Add("order_asc_desc", "asc") - req.URL.RawQuery = q.Encode() - - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - //nolint:tagliatelle // allow for client code - type response struct { - Beneficiaries []*Beneficiary `json:"beneficiaries"` - Pagination struct { - NextPage int `json:"next_page"` - } `json:"pagination"` - } - - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err - } - - return res.Beneficiaries, res.Pagination.NextPage, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/client.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/client.go deleted file mode 100644 index 4b11e34032..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/client.go +++ /dev/null @@ -1,74 +0,0 @@ -package client - -import ( - "context" - "fmt" - "net/http" - "time" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -type apiTransport struct { - authToken string - underlying *otelhttp.Transport -} - -func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Add("X-Auth-Token", t.authToken) - - return t.underlying.RoundTrip(req) -} - -type Client struct { - httpClient *http.Client - endpoint string - loginID string - apiKey string -} - -func (c *Client) buildEndpoint(path string, args ...interface{}) string { - return fmt.Sprintf("%s/%s", c.endpoint, fmt.Sprintf(path, args...)) -} - -const DevAPIEndpoint = "https://devapi.currencycloud.com" - -func newAuthenticatedHTTPClient(authToken string) *http.Client { - return &http.Client{ - Transport: &apiTransport{ - authToken: authToken, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - } -} - -func newHTTPClient() *http.Client { - return &http.Client{ - Transport: otelhttp.NewTransport(http.DefaultTransport), - } -} - -// NewClient creates a new client for the CurrencyCloud API. -func NewClient(loginID, apiKey, endpoint string) (*Client, error) { - if endpoint == "" { - endpoint = DevAPIEndpoint - } - - c := &Client{ - httpClient: newHTTPClient(), - endpoint: endpoint, - loginID: loginID, - apiKey: apiKey, - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - authToken, err := c.authenticate(ctx) - if err != nil { - return nil, err - } - - c.httpClient = newAuthenticatedHTTPClient(authToken) - - return c, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/contact.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/contact.go deleted file mode 100644 index e888bac6a8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/contact.go +++ /dev/null @@ -1,57 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Contact struct { - ID string `json:"id"` -} - -func (c *Client) GetContactID(ctx context.Context, accountID string) (*Contact, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "list_contacts") - now := time.Now() - defer f(ctx, now) - - form := url.Values{} - form.Set("account_id", accountID) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - c.buildEndpoint("/v2/contacts/find"), strings.NewReader(form.Encode())) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - type Contacts struct { - Contacts []*Contact `json:"contacts"` - } - - var res Contacts - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - if len(res.Contacts) == 0 { - return nil, fmt.Errorf("no contact found for account %s", accountID) - } - - return res.Contacts[0], nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/error.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/error.go deleted file mode 100644 index 93461a147a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/error.go +++ /dev/null @@ -1,45 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "io" -) - -type currencyCloudError struct { - StatusCode int `json:"status_code"` - ErrorCode string `json:"error_code"` - ErrorMessages map[string][]*errorMessage `json:"error_messages"` -} - -type errorMessage struct { - Code string `json:"code"` - Message string `json:"message"` -} - -func (ce *currencyCloudError) Error() error { - var errorMessage string - if len(ce.ErrorMessages) > 0 { - for _, message := range ce.ErrorMessages { - if len(message) > 0 { - errorMessage = message[0].Message - break - } - } - } - - if errorMessage == "" { - return fmt.Errorf("unexpected status code: %d", ce.StatusCode) - } - - return fmt.Errorf("%s: %s", ce.ErrorCode, errorMessage) -} - -func unmarshalError(statusCode int, body io.ReadCloser) *currencyCloudError { - var ce currencyCloudError - _ = json.NewDecoder(body).Decode(&ce) - - ce.StatusCode = statusCode - - return &ce -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/payout.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/payout.go deleted file mode 100644 index abcd640b5d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/payout.go +++ /dev/null @@ -1,116 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type PayoutRequest struct { - OnBehalfOf string `json:"on_behalf_of"` - BeneficiaryID string `json:"beneficiary_id"` - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - Reference string `json:"reference"` - UniqueRequestID string `json:"unique_request_id"` -} - -func (pr *PayoutRequest) ToFormData() url.Values { - form := url.Values{} - form.Set("on_behalf_of", pr.OnBehalfOf) - form.Set("beneficiary_id", pr.BeneficiaryID) - form.Set("currency", pr.Currency) - form.Set("amount", pr.Amount.String()) - form.Set("reference", pr.Reference) - if pr.UniqueRequestID != "" { - form.Set("unique_request_id", pr.UniqueRequestID) - } - - return form -} - -type PayoutResponse struct { - ID string `json:"id"` - Amount string `json:"amount"` - BeneficiaryID string `json:"beneficiary_id"` - Currency string `json:"currency"` - Reference string `json:"reference"` - Status string `json:"status"` - Reason string `json:"reason"` - CreatorContactID string `json:"creator_contact_id"` - PaymentType string `json:"payment_type"` - TransferredAt string `json:"transferred_at"` - PaymentDate string `json:"payment_date"` - FailureReason string `json:"failure_reason"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - UniqueRequestID string `json:"unique_request_id"` -} - -func (c *Client) InitiatePayout(ctx context.Context, payoutRequest *PayoutRequest) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - form := payoutRequest.ToFormData() - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - c.buildEndpoint("v2/payments/create"), strings.NewReader(form.Encode())) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - var payoutResponse PayoutResponse - if err = json.NewDecoder(resp.Body).Decode(&payoutResponse); err != nil { - return nil, err - } - - return &payoutResponse, nil -} - -func (c *Client) GetPayout(ctx context.Context, payoutID string) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "get_payment") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, - c.buildEndpoint("v2/payments/%s", payoutID), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - var payoutResponse PayoutResponse - if err = json.NewDecoder(resp.Body).Decode(&payoutResponse); err != nil { - return nil, err - } - - return &payoutResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transactions.go deleted file mode 100644 index 6b86b23d2c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transactions.go +++ /dev/null @@ -1,79 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -//nolint:tagliatelle // allow different styled tags in client -type Transaction struct { - ID string `json:"id"` - AccountID string `json:"account_id"` - Currency string `json:"currency"` - Type string `json:"type"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Action string `json:"action"` - - Amount json.Number `json:"amount"` -} - -func (c *Client) GetTransactions(ctx context.Context, page int, updatedAtFrom time.Time) ([]Transaction, int, error) { - if page < 1 { - return nil, 0, fmt.Errorf("page must be greater than 0") - } - - f := connectors.ClientMetrics(ctx, "currencycloud", "list_transactions") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, - c.buildEndpoint("v2/transactions/find"), http.NoBody) - if err != nil { - return nil, 0, fmt.Errorf("failed to create request: %w", err) - } - - q := req.URL.Query() - q.Add("page", fmt.Sprint(page)) - q.Add("per_page", "25") - if !updatedAtFrom.IsZero() { - q.Add("updated_at_from", updatedAtFrom.Format(time.DateOnly)) - } - q.Add("order", "updated_at") - q.Add("order_asc_desc", "asc") - req.URL.RawQuery = q.Encode() - - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - //nolint:tagliatelle // allow for client code - type response struct { - Transactions []Transaction `json:"transactions"` - Pagination struct { - NextPage int `json:"next_page"` - } `json:"pagination"` - } - - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err - } - - return res.Transactions, res.Pagination.NextPage, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transfer.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transfer.go deleted file mode 100644 index 6f75ae6836..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/client/transfer.go +++ /dev/null @@ -1,116 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type TransferRequest struct { - SourceAccountID string `json:"source_account_id"` - DestinationAccountID string `json:"destination_account_id"` - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - Reason string `json:"reason,omitempty"` - UniqueRequestID string `json:"unique_request_id,omitempty"` -} - -func (tr *TransferRequest) ToFormData() url.Values { - form := url.Values{} - form.Set("source_account_id", tr.SourceAccountID) - form.Set("destination_account_id", tr.DestinationAccountID) - form.Set("currency", tr.Currency) - form.Set("amount", fmt.Sprintf("%v", tr.Amount)) - if tr.Reason != "" { - form.Set("reason", tr.Reason) - } - if tr.UniqueRequestID != "" { - form.Set("unique_request_id", tr.UniqueRequestID) - } - - return form -} - -type TransferResponse struct { - ID string `json:"id"` - ShortReference string `json:"short_reference"` - SourceAccountID string `json:"source_account_id"` - DestinationAccountID string `json:"destination_account_id"` - Currency string `json:"currency"` - Amount string `json:"amount"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - CompletedAt string `json:"completed_at"` - CreatorAccountID string `json:"creator_account_id"` - CreatorContactID string `json:"creator_contact_id"` - Reason string `json:"reason"` - UniqueRequestID string `json:"unique_request_id"` -} - -func (c *Client) InitiateTransfer(ctx context.Context, transferRequest *TransferRequest) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "initiate_transfer") - now := time.Now() - defer f(ctx, now) - - form := transferRequest.ToFormData() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - c.buildEndpoint("v2/transfers/create"), strings.NewReader(form.Encode())) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - var res TransferResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} - -func (c *Client) GetTransfer(ctx context.Context, transferID string) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "currencycloud", "get_transfer") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, - c.buildEndpoint("v2/transfers/%s", transferID), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - req.Header.Add("Accept", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - - var res TransferResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/config.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/config.go deleted file mode 100644 index 1512801aba..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/config.go +++ /dev/null @@ -1,65 +0,0 @@ -package currencycloud - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" -) - -const ( - defaultPollingDuration = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" bson:"name"` - LoginID string `json:"loginID" bson:"loginID"` - APIKey string `json:"apiKey" bson:"apiKey"` - Endpoint string `json:"endpoint" bson:"endpoint"` - PollingPeriod connectors.Duration `json:"pollingPeriod" bson:"pollingPeriod"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return fmt.Sprintf("loginID=%s, endpoint=%s, pollingPeriod=%s, apiKey=****", c.LoginID, c.Endpoint, c.PollingPeriod.String()) -} - -func (c Config) Validate() error { - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.LoginID == "" { - return ErrMissingLoginID - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("loginID", configtemplate.TypeString, "", true) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("endpoint", configtemplate.TypeString, client.DevAPIEndpoint, false) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingDuration.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/connector.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/connector.go deleted file mode 100644 index afa1d45fa7..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/connector.go +++ /dev/null @@ -1,136 +0,0 @@ -package currencycloud - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" -) - -const name = models.ConnectorProviderCurrencyCloud - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch users and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transfer *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/currencies.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/currencies.go deleted file mode 100644 index dd05ae31d3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/currencies.go +++ /dev/null @@ -1,51 +0,0 @@ -package currencycloud - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // c.f.: https://support.currencycloud.com/hc/en-gb/articles/7840216562972-Currency-Decimal-Places - supportedCurrenciesWithDecimal = map[string]int{ - "AUD": currency.ISO4217Currencies["AUD"], // Australian Dollar - "CAD": currency.ISO4217Currencies["CAD"], // Canadian Dollar - "CZK": currency.ISO4217Currencies["CZK"], // Czech Koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish Krone - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong Dollar - "INR": currency.ISO4217Currencies["INR"], // Indian Rupee - "IDR": currency.ISO4217Currencies["IDR"], // Indonesia, Rupiah - "ILS": currency.ISO4217Currencies["ILS"], // New Israeli Shekel - "JPY": currency.ISO4217Currencies["JPY"], // Japan, Yen - "KES": currency.ISO4217Currencies["KES"], // Kenyan Shilling - "MYR": currency.ISO4217Currencies["MYR"], // Malaysian Ringgit - "MXN": currency.ISO4217Currencies["MXN"], // Mexican Peso - "NZD": currency.ISO4217Currencies["NZD"], // New Zealand Dollar - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian Krone - "PHP": currency.ISO4217Currencies["PHP"], // Philippine Peso - "PLN": currency.ISO4217Currencies["PLN"], // Poland, Zloty - "RON": currency.ISO4217Currencies["RON"], // Romania, New Leu - "SAR": currency.ISO4217Currencies["SAR"], // Saudi Riyal - "SGD": currency.ISO4217Currencies["SGD"], // Singapore Dollar - "ZAR": currency.ISO4217Currencies["ZAR"], // South Africa, Rand - "SEK": currency.ISO4217Currencies["SEK"], // Swedish Krona - "CHF": currency.ISO4217Currencies["CHF"], // Swiss Franc - "THB": currency.ISO4217Currencies["THB"], // Thailand, Baht - "TRY": currency.ISO4217Currencies["TRY"], // New Turkish Lira - "GBP": currency.ISO4217Currencies["GBP"], // Pound Sterling - "AED": currency.ISO4217Currencies["AED"], // UAE Dirham - "USD": currency.ISO4217Currencies["USD"], // US Dollar - "UGX": currency.ISO4217Currencies["UGX"], // Uganda Shilling - "QAR": currency.ISO4217Currencies["QAR"], // Qatari Riyal - - // Unsupported currencies - // the following currencies are not existing in ISO 4217, so we prefer - // not to support them for now. - // "CNH": 2, // Chinese Yuan - - // The following currencies have a different value in ISO 4217, so we - // prefer to not support them for now. - // "HUF": 0, // Hungarian Forint - // "KWD": 2, // Kuwaiti Dinar - // "OMR": 2, // Rial Omani - // "BHD": 2, // Bahraini Dinar - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/errors.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/errors.go deleted file mode 100644 index 4c4b5b9681..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/errors.go +++ /dev/null @@ -1,23 +0,0 @@ -package currencycloud - -import "github.com/pkg/errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingAPIKey is returned when the api key is missing from config. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingLoginID is returned when the login id is missing from config. - ErrMissingLoginID = errors.New("missing loginID from config") - - // ErrMissingPollingPeriod is returned when the polling period is missing from config. - ErrMissingPollingPeriod = errors.New("missing pollingPeriod from config") - - // ErrDurationInvalid is returned when the duration is invalid. - ErrDurationInvalid = errors.New("duration is invalid") - - // ErrMissingName is returned when the name is missing from config. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/loader.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/loader.go deleted file mode 100644 index e42c2fa6a2..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/loader.go +++ /dev/null @@ -1,49 +0,0 @@ -package currencycloud - -import ( - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = 2 * time.Minute - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_accounts.go deleted file mode 100644 index 012fe99301..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_accounts.go +++ /dev/null @@ -1,160 +0,0 @@ -package currencycloud - -import ( - "context" - "encoding/json" - "errors" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type fetchAccountsState struct { - LastPage int - LastCreatedAt time.Time -} - -func taskFetchAccounts( - client *client.Client, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchAccountsState{}) - if state.LastPage == 0 { - // First run, the first page for currencycloud starts at 1 and not 0 - state.LastPage = 1 - } - - newState, err := fetchAccount(ctx, client, connectorID, ingester, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchAccount( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchAccountsState, -) (fetchAccountsState, error) { - newState := fetchAccountsState{ - LastPage: state.LastPage, - LastCreatedAt: state.LastCreatedAt, - } - - page := state.LastPage - for { - if page < 0 { - break - } - - pagedAccounts, nextPage, err := client.GetAccounts(ctx, page) - if err != nil { - return fetchAccountsState{}, err - } - - page = nextPage - - batch := ingestion.AccountBatch{} - for _, account := range pagedAccounts { - switch account.CreatedAt.Compare(state.LastCreatedAt) { - case -1, 0: - // Account already ingested, skip - continue - default: - } - - raw, err := json.Marshal(account) - if err != nil { - return fetchAccountsState{}, err - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - // Moneycorp does not send the opening date of the account - CreatedAt: account.CreatedAt, - Reference: account.ID, - ConnectorID: connectorID, - AccountName: account.AccountName, - Type: models.AccountTypeInternal, - RawData: raw, - }) - - newState.LastCreatedAt = account.CreatedAt - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return fetchAccountsState{}, err - } - } - - newState.LastPage = page - - taskTransactions, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions from client", - Key: taskNameFetchTransactions, - }) - if err != nil { - return fetchAccountsState{}, err - } - - err = scheduler.Schedule(ctx, taskTransactions, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchAccountsState{}, err - } - - taskBalances, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch balances from client", - Key: taskNameFetchBalances, - }) - if err != nil { - return fetchAccountsState{}, err - } - - err = scheduler.Schedule(ctx, taskBalances, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchAccountsState{}, err - } - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_balances.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_balances.go deleted file mode 100644 index f7ab4f5b6f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_balances.go +++ /dev/null @@ -1,105 +0,0 @@ -package currencycloud - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchBalances( - client *client.Client, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskFetchBalances", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - if err := fetchBalances(ctx, client, connectorID, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchBalances( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, -) error { - page := 1 - for { - if page < 0 { - break - } - - pagedBalances, nextPage, err := client.GetBalances(ctx, page) - if err != nil { - return err - } - - page = nextPage - - if err := ingestBalancesBatch(ctx, connectorID, ingester, pagedBalances); err != nil { - return err - } - } - - return nil -} - -func ingestBalancesBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - balances []*client.Balance, -) error { - batch := ingestion.BalanceBatch{} - for _, balance := range balances { - // No need to check if the currency is supported for accounts and balances. - precision := supportedCurrenciesWithDecimal[balance.Currency] - - amount, err := currency.GetAmountWithPrecisionFromString(balance.Amount.String(), precision) - if err != nil { - return err - } - - now := time.Now() - batch = append(batch, &models.Balance{ - AccountID: models.AccountID{ - Reference: balance.AccountID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Currency), - Balance: amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestBalances(ctx, batch, true); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_beneficiaries.go deleted file mode 100644 index 451c26539b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_beneficiaries.go +++ /dev/null @@ -1,127 +0,0 @@ -package currencycloud - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type fetchBeneficiariesState struct { - LastPage int - LastCreatedAt time.Time -} - -func taskFetchBeneficiaries( - client *client.Client, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskFetchBeneficiaries", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchBeneficiariesState{}) - if state.LastPage == 0 { - // First run, the first page for currencycloud starts at 1 and not 0 - state.LastPage = 1 - } - - newState, err := fetchBeneficiaries(ctx, client, connectorID, ingester, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchBeneficiaries( - ctx context.Context, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchBeneficiariesState, -) (fetchBeneficiariesState, error) { - newState := fetchBeneficiariesState{ - LastPage: state.LastPage, - LastCreatedAt: state.LastCreatedAt, - } - - page := state.LastPage - for { - if page < 0 { - break - } - - pagedBeneficiaries, nextPage, err := client.GetBeneficiaries(ctx, page) - if err != nil { - return fetchBeneficiariesState{}, err - } - - page = nextPage - - batch := ingestion.AccountBatch{} - for _, beneficiary := range pagedBeneficiaries { - switch beneficiary.CreatedAt.Compare(state.LastCreatedAt) { - case -1, 0: - // Account already ingested, skip - continue - default: - } - - raw, err := json.Marshal(beneficiary) - if err != nil { - return fetchBeneficiariesState{}, err - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: beneficiary.ID, - ConnectorID: connectorID, - }, - // Moneycorp does not send the opening date of the account - CreatedAt: beneficiary.CreatedAt, - Reference: beneficiary.ID, - ConnectorID: connectorID, - AccountName: beneficiary.Name, - Type: models.AccountTypeExternal, - RawData: raw, - }) - - newState.LastCreatedAt = beneficiary.CreatedAt - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return fetchBeneficiariesState{}, err - } - } - - newState.LastPage = page - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_transactions.go deleted file mode 100644 index e1fbb5d91c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_fetch_transactions.go +++ /dev/null @@ -1,179 +0,0 @@ -package currencycloud - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type fetchTransactionsState struct { - LastUpdatedAt time.Time -} - -func taskFetchTransactions(client *client.Client, config Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchTransactionsState{}) - - newState, err := ingestTransactions(ctx, connectorID, client, ingester, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestTransactions( - ctx context.Context, - connectorID models.ConnectorID, - client *client.Client, - ingester ingestion.Ingester, - state fetchTransactionsState, -) (fetchTransactionsState, error) { - newState := fetchTransactionsState{ - LastUpdatedAt: state.LastUpdatedAt, - } - - page := 1 - for { - if page < 0 { - break - } - - transactions, nextPage, err := client.GetTransactions(ctx, page, state.LastUpdatedAt) - if err != nil { - return fetchTransactionsState{}, err - } - - page = nextPage - - batch := ingestion.PaymentBatch{} - - for _, transaction := range transactions { - switch transaction.UpdatedAt.Compare(state.LastUpdatedAt) { - case -1, 0: - continue - default: - } - - precision, ok := supportedCurrenciesWithDecimal[transaction.Currency] - if !ok { - continue - } - - amount, err := currency.GetAmountWithPrecisionFromString(transaction.Amount.String(), precision) - if err != nil { - return fetchTransactionsState{}, err - } - - var rawData json.RawMessage - - rawData, err = json.Marshal(transaction) - if err != nil { - return fetchTransactionsState{}, fmt.Errorf("failed to marshal transaction: %w", err) - } - - paymentType := matchTransactionType(transaction.Type) - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transaction.ID, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - Reference: transaction.ID, - CreatedAt: transaction.CreatedAt, - Type: paymentType, - ConnectorID: connectorID, - Status: matchTransactionStatus(transaction.Status), - Scheme: models.PaymentSchemeOther, - Amount: amount, - InitialAmount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transaction.Currency), - RawData: rawData, - }, - } - - switch paymentType { - case models.PaymentTypePayOut: - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: transaction.AccountID, - ConnectorID: connectorID, - } - default: - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: transaction.AccountID, - ConnectorID: connectorID, - } - } - - batch = append(batch, batchElement) - - newState.LastUpdatedAt = transaction.UpdatedAt - } - - err = ingester.IngestPayments(ctx, batch) - if err != nil { - return fetchTransactionsState{}, err - } - } - - return newState, nil -} - -func matchTransactionType(transactionType string) models.PaymentType { - switch transactionType { - case "credit": - return models.PaymentTypePayIn - case "debit": - return models.PaymentTypePayOut - } - - return models.PaymentTypeOther -} - -func matchTransactionStatus(transactionStatus string) models.PaymentStatus { - switch transactionStatus { - case "completed": - return models.PaymentStatusSucceeded - case "pending": - return models.PaymentStatusPending - case "deleted": - return models.PaymentStatusFailed - } - - return models.PaymentStatusOther -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_main.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_main.go deleted file mode 100644 index 2c994ea8af..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_main.go +++ /dev/null @@ -1,68 +0,0 @@ -package currencycloud - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - taskBeneficiaries, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch beneficiaries from client", - Key: taskNameFetchBeneficiaries, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskBeneficiaries, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_payments.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_payments.go deleted file mode 100644 index 30970e0ac0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_payments.go +++ /dev/null @@ -1,328 +0,0 @@ -package currencycloud - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -func taskInitiatePayment(currencyCloudClient *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferInitiationID", transferInitiationID.String()), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, currencyCloudClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - currencyCloudClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("no source account provided") - return err - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - var curr string - var precision int - curr, precision, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - amount, err := currency.GetStringAmountFromBigIntWithPrecision(transfer.Amount, precision) - if err != nil { - return err - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *client.TransferResponse - resp, err = currencyCloudClient.InitiateTransfer(ctx, &client.TransferRequest{ - SourceAccountID: transfer.SourceAccountID.Reference, - DestinationAccountID: transfer.DestinationAccountID.Reference, - Currency: curr, - Amount: json.Number(amount), - Reason: transfer.Description, - UniqueRequestID: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - contact, err := currencyCloudClient.GetContactID(ctx, transfer.SourceAccount.ID.Reference) - if err != nil { - return err - } - - var resp *client.PayoutResponse - resp, err = currencyCloudClient.InitiatePayout(ctx, &client.PayoutRequest{ - OnBehalfOf: contact.ID, - BeneficiaryID: transfer.DestinationAccount.Reference, - Currency: curr, - Amount: json.Number(amount), - Reference: transfer.Description, - UniqueRequestID: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - currencyCloudClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "currencycloud.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferInitiationID", transferInitiationID.String()), - attribute.String("paymentID", paymentID.String()), - attribute.Int("attempt", attempt), - attribute.String("reference", paymentID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, currencyCloudClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - currencyCloudClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var status string - var resultMessage string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - resp, err := currencyCloudClient.GetTransfer(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.Reason - case models.TransferInitiationTypePayout: - resp, err := currencyCloudClient.GetPayout(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.Reason - } - - switch status { - case "pending": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "completed": - err := ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - case "cancelled": - err := ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, resultMessage, time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/currencycloud/task_resolve.go deleted file mode 100644 index f4cdbad536..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/currencycloud/task_resolve.go +++ /dev/null @@ -1,68 +0,0 @@ -package currencycloud - -import ( - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currencycloud/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskNameMain = "main" - taskNameFetchTransactions = "fetch-transactions" - taskNameFetchAccounts = "fetch-accounts" - taskNameFetchBeneficiaries = "fetch-beneficiaries" - taskNameFetchBalances = "fetch-balances" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` -} - -func resolveTasks(config Config) func(taskDefinition TaskDescriptor) task.Task { - currencyCloudClient, err := client.NewClient(config.LoginID, config.APIKey, config.Endpoint) - if err != nil { - return func(taskDefinition TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("failed to initiate client: %w", err) - } - } - } - - return func(taskDescriptor TaskDescriptor) task.Task { - if taskDescriptor.Key == "" { - // Keep the compatibility with previous version if the connector. - // If the key is empty, use the name as the key. - taskDescriptor.Key = taskDescriptor.Name - } - - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchAccounts: - return taskFetchAccounts(currencyCloudClient) - case taskNameFetchBeneficiaries: - return taskFetchBeneficiaries(currencyCloudClient) - case taskNameFetchTransactions: - return taskFetchTransactions(currencyCloudClient, config) - case taskNameFetchBalances: - return taskFetchBalances(currencyCloudClient) - case taskNameInitiatePayment: - return taskInitiatePayment(currencyCloudClient, taskDescriptor.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(currencyCloudClient, taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Name, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/config.go b/components/payments/cmd/connectors/internal/connectors/dummypay/config.go deleted file mode 100644 index 148dad4fbc..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/config.go +++ /dev/null @@ -1,76 +0,0 @@ -package dummypay - -import ( - "encoding/json" - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -// Config is the configuration for the dummy payment connector. -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - - // Directory is the directory where the files are stored. - Directory string `json:"directory" yaml:"directory" bson:"directory"` - - // FilePollingPeriod is the period between file polling. - FilePollingPeriod connectors.Duration `json:"filePollingPeriod" yaml:"filePollingPeriod" bson:"filePollingPeriod"` - - // PrefixFileToIngest is the prefix of the file to ingest. - PrefixFileToIngest string `json:"prefixFileToIngest" yaml:"prefixFileToIngest" bson:"prefixFileToIngest"` - - // NumberOfAccountsPreGenerated is the number of accounts to pre-generate. - NumberOfAccountsPreGenerated int `json:"numberOfAccountsPreGenerated" yaml:"numberOfAccountsPreGenerated" bson:"numberOfAccountsPreGenerated"` - // NumberOfPaymentsPreGenerated is the number of payments to pre-generate. - NumberOfPaymentsPreGenerated int `json:"numberOfPaymentsPreGenerated" yaml:"numberOfPaymentsPreGenerated" bson:"numberOfPaymentsPreGenerated"` -} - -// String returns a string representation of the configuration. -func (c Config) String() string { - return fmt.Sprintf("directory=%s, filePollingPeriod=%s", - c.Directory, c.FilePollingPeriod.String()) -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -// Validate validates the configuration. -func (c Config) Validate() error { - // require directory path to be present - if c.Directory == "" { - return ErrMissingDirectory - } - - // check if file polling period is set properly - if c.FilePollingPeriod.Duration <= 0 { - return fmt.Errorf("filePollingPeriod must be greater than 0: %w", - ErrFilePollingPeriodInvalid) - } - - if c.Name == "" { - return fmt.Errorf("name must be set: %w", ErrMissingName) - } - - return nil -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("directory", configtemplate.TypeString, "", true) - cfg.AddParameter("filePollingPeriod", configtemplate.TypeDurationNs, "", true) - cfg.AddParameter("prefixFileToIngest", configtemplate.TypeString, "", false) - cfg.AddParameter("numberOfAccountsPreGenerated", configtemplate.TypeDurationUnsignedInteger, "0", false) - cfg.AddParameter("numberOfPaymentsPreGenerated", configtemplate.TypeDurationUnsignedInteger, "0", false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/config_test.go b/components/payments/cmd/connectors/internal/connectors/dummypay/config_test.go deleted file mode 100644 index 224746a030..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/config_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package dummypay - -import ( - "os" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/stretchr/testify/assert" -) - -// TestConfigString tests the string representation of the config. -func TestConfigString(t *testing.T) { - t.Parallel() - - config := Config{ - Directory: "test", - FilePollingPeriod: connectors.Duration{Duration: time.Second}, - } - - assert.Equal(t, "directory=test, filePollingPeriod=1s", config.String()) -} - -// TestConfigValidate tests the validation of the config. -func TestConfigValidate(t *testing.T) { - t.Parallel() - - var config Config - - config.Name = "test1" - - // fail on missing directory - assert.EqualError(t, config.Validate(), ErrMissingDirectory.Error()) - - // fail on missing RW access to directory - config.Directory = "/non-existing" - assert.Error(t, config.Validate()) - - // set directory with RW access - userHomeDir, err := os.UserHomeDir() - if err != nil { - t.Error(err) - } - - config.Directory = userHomeDir - - // fail on invalid file polling period - config.FilePollingPeriod.Duration = -1 - assert.ErrorIs(t, config.Validate(), ErrFilePollingPeriodInvalid) - - // success - config.FilePollingPeriod.Duration = 1 - assert.NoError(t, config.Validate()) -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/connector.go b/components/payments/cmd/connectors/internal/connectors/dummypay/connector.go deleted file mode 100644 index a1d201b5d7..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/connector.go +++ /dev/null @@ -1,121 +0,0 @@ -package dummypay - -import ( - "context" - "fmt" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -// name is the name of the connector. -const name = models.ConnectorProviderDummyPay - -// Connector is the connector for the dummy payment connector. -type Connector struct { - logger logging.Logger - cfg Config - fs fs -} - -func newConnector(logger logging.Logger, cfg Config, fs fs) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - fs: fs, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - c.cfg = cfg - - return nil -} - -// Install executes post-installation steps to read and generate files. -// It is called after the connector is installed. -func (c *Connector) Install(ctx task.ConnectorContext) error { - initDirectoryDescriptor, err := models.EncodeTaskDescriptor(newTaskGenerateFiles()) - if err != nil { - return fmt.Errorf("failed to create generate files task descriptor: %w", err) - } - - if err = ctx.Scheduler().Schedule(ctx.Context(), initDirectoryDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - RestartOption: models.OPTIONS_RESTART_NEVER, - }); err != nil { - return fmt.Errorf("failed to schedule task to generate files: %w", err) - } - - readFilesDescriptor, err := models.EncodeTaskDescriptor(newTaskReadFiles()) - if err != nil { - return fmt.Errorf("failed to create read files task descriptor: %w", err) - } - - if err = ctx.Scheduler().Schedule(ctx.Context(), readFilesDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.FilePollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }); err != nil { - return fmt.Errorf("failed to schedule task to read files: %w", err) - } - - return nil -} - -// Uninstall executes pre-uninstallation steps to remove the generated files. -// It is called before the connector is uninstalled. -func (c *Connector) Uninstall(ctx context.Context) error { - c.logger.Infof("Removing generated files from '%s'...", c.cfg.Directory) - - err := removeFiles(c.cfg, c.fs) - if err != nil { - return fmt.Errorf("failed to remove generated files: %w", err) - } - - return nil -} - -// Resolve resolves a task execution request based on the task descriptor. -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - c.logger.Infof("Executing '%s' task...", taskDescriptor.Key) - - return handleResolve(c.cfg, taskDescriptor, c.fs) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - // TODO implement me - return connectors.ErrNotImplemented -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - // TODO implement me - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - // TODO implement me - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/connector_test.go b/components/payments/cmd/connectors/internal/connectors/dummypay/connector_test.go deleted file mode 100644 index bf685f539a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/connector_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package dummypay - -import ( - "context" - "reflect" - "testing" - "time" - - "github.com/formancehq/payments/internal/models" - - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - - "github.com/formancehq/go-libs/logging" - "github.com/stretchr/testify/assert" -) - -// Create a minimal mock for connector installation. -type ( - mockConnectorContext struct { - ctx context.Context - } - mockScheduler struct{} -) - -func (mcc *mockConnectorContext) Context() context.Context { - return mcc.ctx -} - -func (mcc mockScheduler) Schedule(ctx context.Context, p models.TaskDescriptor, options models.TaskSchedulerOptions) error { - return nil -} - -func (mcc *mockConnectorContext) Scheduler() task.Scheduler { - return mockScheduler{} -} - -func TestConnector(t *testing.T) { - t.Parallel() - - config := Config{} - logger := logging.FromContext(context.TODO()) - - fileSystem := newTestFS() - - connector := newConnector(logger, config, fileSystem) - - err := connector.Install(new(mockConnectorContext)) - assert.NoErrorf(t, err, "Install() failed") - - testCases := []struct { - key taskKey - task task.Task - }{ - {taskKeyReadFiles, taskReadFiles(config, fileSystem)}, - {taskKeyInitDirectory, taskGenerateFiles(config, fileSystem)}, - {taskKeyIngest, taskIngest(config, TaskDescriptor{}, fileSystem)}, - } - - for _, testCase := range testCases { - var taskDescriptor models.TaskDescriptor - - taskDescriptor, err = models.EncodeTaskDescriptor(TaskDescriptor{Key: testCase.key}) - assert.NoErrorf(t, err, "EncodeTaskDescriptor() failed") - - assert.EqualValues(t, - reflect.ValueOf(testCase.task).String(), - reflect.ValueOf(connector.Resolve(taskDescriptor)).String(), - ) - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{Key: "test"}) - assert.NoErrorf(t, err, "EncodeTaskDescriptor() failed") - - assert.EqualValues(t, - reflect.ValueOf(func() error { return nil }).String(), - reflect.ValueOf(connector.Resolve(taskDescriptor)).String(), - ) - - assert.NoError(t, connector.Uninstall(context.TODO())) -} - -type MockIngester struct{} - -func (m *MockIngester) IngestAccounts(ctx context.Context, batch ingestion.AccountBatch) error { - return nil -} - -func (m *MockIngester) IngestPayments(ctx context.Context, batch ingestion.PaymentBatch) error { - return nil -} - -func (m *MockIngester) IngestBalances(ctx context.Context, batch ingestion.BalanceBatch, checkIfAccountExists bool) error { - return nil -} - -func (m *MockIngester) UpdateTaskState(ctx context.Context, state any) error { - return nil -} - -func (m *MockIngester) UpdateTransferInitiationPayment(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, status models.TransferInitiationStatus, errorMessage string, updatedAt time.Time) error { - return nil -} - -func (m *MockIngester) UpdateTransferInitiationPaymentsStatus(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, status models.TransferInitiationStatus, errorMessage string, updatedAt time.Time) error { - return nil -} - -func (m *MockIngester) AddTransferInitiationPaymentID(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, updatedAt time.Time) error { - return nil -} - -func (m *MockIngester) UpdateTransferReversalStatus(ctx context.Context, transfer *models.TransferInitiation, transferReversal *models.TransferReversal) error { - return nil -} - -func (m *MockIngester) LinkBankAccountWithAccount(ctx context.Context, bankAccount *models.BankAccount, accountID *models.AccountID) error { - return nil -} - -var _ ingestion.Ingester = (*MockIngester)(nil) diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/currencies.go b/components/payments/cmd/connectors/internal/connectors/dummypay/currencies.go deleted file mode 100644 index 75b0ef3d35..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/currencies.go +++ /dev/null @@ -1,7 +0,0 @@ -package dummypay - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - supportedCurrenciesWithDecimal = currency.ISO4217Currencies -) diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/errors.go b/components/payments/cmd/connectors/internal/connectors/dummypay/errors.go deleted file mode 100644 index 9529b82ce0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/errors.go +++ /dev/null @@ -1,23 +0,0 @@ -package dummypay - -import "github.com/pkg/errors" - -var ( - // ErrMissingDirectory is returned when the directory is missing. - ErrMissingDirectory = errors.New("missing directory to watch") - - // ErrFilePollingPeriodInvalid is returned when the file polling period is invalid. - ErrFilePollingPeriodInvalid = errors.New("file polling period is invalid") - - // ErrFileGenerationPeriodInvalid is returned when the file generation period is invalid. - ErrFileGenerationPeriodInvalid = errors.New("file generation period is invalid") - - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrDurationInvalid is returned when the duration is invalid. - ErrDurationInvalid = errors.New("duration is invalid") - - // ErrMissingName is returned when the name is missing. - ErrMissingName = errors.New("missing name") -) diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/fs.go b/components/payments/cmd/connectors/internal/connectors/dummypay/fs.go deleted file mode 100644 index 5224ccda1d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/fs.go +++ /dev/null @@ -1,12 +0,0 @@ -package dummypay - -import ( - "github.com/spf13/afero" -) - -type fs afero.Fs - -// newFS creates a new file system access point. -func newFS() fs { - return afero.NewOsFs() -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/fs_test.go b/components/payments/cmd/connectors/internal/connectors/dummypay/fs_test.go deleted file mode 100644 index 36d374a586..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/fs_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package dummypay - -import "github.com/spf13/afero" - -func newTestFS() fs { - return afero.NewMemMapFs() -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/loader.go b/components/payments/cmd/connectors/internal/connectors/dummypay/loader.go deleted file mode 100644 index 20702fd3f4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/loader.go +++ /dev/null @@ -1,57 +0,0 @@ -package dummypay - -import ( - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -// Name returns the name of the connector. -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -// AllowTasks returns the amount of tasks that are allowed to be scheduled. -func (l *Loader) AllowTasks() int { - return 10 -} - -const ( - // defaultFilePollingPeriod is the default period between file polling. - defaultFilePollingPeriod = 10 * time.Second -) - -// ApplyDefaults applies default values to the configuration. -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.FilePollingPeriod.Duration == 0 { - cfg.FilePollingPeriod = connectors.Duration{Duration: defaultFilePollingPeriod} - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -// Load returns the connector. -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config, newFS()) -} - -// Router returns the router. -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/loader_test.go b/components/payments/cmd/connectors/internal/connectors/dummypay/loader_test.go deleted file mode 100644 index c8128a1b32..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/loader_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package dummypay - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/stretchr/testify/assert" -) - -// TestLoader tests the loader. -func TestLoader(t *testing.T) { - t.Parallel() - - config := Config{} - logger := logging.FromContext(context.TODO()) - - loader := NewLoader() - - assert.Equal(t, name, loader.Name()) - assert.Equal(t, 10, loader.AllowTasks()) - assert.Equal(t, Config{ - Name: "DUMMY-PAY", - FilePollingPeriod: connectors.Duration{Duration: 10 * time.Second}, - }, loader.ApplyDefaults(config)) - - assert.EqualValues(t, newConnector(logger, config, newFS()), loader.Load(logger, config)) -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/models.go b/components/payments/cmd/connectors/internal/connectors/dummypay/models.go deleted file mode 100644 index 34fe8a9369..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/models.go +++ /dev/null @@ -1,42 +0,0 @@ -package dummypay - -import ( - "math/big" - "time" -) - -type Kind string - -const ( - KindPayment Kind = "payment" - KindAccount Kind = "account" -) - -type object struct { - Kind Kind `json:"kind"` - Payment *payment `json:"payment,omitempty"` - Account *account `json:"account,omitempty"` -} - -// payment represents a payment structure used in the generated files. -type payment struct { - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - Amount *big.Int `json:"amount"` - Asset string `json:"asset"` - Type string `json:"type"` - Status string `json:"status"` - Scheme string `json:"scheme"` - SourceAccountID string `json:"sourceAccountId"` - DestinationAccountID string `json:"destinationAccountId"` - Metadata map[string]string `json:"metadata"` -} - -type account struct { - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - DefaultAsset string `json:"defaultAsset"` - AccountName string `json:"accountName"` - Type string `json:"type"` - Metadata map[string]string `json:"metadata"` -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/remove_files.go b/components/payments/cmd/connectors/internal/connectors/dummypay/remove_files.go deleted file mode 100644 index 06e2eb1a80..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/remove_files.go +++ /dev/null @@ -1,39 +0,0 @@ -package dummypay - -import ( - "fmt" - "os" - "strings" - - "github.com/spf13/afero" -) - -// removeFiles removes all files from the given directory. -// Only removes files that has generatedFilePrefix in the name. -func removeFiles(config Config, fs fs) error { - dir, err := afero.ReadDir(fs, config.Directory) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return fmt.Errorf("failed to open directory '%s': %w", config.Directory, err) - } - - // iterate over all files in the directory - for _, file := range dir { - // skip files that do not match the generatedFilePrefix - if config.PrefixFileToIngest != "" { - if !strings.HasPrefix(file.Name(), generatedFilePrefix) && !strings.HasPrefix(file.Name(), config.PrefixFileToIngest) { - continue - } - } - - // remove the file - err = fs.Remove(fmt.Sprintf("%s/%s", config.Directory, file.Name())) - if err != nil { - return fmt.Errorf("failed to remove file '%s': %w", file.Name(), err) - } - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/task_descriptor.go b/components/payments/cmd/connectors/internal/connectors/dummypay/task_descriptor.go deleted file mode 100644 index 20cb02be8d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/task_descriptor.go +++ /dev/null @@ -1,34 +0,0 @@ -package dummypay - -import ( - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -// taskKey defines a unique key of the task. -type taskKey string - -// TaskDescriptor represents a task descriptor. -type TaskDescriptor struct { - Name string `json:"name" bson:"name" yaml:"name"` - Key taskKey `json:"key" bson:"key" yaml:"key"` - FileName string `json:"fileName" bson:"fileName" yaml:"fileName"` -} - -// handleResolve resolves a task execution request based on the task descriptor. -func handleResolve(config Config, descriptor TaskDescriptor, fs fs) task.Task { - switch descriptor.Key { - case taskKeyReadFiles: - return taskReadFiles(config, fs) - case taskKeyIngest: - return taskIngest(config, descriptor, fs) - case taskKeyInitDirectory: - return taskGenerateFiles(config, fs) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", descriptor.Key, ErrMissingTask) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/task_ingest.go b/components/payments/cmd/connectors/internal/connectors/dummypay/task_ingest.go deleted file mode 100644 index 34d20f4439..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/task_ingest.go +++ /dev/null @@ -1,172 +0,0 @@ -package dummypay - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const ( - taskKeyIngest = "ingest" -) - -// newTaskIngest returns a new task descriptor for the ingest task. -func newTaskIngest(filePath string) TaskDescriptor { - return TaskDescriptor{ - Name: "Ingest accounts and payments from read files", - Key: taskKeyIngest, - FileName: filePath, - } -} - -// taskIngest ingests a payment file. -func taskIngest(config Config, descriptor TaskDescriptor, fs fs) task.Task { - return func( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - ) error { - err := handleFile(ctx, connectorID, ingester, config, descriptor, fs) - if err != nil { - return fmt.Errorf("failed to handle file '%s': %w", descriptor.FileName, err) - } - - return nil - } -} - -func handleFile( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - config Config, - descriptor TaskDescriptor, - fs fs, -) error { - object, err := getObject(config, descriptor, fs) - if err != nil { - return err - } - - switch object.Kind { - case KindAccount: - batch, err := handleAccount(connectorID, object.Account) - if err != nil { - return err - } - - err = ingester.IngestAccounts(ctx, batch) - if err != nil { - return fmt.Errorf("failed to ingest file '%s': %w", descriptor.FileName, err) - } - case KindPayment: - batch, err := handlePayment(connectorID, object.Payment) - if err != nil { - return err - } - - err = ingester.IngestPayments(ctx, batch) - if err != nil { - return fmt.Errorf("failed to ingest file '%s': %w", descriptor.FileName, err) - } - default: - return fmt.Errorf("unknown object kind '%s'", object.Kind) - } - - return nil -} - -func getObject(config Config, descriptor TaskDescriptor, fs fs) (*object, error) { - file, err := fs.Open(filepath.Join(config.Directory, descriptor.FileName)) - if err != nil { - return nil, fmt.Errorf("failed to open file '%s': %w", descriptor.FileName, err) - } - defer file.Close() - - var objectElement object - err = json.NewDecoder(file).Decode(&objectElement) - if err != nil { - return nil, fmt.Errorf("failed to decode file '%s': %w", descriptor.FileName, err) - } - - return &objectElement, nil -} - -func handleAccount(connectorID models.ConnectorID, accountElement *account) (ingestion.AccountBatch, error) { - accountType, err := models.AccountTypeFromString(accountElement.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse account type: %w", err) - } - - raw, err := json.Marshal(accountElement) - if err != nil { - return nil, fmt.Errorf("failed to marshal payment: %w", err) - } - - ingestionPayload := ingestion.AccountBatch{&models.Account{ - ID: models.AccountID{ - Reference: accountElement.Reference, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: accountElement.CreatedAt, - Reference: accountElement.Reference, - DefaultAsset: models.Asset(accountElement.DefaultAsset), - AccountName: accountElement.AccountName, - Type: accountType, - Metadata: map[string]string{}, - RawData: raw, - }} - - return ingestionPayload, nil -} - -func handlePayment(connectorID models.ConnectorID, paymentElement *payment) (ingestion.PaymentBatch, error) { - paymentType, err := models.PaymentTypeFromString(paymentElement.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse payment type '%s': %w", paymentElement.Type, err) - } - - paymentStatus, err := models.PaymentStatusFromString(paymentElement.Status) - if err != nil { - return nil, fmt.Errorf("failed to parse payment status '%s': %w", paymentElement.Status, err) - } - - paymentScheme, err := models.PaymentSchemeFromString(paymentElement.Scheme) - if err != nil { - return nil, fmt.Errorf("failed to parse payment scheme '%s': %w", paymentElement.Scheme, err) - } - - raw, err := json.Marshal(paymentElement) - if err != nil { - return nil, fmt.Errorf("failed to marshal payment: %w", err) - } - - ingestionPayload := ingestion.PaymentBatch{ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: paymentElement.Reference, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - Reference: paymentElement.Reference, - ConnectorID: connectorID, - Amount: paymentElement.Amount, - InitialAmount: paymentElement.Amount, - Type: paymentType, - Status: paymentStatus, - Scheme: paymentScheme, - Asset: models.Asset(paymentElement.Asset), - RawData: raw, - }, - }} - - return ingestionPayload, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/task_init_directory.go b/components/payments/cmd/connectors/internal/connectors/dummypay/task_init_directory.go deleted file mode 100644 index a8c93782c4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/task_init_directory.go +++ /dev/null @@ -1,302 +0,0 @@ -package dummypay - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "math/rand" - "os" - "time" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskKeyInitDirectory = "init-directory" - asset = "DUMMYCOIN" - generatedFilePrefix = "dummypay-generated-file" -) - -// newTaskGenerateFiles returns a new task descriptor for the task that generates files. -func newTaskGenerateFiles() TaskDescriptor { - return TaskDescriptor{ - Name: "Generate files into a directory", - Key: taskKeyInitDirectory, - } -} - -// taskGenerateFiles generates payment files to a given directory. -func taskGenerateFiles(config Config, fs fs) task.Task { - return func(ctx context.Context, ingester ingestion.Ingester, connectorID models.ConnectorID) error { - err := fs.Mkdir(config.Directory, 0o777) //nolint:gomnd - if err != nil && !os.IsExist(err) { - return fmt.Errorf( - "failed to create dummypay config directory '%s': %w", config.Directory, err) - } - - var accountIDs []*models.AccountID - for i := 0; i < config.NumberOfAccountsPreGenerated; i++ { - accountID, err := generateAccountsFile(ctx, connectorID, ingester, config, fs) - if err != nil { - return fmt.Errorf("failed to generate accounts file: %w", err) - } - - accountIDs = append(accountIDs, accountID) - } - - for i := 0; i < config.NumberOfPaymentsPreGenerated; i++ { - if err := generatePaymentsFile(ctx, generatedFilePrefix, connectorID, ingester, accountIDs, config, fs); err != nil { - return fmt.Errorf("failed to generate payments files: %w", err) - } - } - - return nil - } -} - -func generateAccountsFile( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - config Config, - fs fs, -) (*models.AccountID, error) { - name := fmt.Sprintf("account-%d", time.Now().UnixNano()) - key := fmt.Sprintf("%s-%s", generatedFilePrefix, name) - fileKey := fmt.Sprintf("%s/%s.json", config.Directory, key) - - generatedAccount := account{ - Reference: uuid.New().String(), - CreatedAt: time.Now(), - DefaultAsset: asset, - AccountName: name, - Type: generateRandomAccountType().String(), - Metadata: map[string]string{}, - } - - file, err := fs.Create(fileKey) - if err != nil { - return nil, fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - // Encode the payment object as JSON to a new file. - err = json.NewEncoder(file).Encode(&object{ - Kind: KindAccount, - Account: &generatedAccount, - }) - if err != nil { - return nil, fmt.Errorf("failed to encode json into file: %w", err) - } - - raw, err := json.Marshal(generatedAccount) - if err != nil { - return nil, fmt.Errorf("failed to marshal account: %w", err) - } - - accountID := models.AccountID{ - Reference: generatedAccount.Reference, - ConnectorID: connectorID, - } - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch{ - { - ID: accountID, - ConnectorID: connectorID, - CreatedAt: generatedAccount.CreatedAt, - Reference: generatedAccount.Reference, - DefaultAsset: asset, - AccountName: name, - Type: models.AccountType(generatedAccount.Type), - Metadata: map[string]string{}, - RawData: raw, - }, - }); err != nil { - return nil, fmt.Errorf("failed to ingest accounts: %w", err) - } - - return &accountID, nil -} - -func generatePaymentsFile( - ctx context.Context, - prefix string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accountIDs []*models.AccountID, - config Config, - fs fs, -) error { - name := fmt.Sprintf("payment-%d", time.Now().UnixNano()) - key := name - if prefix != "" { - key = fmt.Sprintf("%s-%s", generatedFilePrefix, name) - } - fileKey := fmt.Sprintf("%s/%s.json", config.Directory, key) - - generatedPayment := payment{ - Reference: uuid.New().String(), - CreatedAt: time.Now(), - Amount: big.NewInt(int64(rand.Intn(10000))), - Asset: asset, - Type: generateRandomPaymentType().String(), - Status: generateRandomStatus().String(), - Scheme: generateRandomScheme().String(), - Metadata: map[string]string{}, - } - - var sourceAccountID, destinationAccountID *models.AccountID - if len(accountIDs) != 0 { - if generateRandomNumber() > nMax/2 { - sourceAccountID = accountIDs[generateRandomNumber()%len(accountIDs)] - generatedPayment.SourceAccountID = sourceAccountID.String() - } else { - destinationAccountID = accountIDs[generateRandomNumber()%len(accountIDs)] - generatedPayment.DestinationAccountID = destinationAccountID.String() - } - } - - file, err := fs.Create(fileKey) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - // Encode the payment object as JSON to a new file. - err = json.NewEncoder(file).Encode(&object{ - Kind: KindPayment, - Payment: &generatedPayment, - }) - if err != nil { - return fmt.Errorf("failed to encode json into file: %w", err) - } - - raw, err := json.Marshal(generatedPayment) - if err != nil { - return fmt.Errorf("failed to marshal payment: %w", err) - } - if err := ingester.IngestPayments(ctx, ingestion.PaymentBatch{ - { - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: generatedPayment.Reference, - Type: models.PaymentType(generatedPayment.Type), - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: generatedPayment.CreatedAt, - Reference: generatedPayment.Reference, - Amount: generatedPayment.Amount, - InitialAmount: generatedPayment.Amount, - Type: models.PaymentType(generatedPayment.Type), - Status: models.PaymentStatus(generatedPayment.Status), - Scheme: models.PaymentScheme(generatedPayment.Scheme), - Asset: asset, - RawData: raw, - SourceAccountID: sourceAccountID, - DestinationAccountID: destinationAccountID, - }, - }, - }); err != nil { - return fmt.Errorf("failed to ingest payments: %w", err) - } - - return nil -} - -// nMax is the maximum number that can be generated -// with the minimum being 0. -const nMax = 10000 - -func generateRandomAccountType() models.AccountType { - // 50% chance. - accountType := models.AccountTypeInternal - - // 50% chance. - if generateRandomNumber() > nMax/2 { - accountType = models.AccountTypeExternal - } - - return accountType -} - -// generateRandomNumber generates a random number between 0 and nMax. -func generateRandomNumber() int { - rand.Seed(time.Now().UnixNano()) - - //nolint:gosec // allow weak random number generator as it is not used for security - value := rand.Intn(nMax) - - return value -} - -// generateRandomType generates a random payment type. -func generateRandomPaymentType() models.PaymentType { - paymentType := models.PaymentTypePayIn - - num := generateRandomNumber() - switch { - case num < nMax/4: // 25% chance - paymentType = models.PaymentTypePayOut - case num < nMax/3: // ~9% chance - paymentType = models.PaymentTypeTransfer - } - - return paymentType -} - -// generateRandomStatus generates a random payment status. -func generateRandomStatus() models.PaymentStatus { - // ~50% chance. - paymentStatus := models.PaymentStatusSucceeded - - num := generateRandomNumber() - - switch { - case num < nMax/4: // 25% chance - paymentStatus = models.PaymentStatusPending - case num < nMax/3: // ~9% chance - paymentStatus = models.PaymentStatusFailed - case num < nMax/2: // ~16% chance - paymentStatus = models.PaymentStatusCancelled - } - - return paymentStatus -} - -// generateRandomScheme generates a random payment scheme. -func generateRandomScheme() models.PaymentScheme { - num := generateRandomNumber() / 1000 //nolint:gomnd // allow for random number - - paymentScheme := models.PaymentSchemeCardMasterCard - - availableSchemes := []models.PaymentScheme{ - models.PaymentSchemeCardMasterCard, - models.PaymentSchemeCardVisa, - models.PaymentSchemeCardDiscover, - models.PaymentSchemeCardJCB, - models.PaymentSchemeCardUnionPay, - models.PaymentSchemeCardAmex, - models.PaymentSchemeCardDiners, - models.PaymentSchemeSepaDebit, - models.PaymentSchemeSepaCredit, - models.PaymentSchemeApplePay, - models.PaymentSchemeGooglePay, - models.PaymentSchemeA2A, - models.PaymentSchemeACHDebit, - models.PaymentSchemeACH, - models.PaymentSchemeRTP, - } - - if num < len(availableSchemes) { - paymentScheme = availableSchemes[num] - } - - return paymentScheme -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/task_read_files.go b/components/payments/cmd/connectors/internal/connectors/dummypay/task_read_files.go deleted file mode 100644 index 88f30ca5e3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/task_read_files.go +++ /dev/null @@ -1,92 +0,0 @@ -package dummypay - -import ( - "context" - "fmt" - "os" - "strings" - - "github.com/formancehq/payments/internal/models" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/spf13/afero" -) - -const ( - taskKeyReadFiles = "read-files" -) - -// newTaskReadFiles creates a new task descriptor for the taskReadFiles task. -func newTaskReadFiles() TaskDescriptor { - return TaskDescriptor{ - Name: "Read Files from directory", - Key: taskKeyReadFiles, - } -} - -// taskReadFiles creates a task that reads files from a given directory. -// Only reads files with the generatedFilePrefix in their name. -func taskReadFiles(config Config, fs fs) task.Task { - return func(ctx context.Context, logger logging.Logger, - scheduler task.Scheduler, - ) error { - err := fs.Mkdir(config.Directory, 0o777) //nolint:gomnd - if err != nil && !os.IsExist(err) { - return fmt.Errorf( - "failed to create dummypay config directory '%s': %w", config.Directory, err) - } - - files, err := parseFilesToIngest(config, fs) - if err != nil { - return fmt.Errorf("error parsing files to ingest: %w", err) - } - - for _, file := range files { - descriptor, err := models.EncodeTaskDescriptor(newTaskIngest(file)) - if err != nil { - return err - } - - // schedule a task to ingest the file into the payments system. - err = scheduler.Schedule(ctx, descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - if err != nil { - return fmt.Errorf("failed to schedule task to ingest file '%s': %w", file, err) - } - - } - - return nil - } -} - -func parseFilesToIngest(config Config, fs fs) ([]string, error) { - dir, err := afero.ReadDir(fs, config.Directory) - if err != nil { - return nil, fmt.Errorf("error reading directory '%s': %w", config.Directory, err) - } - - var files []string //nolint:prealloc // length is unknown - - // iterate over all files in the directory. - for _, file := range dir { - // skip files that match the generatedFilePrefix because they were already ingested. - if strings.HasPrefix(file.Name(), generatedFilePrefix) { - continue - } - - if config.PrefixFileToIngest != "" { - // skip files that do not match the toIngestFilePrefix. - if !strings.HasPrefix(file.Name(), config.PrefixFileToIngest) { - continue - } - } - - files = append(files, file.Name()) - } - - return files, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/dummypay/task_test.go b/components/payments/cmd/connectors/internal/connectors/dummypay/task_test.go deleted file mode 100644 index f7b79a4413..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/dummypay/task_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package dummypay - -import ( - "context" - "testing" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" -) - -func TestTasks(t *testing.T) { - t.Parallel() - - config := Config{Directory: "/tmp"} - fs := newTestFS() - - // test generating files - err := generatePaymentsFile(context.Background(), "", models.ConnectorID{}, &MockIngester{}, []*models.AccountID{}, config, fs) - assert.NoError(t, err) - - files, err := afero.ReadDir(fs, config.Directory) - assert.NoError(t, err) - assert.Len(t, files, 1) - - // test reading files - filesList, err := parseFilesToIngest(config, fs) - assert.NoError(t, err) - assert.Len(t, filesList, 1) - - // test getting object - object, err := getObject(config, TaskDescriptor{Key: taskKeyIngest, FileName: files[0].Name()}, fs) - assert.NoError(t, err) - assert.NotNil(t, object) - assert.NotNil(t, object.Payment) - - // test ingesting files - payload, err := handlePayment(models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }, object.Payment) - assert.NoError(t, err) - assert.Len(t, payload, 1) - - // test removing files - err = removeFiles(config, fs) - assert.NoError(t, err) - - files, err = afero.ReadDir(fs, config.Directory) - assert.NoError(t, err) - assert.Len(t, files, 0) -} diff --git a/components/payments/cmd/connectors/internal/connectors/duration.go b/components/payments/cmd/connectors/internal/connectors/duration.go deleted file mode 100644 index c6e5ee31d1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/duration.go +++ /dev/null @@ -1,45 +0,0 @@ -package connectors - -import ( - "encoding/json" - "fmt" - "time" -) - -type Duration struct { - time.Duration `json:"duration"` -} - -func (d *Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Duration.String()) -} - -func (d *Duration) UnmarshalJSON(b []byte) error { - var rawValue any - - if err := json.Unmarshal(b, &rawValue); err != nil { - return fmt.Errorf("custom Duration UnmarshalJSON: %w", err) - } - - switch value := rawValue.(type) { - case string: - var err error - d.Duration, err = time.ParseDuration(value) - if err != nil { - return fmt.Errorf("custom Duration UnmarshalJSON: time.ParseDuration: %w", err) - } - - return nil - case map[string]interface{}: - switch val := value["duration"].(type) { - case float64: - d.Duration = time.Duration(int64(val)) - - return nil - default: - return fmt.Errorf("custom Duration UnmarshalJSON from map: invalid type: value:%v, type:%T", val, val) - } - default: - return fmt.Errorf("custom Duration UnmarshalJSON: invalid type: value:%v, type:%T", value, value) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/errors.go b/components/payments/cmd/connectors/internal/connectors/errors.go deleted file mode 100644 index 2f4b28d47e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/errors.go +++ /dev/null @@ -1,8 +0,0 @@ -package connectors - -import "errors" - -var ( - ErrNotImplemented = errors.New("not implemented") - ErrInvalidConfig = errors.New("invalid config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/generic/client/accounts.go deleted file mode 100644 index b2cc74ffdc..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/accounts.go +++ /dev/null @@ -1,27 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/genericclient" -) - -func (c *Client) ListAccounts(ctx context.Context, page, pageSize int64) ([]genericclient.Account, error) { - f := connectors.ClientMetrics(ctx, "generic", "list_accounts") - now := time.Now() - defer f(ctx, now) - - req := c.apiClient.DefaultApi. - GetAccounts(ctx). - Page(page). - PageSize(pageSize) - - accounts, _, err := req.Execute() - if err != nil { - return nil, err - } - - return accounts, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/balances.go b/components/payments/cmd/connectors/internal/connectors/generic/client/balances.go deleted file mode 100644 index ce5f085b0c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/balances.go +++ /dev/null @@ -1,24 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/genericclient" -) - -func (c *Client) GetBalances(ctx context.Context, accountID string) (*genericclient.Balances, error) { - f := connectors.ClientMetrics(ctx, "generic", "get_balance") - now := time.Now() - defer f(ctx, now) - - req := c.apiClient.DefaultApi.GetAccountBalances(ctx, accountID) - - balances, _, err := req.Execute() - if err != nil { - return nil, err - } - - return balances, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/generic/client/beneficiaries.go deleted file mode 100644 index 5e3c2196b0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/beneficiaries.go +++ /dev/null @@ -1,31 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/genericclient" -) - -func (c *Client) ListBeneficiaries(ctx context.Context, page, pageSize int64, createdAtFrom time.Time) ([]genericclient.Beneficiary, error) { - f := connectors.ClientMetrics(ctx, "generic", "list_beneficiaries") - now := time.Now() - defer f(ctx, now) - - req := c.apiClient.DefaultApi. - GetBeneficiaries(ctx). - Page(page). - PageSize(pageSize) - - if !createdAtFrom.IsZero() { - req = req.CreatedAtFrom(createdAtFrom) - } - - beneficiaries, _, err := req.Execute() - if err != nil { - return nil, err - } - - return beneficiaries, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/client.go b/components/payments/cmd/connectors/internal/connectors/generic/client/client.go deleted file mode 100644 index 7388a96d68..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/client.go +++ /dev/null @@ -1,44 +0,0 @@ -package client - -import ( - "fmt" - "net/http" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/genericclient" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -type apiTransport struct { - APIKey string - underlying http.RoundTripper -} - -func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.APIKey)) - - return t.underlying.RoundTrip(req) -} - -type Client struct { - apiClient *genericclient.APIClient -} - -func NewClient(apiKey, baseURL string, logger logging.Logger) *Client { - httpClient := &http.Client{ - Transport: &apiTransport{ - APIKey: apiKey, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - } - - configuration := genericclient.NewConfiguration() - configuration.HTTPClient = httpClient - configuration.Servers[0].URL = baseURL - - genericClient := genericclient.NewAPIClient(configuration) - - return &Client{ - apiClient: genericClient, - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.gitignore b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.gitignore deleted file mode 100644 index daf913b1b3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator-ignore b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator-ignore deleted file mode 100644 index 7484ee590a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator-ignore +++ /dev/null @@ -1,23 +0,0 @@ -# OpenAPI Generator Ignore -# Generated by openapi-generator https://github.com/openapitools/openapi-generator - -# Use this file to prevent files from being overwritten by the generator. -# The patterns follow closely to .gitignore or .dockerignore. - -# As an example, the C# client generator defines ApiClient.cs. -# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: -#ApiClient.cs - -# You can match any string of characters against a directory, file or extension with a single asterisk (*): -#foo/*/qux -# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux - -# You can recursively match patterns against a directory, file or extension with a double asterisk (**): -#foo/**/qux -# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux - -# You can also negate patterns with an exclamation (!). -# For example, you can ignore all files in a docs folder with the file extension .md: -#docs/*.md -# Then explicitly reverse the ignore rule for a single file: -#!docs/README.md diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/FILES b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/FILES deleted file mode 100644 index ba9823d79a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/FILES +++ /dev/null @@ -1,31 +0,0 @@ -.gitignore -.openapi-generator-ignore -.travis.yml -README.md -api/openapi.yaml -api_default.go -client.go -configuration.go -docs/Account.md -docs/Balance.md -docs/Balances.md -docs/Beneficiary.md -docs/DefaultApi.md -docs/Error.md -docs/Transaction.md -docs/TransactionStatus.md -docs/TransactionType.md -git_push.sh -go.mod -go.sum -model_account.go -model_balance.go -model_balances.go -model_beneficiary.go -model_error.go -model_transaction.go -model_transaction_status.go -model_transaction_type.go -response.go -test/api_default_test.go -utils.go diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/VERSION b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/VERSION deleted file mode 100644 index cd802a1ec4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.openapi-generator/VERSION +++ /dev/null @@ -1 +0,0 @@ -6.6.0 \ No newline at end of file diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.travis.yml b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.travis.yml deleted file mode 100644 index f5cb2ce9a5..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go - -install: - - go get -d -v . - -script: - - go build -v ./ - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/README.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/README.md deleted file mode 100644 index b67617e452..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/README.md +++ /dev/null @@ -1,122 +0,0 @@ -# Go API client for genericclient - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -## Overview -This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client. - -- API version: v0.1 -- Package version: latest -- Build package: org.openapitools.codegen.languages.GoClientCodegen - -## Installation - -Install the following dependencies: - -```shell -go get github.com/stretchr/testify/assert -go get golang.org/x/net/context -``` - -Put the package under your project folder and add the following in import: - -```golang -import genericclient "github.com/formancehq/payments/genericclient" -``` - -To use a proxy, set the environment variable `HTTP_PROXY`: - -```golang -os.Setenv("HTTP_PROXY", "http://proxy_name:proxy_port") -``` - -## Configuration of Server URL - -Default configuration comes with `Servers` field that contains server objects as defined in the OpenAPI specification. - -### Select Server Configuration - -For using other server than the one defined on index 0 set context value `sw.ContextServerIndex` of type `int`. - -```golang -ctx := context.WithValue(context.Background(), genericclient.ContextServerIndex, 1) -``` - -### Templated Server URL - -Templated server URL is formatted using default variables from configuration or from context value `sw.ContextServerVariables` of type `map[string]string`. - -```golang -ctx := context.WithValue(context.Background(), genericclient.ContextServerVariables, map[string]string{ - "basePath": "v2", -}) -``` - -Note, enum values are always validated and all unused variables are silently ignored. - -### URLs Configuration per Operation - -Each operation can use different server URL defined using `OperationServers` map in the `Configuration`. -An operation is uniquely identified by `"{classname}Service.{nickname}"` string. -Similar rules for overriding default operation server index and variables applies by using `sw.ContextOperationServerIndices` and `sw.ContextOperationServerVariables` context maps. - -```golang -ctx := context.WithValue(context.Background(), genericclient.ContextOperationServerIndices, map[string]int{ - "{classname}Service.{nickname}": 2, -}) -ctx = context.WithValue(context.Background(), genericclient.ContextOperationServerVariables, map[string]map[string]string{ - "{classname}Service.{nickname}": { - "port": "8443", - }, -}) -``` - -## Documentation for API Endpoints - -All URIs are relative to *http://localhost* - -Class | Method | HTTP request | Description ------------- | ------------- | ------------- | ------------- -*DefaultApi* | [**GetAccountBalances**](docs/DefaultApi.md#getaccountbalances) | **Get** /accounts/{accountId}/balances | Get account balance -*DefaultApi* | [**GetAccounts**](docs/DefaultApi.md#getaccounts) | **Get** /accounts | Get all accounts -*DefaultApi* | [**GetBeneficiaries**](docs/DefaultApi.md#getbeneficiaries) | **Get** /beneficiaries | Get all beneficiaries -*DefaultApi* | [**GetTransactions**](docs/DefaultApi.md#gettransactions) | **Get** /transactions | Get all transactions - - -## Documentation For Models - - - [Account](docs/Account.md) - - [Balance](docs/Balance.md) - - [Balances](docs/Balances.md) - - [Beneficiary](docs/Beneficiary.md) - - [Error](docs/Error.md) - - [Transaction](docs/Transaction.md) - - [TransactionStatus](docs/TransactionStatus.md) - - [TransactionType](docs/TransactionType.md) - - -## Documentation For Authorization - -Endpoints do not require authorization. - - -## Documentation for Utility Methods - -Due to the fact that model structure members are all pointers, this package contains -a number of utility functions to easily obtain pointers to values of basic types. -Each of these functions takes a value of the given basic type and returns a pointer to it: - -* `PtrBool` -* `PtrInt` -* `PtrInt32` -* `PtrInt64` -* `PtrFloat` -* `PtrFloat32` -* `PtrFloat64` -* `PtrString` -* `PtrTime` - -## Author - - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api/openapi.yaml b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api/openapi.yaml deleted file mode 100644 index 7f79fb8276..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api/openapi.yaml +++ /dev/null @@ -1,496 +0,0 @@ -openapi: 3.0.3 -info: - title: GENERIC connector API - version: v0.1 -servers: -- url: / -paths: - /accounts: - get: - operationId: getAccounts - parameters: - - description: Number of items per page - example: 100 - explode: true - in: query - name: pageSize - required: false - schema: - default: 100 - format: int64 - minimum: 1 - type: integer - style: form - - description: Page number - example: 1 - explode: true - in: query - name: page - required: false - schema: - default: 1 - format: int64 - minimum: 1 - type: integer - style: form - - description: Sort order - example: createdAt:asc - explode: true - in: query - name: sort - required: false - schema: - type: string - style: form - - description: Filter by created at date - explode: true - in: query - name: createdAtFrom - required: false - schema: - format: date-time - type: string - style: form - responses: - "200": - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Account' - type: array - description: List of accounts - default: - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: General error - summary: Get all accounts - /accounts/{accountId}/balances: - get: - operationId: getAccountBalances - parameters: - - explode: false - in: path - name: accountId - required: true - schema: - type: string - style: simple - responses: - "200": - content: - application/json: - schema: - $ref: '#/components/schemas/Balances' - description: Account balances - default: - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: General error - summary: Get account balance - /beneficiaries: - get: - operationId: getBeneficiaries - parameters: - - description: Number of items per page - example: 100 - explode: true - in: query - name: pageSize - required: false - schema: - default: 100 - format: int64 - minimum: 1 - type: integer - style: form - - description: Page number - example: 1 - explode: true - in: query - name: page - required: false - schema: - default: 1 - format: int64 - minimum: 1 - type: integer - style: form - - description: Sort order - example: createdAt:asc - explode: true - in: query - name: sort - required: false - schema: - type: string - style: form - - description: Filter by created at date - explode: true - in: query - name: createdAtFrom - required: false - schema: - format: date-time - type: string - style: form - responses: - "200": - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Beneficiary' - type: array - description: List of beneficiaries - default: - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: General error - summary: Get all beneficiaries - /transactions: - get: - operationId: getTransactions - parameters: - - description: Number of items per page - example: 100 - explode: true - in: query - name: pageSize - required: false - schema: - default: 100 - format: int64 - minimum: 1 - type: integer - style: form - - description: Page number - example: 1 - explode: true - in: query - name: page - required: false - schema: - default: 1 - format: int64 - minimum: 1 - type: integer - style: form - - description: Sort order - example: createdAt:asc - explode: true - in: query - name: sort - required: false - schema: - type: string - style: form - - description: Filter by updated at date - explode: true - in: query - name: updatedAtFrom - required: false - schema: - format: date-time - type: string - style: form - responses: - "200": - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Transaction' - type: array - description: List of transactions - default: - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: General error - summary: Get all transactions -components: - parameters: - AccountId: - explode: false - in: path - name: accountId - required: true - schema: - type: string - style: simple - PageSize: - description: Number of items per page - example: 100 - explode: true - in: query - name: pageSize - required: false - schema: - default: 100 - format: int64 - minimum: 1 - type: integer - style: form - Page: - description: Page number - example: 1 - explode: true - in: query - name: page - required: false - schema: - default: 1 - format: int64 - minimum: 1 - type: integer - style: form - Sort: - description: Sort order - example: createdAt:asc - explode: true - in: query - name: sort - required: false - schema: - type: string - style: form - CreatedAtFrom: - description: Filter by created at date - explode: true - in: query - name: createdAtFrom - required: false - schema: - format: date-time - type: string - style: form - UpdatedAtFrom: - description: Filter by updated at date - explode: true - in: query - name: updatedAtFrom - required: false - schema: - format: date-time - type: string - style: form - responses: - NoContent: - description: No content - ErrorResponse: - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - description: General error - Accounts: - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Account' - type: array - description: List of accounts - Balances: - content: - application/json: - schema: - $ref: '#/components/schemas/Balances' - description: Account balances - Beneficiaries: - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Beneficiary' - type: array - description: List of beneficiaries - Transactions: - content: - application/json: - schema: - items: - $ref: '#/components/schemas/Transaction' - type: array - description: List of transactions - schemas: - Error: - properties: - Title: - type: string - Detail: - type: string - required: - - Detail - - Title - type: object - Account: - example: - createdAt: 2000-01-23T04:56:07.000+00:00 - metadata: - key: metadata - accountName: accountName - id: id - properties: - id: - type: string - accountName: - type: string - createdAt: - format: date-time - type: string - metadata: - additionalProperties: - type: string - nullable: true - type: object - required: - - accountName - - createdAt - - id - type: object - Balances: - example: - accountID: accountID - balances: - - amount: amount - currency: currency - - amount: amount - currency: currency - at: 2000-01-23T04:56:07.000+00:00 - id: id - properties: - id: - type: string - accountID: - type: string - at: - format: date-time - type: string - balances: - items: - $ref: '#/components/schemas/Balance' - type: array - required: - - accountID - - at - - balances - - id - type: object - Balance: - example: - amount: amount - currency: currency - properties: - amount: - type: string - currency: - type: string - required: - - amount - - currency - type: object - Beneficiary: - example: - createdAt: 2000-01-23T04:56:07.000+00:00 - metadata: - key: metadata - ownerName: ownerName - id: id - properties: - id: - type: string - createdAt: - format: date-time - type: string - ownerName: - type: string - metadata: - additionalProperties: - type: string - nullable: true - type: object - required: - - createdAt - - id - - ownerName - type: object - Transaction: - example: - createdAt: 2000-01-23T04:56:07.000+00:00 - amount: amount - sourceAccountID: sourceAccountID - metadata: - key: metadata - scheme: scheme - currency: currency - id: id - relatedTransactionID: relatedTransactionID - type: null - destinationAccountID: destinationAccountID - updatedAt: 2000-01-23T04:56:07.000+00:00 - status: null - properties: - id: - type: string - relatedTransactionID: - type: string - createdAt: - format: date-time - type: string - updatedAt: - format: date-time - type: string - currency: - type: string - scheme: - type: string - type: - $ref: '#/components/schemas/TransactionType' - status: - $ref: '#/components/schemas/TransactionStatus' - amount: - type: string - sourceAccountID: - type: string - destinationAccountID: - type: string - metadata: - additionalProperties: - type: string - nullable: true - type: object - required: - - amount - - createdAt - - currency - - id - - status - - type - - updatedAt - type: object - Metadata: - additionalProperties: - type: string - nullable: true - type: object - TransactionType: - enum: - - PAYIN - - PAYOUT - - TRANSFER - type: string - TransactionStatus: - enum: - - PENDING - - SUCCEEDED - - FAILED - type: string diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api_default.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api_default.go deleted file mode 100644 index e196970c4d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/api_default.go +++ /dev/null @@ -1,569 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "bytes" - "context" - "io" - "net/http" - "net/url" - "strings" - "time" -) - - -// DefaultApiService DefaultApi service -type DefaultApiService service - -type ApiGetAccountBalancesRequest struct { - ctx context.Context - ApiService *DefaultApiService - accountId string -} - -func (r ApiGetAccountBalancesRequest) Execute() (*Balances, *http.Response, error) { - return r.ApiService.GetAccountBalancesExecute(r) -} - -/* -GetAccountBalances Get account balance - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @param accountId - @return ApiGetAccountBalancesRequest -*/ -func (a *DefaultApiService) GetAccountBalances(ctx context.Context, accountId string) ApiGetAccountBalancesRequest { - return ApiGetAccountBalancesRequest{ - ApiService: a, - ctx: ctx, - accountId: accountId, - } -} - -// Execute executes the request -// @return Balances -func (a *DefaultApiService) GetAccountBalancesExecute(r ApiGetAccountBalancesRequest) (*Balances, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue *Balances - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.GetAccountBalances") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/accounts/{accountId}/balances" - localVarPath = strings.Replace(localVarPath, "{"+"accountId"+"}", url.PathEscape(parameterValueToString(r.accountId, "accountId")), -1) - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiGetAccountsRequest struct { - ctx context.Context - ApiService *DefaultApiService - pageSize *int64 - page *int64 - sort *string - createdAtFrom *time.Time -} - -// Number of items per page -func (r ApiGetAccountsRequest) PageSize(pageSize int64) ApiGetAccountsRequest { - r.pageSize = &pageSize - return r -} - -// Page number -func (r ApiGetAccountsRequest) Page(page int64) ApiGetAccountsRequest { - r.page = &page - return r -} - -// Sort order -func (r ApiGetAccountsRequest) Sort(sort string) ApiGetAccountsRequest { - r.sort = &sort - return r -} - -// Filter by created at date -func (r ApiGetAccountsRequest) CreatedAtFrom(createdAtFrom time.Time) ApiGetAccountsRequest { - r.createdAtFrom = &createdAtFrom - return r -} - -func (r ApiGetAccountsRequest) Execute() ([]Account, *http.Response, error) { - return r.ApiService.GetAccountsExecute(r) -} - -/* -GetAccounts Get all accounts - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiGetAccountsRequest -*/ -func (a *DefaultApiService) GetAccounts(ctx context.Context) ApiGetAccountsRequest { - return ApiGetAccountsRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// @return []Account -func (a *DefaultApiService) GetAccountsExecute(r ApiGetAccountsRequest) ([]Account, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue []Account - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.GetAccounts") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/accounts" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - if r.pageSize != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "pageSize", r.pageSize, "") - } - if r.page != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "") - } - if r.sort != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "sort", r.sort, "") - } - if r.createdAtFrom != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "createdAtFrom", r.createdAtFrom, "") - } - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiGetBeneficiariesRequest struct { - ctx context.Context - ApiService *DefaultApiService - pageSize *int64 - page *int64 - sort *string - createdAtFrom *time.Time -} - -// Number of items per page -func (r ApiGetBeneficiariesRequest) PageSize(pageSize int64) ApiGetBeneficiariesRequest { - r.pageSize = &pageSize - return r -} - -// Page number -func (r ApiGetBeneficiariesRequest) Page(page int64) ApiGetBeneficiariesRequest { - r.page = &page - return r -} - -// Sort order -func (r ApiGetBeneficiariesRequest) Sort(sort string) ApiGetBeneficiariesRequest { - r.sort = &sort - return r -} - -// Filter by created at date -func (r ApiGetBeneficiariesRequest) CreatedAtFrom(createdAtFrom time.Time) ApiGetBeneficiariesRequest { - r.createdAtFrom = &createdAtFrom - return r -} - -func (r ApiGetBeneficiariesRequest) Execute() ([]Beneficiary, *http.Response, error) { - return r.ApiService.GetBeneficiariesExecute(r) -} - -/* -GetBeneficiaries Get all beneficiaries - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiGetBeneficiariesRequest -*/ -func (a *DefaultApiService) GetBeneficiaries(ctx context.Context) ApiGetBeneficiariesRequest { - return ApiGetBeneficiariesRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// @return []Beneficiary -func (a *DefaultApiService) GetBeneficiariesExecute(r ApiGetBeneficiariesRequest) ([]Beneficiary, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue []Beneficiary - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.GetBeneficiaries") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/beneficiaries" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - if r.pageSize != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "pageSize", r.pageSize, "") - } - if r.page != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "") - } - if r.sort != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "sort", r.sort, "") - } - if r.createdAtFrom != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "createdAtFrom", r.createdAtFrom, "") - } - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} - -type ApiGetTransactionsRequest struct { - ctx context.Context - ApiService *DefaultApiService - pageSize *int64 - page *int64 - sort *string - updatedAtFrom *time.Time -} - -// Number of items per page -func (r ApiGetTransactionsRequest) PageSize(pageSize int64) ApiGetTransactionsRequest { - r.pageSize = &pageSize - return r -} - -// Page number -func (r ApiGetTransactionsRequest) Page(page int64) ApiGetTransactionsRequest { - r.page = &page - return r -} - -// Sort order -func (r ApiGetTransactionsRequest) Sort(sort string) ApiGetTransactionsRequest { - r.sort = &sort - return r -} - -// Filter by updated at date -func (r ApiGetTransactionsRequest) UpdatedAtFrom(updatedAtFrom time.Time) ApiGetTransactionsRequest { - r.updatedAtFrom = &updatedAtFrom - return r -} - -func (r ApiGetTransactionsRequest) Execute() ([]Transaction, *http.Response, error) { - return r.ApiService.GetTransactionsExecute(r) -} - -/* -GetTransactions Get all transactions - - @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). - @return ApiGetTransactionsRequest -*/ -func (a *DefaultApiService) GetTransactions(ctx context.Context) ApiGetTransactionsRequest { - return ApiGetTransactionsRequest{ - ApiService: a, - ctx: ctx, - } -} - -// Execute executes the request -// @return []Transaction -func (a *DefaultApiService) GetTransactionsExecute(r ApiGetTransactionsRequest) ([]Transaction, *http.Response, error) { - var ( - localVarHTTPMethod = http.MethodGet - localVarPostBody interface{} - formFiles []formFile - localVarReturnValue []Transaction - ) - - localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "DefaultApiService.GetTransactions") - if err != nil { - return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} - } - - localVarPath := localBasePath + "/transactions" - - localVarHeaderParams := make(map[string]string) - localVarQueryParams := url.Values{} - localVarFormParams := url.Values{} - - if r.pageSize != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "pageSize", r.pageSize, "") - } - if r.page != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "page", r.page, "") - } - if r.sort != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "sort", r.sort, "") - } - if r.updatedAtFrom != nil { - parameterAddToHeaderOrQuery(localVarQueryParams, "updatedAtFrom", r.updatedAtFrom, "") - } - // to determine the Content-Type header - localVarHTTPContentTypes := []string{} - - // set Content-Type header - localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) - if localVarHTTPContentType != "" { - localVarHeaderParams["Content-Type"] = localVarHTTPContentType - } - - // to determine the Accept header - localVarHTTPHeaderAccepts := []string{"application/json"} - - // set Accept header - localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) - if localVarHTTPHeaderAccept != "" { - localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept - } - req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles) - if err != nil { - return localVarReturnValue, nil, err - } - - localVarHTTPResponse, err := a.client.callAPI(req) - if err != nil || localVarHTTPResponse == nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) - localVarHTTPResponse.Body.Close() - localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) - if err != nil { - return localVarReturnValue, localVarHTTPResponse, err - } - - if localVarHTTPResponse.StatusCode >= 300 { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: localVarHTTPResponse.Status, - } - var v Error - err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr.error = err.Error() - return localVarReturnValue, localVarHTTPResponse, newErr - } - newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v) - newErr.model = v - return localVarReturnValue, localVarHTTPResponse, newErr - } - - err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) - if err != nil { - newErr := &GenericOpenAPIError{ - body: localVarBody, - error: err.Error(), - } - return localVarReturnValue, localVarHTTPResponse, newErr - } - - return localVarReturnValue, localVarHTTPResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/client.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/client.go deleted file mode 100644 index 8670ac161c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/client.go +++ /dev/null @@ -1,656 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "bytes" - "context" - "encoding/json" - "encoding/xml" - "errors" - "fmt" - "io" - "log" - "mime/multipart" - "net/http" - "net/http/httputil" - "net/url" - "os" - "path/filepath" - "reflect" - "regexp" - "strconv" - "strings" - "time" - "unicode/utf8" - -) - -var ( - jsonCheck = regexp.MustCompile(`(?i:(?:application|text)/(?:vnd\.[^;]+\+)?json)`) - xmlCheck = regexp.MustCompile(`(?i:(?:application|text)/xml)`) - queryParamSplit = regexp.MustCompile(`(^|&)([^&]+)`) - queryDescape = strings.NewReplacer( "%5B", "[", "%5D", "]" ) -) - -// APIClient manages communication with the GENERIC connector API API vv0.1 -// In most cases there should be only one, shared, APIClient. -type APIClient struct { - cfg *Configuration - common service // Reuse a single struct instead of allocating one for each service on the heap. - - // API Services - - DefaultApi *DefaultApiService -} - -type service struct { - client *APIClient -} - -// NewAPIClient creates a new API client. Requires a userAgent string describing your application. -// optionally a custom http.Client to allow for advanced features such as caching. -func NewAPIClient(cfg *Configuration) *APIClient { - if cfg.HTTPClient == nil { - cfg.HTTPClient = http.DefaultClient - } - - c := &APIClient{} - c.cfg = cfg - c.common.client = c - - // API Services - c.DefaultApi = (*DefaultApiService)(&c.common) - - return c -} - -func atoi(in string) (int, error) { - return strconv.Atoi(in) -} - -// selectHeaderContentType select a content type from the available list. -func selectHeaderContentType(contentTypes []string) string { - if len(contentTypes) == 0 { - return "" - } - if contains(contentTypes, "application/json") { - return "application/json" - } - return contentTypes[0] // use the first content type specified in 'consumes' -} - -// selectHeaderAccept join all accept types and return -func selectHeaderAccept(accepts []string) string { - if len(accepts) == 0 { - return "" - } - - if contains(accepts, "application/json") { - return "application/json" - } - - return strings.Join(accepts, ",") -} - -// contains is a case insensitive match, finding needle in a haystack -func contains(haystack []string, needle string) bool { - for _, a := range haystack { - if strings.EqualFold(a, needle) { - return true - } - } - return false -} - -// Verify optional parameters are of the correct type. -func typeCheckParameter(obj interface{}, expected string, name string) error { - // Make sure there is an object. - if obj == nil { - return nil - } - - // Check the type is as expected. - if reflect.TypeOf(obj).String() != expected { - return fmt.Errorf("expected %s to be of type %s but received %s", name, expected, reflect.TypeOf(obj).String()) - } - return nil -} - -func parameterValueToString( obj interface{}, key string ) string { - if reflect.TypeOf(obj).Kind() != reflect.Ptr { - return fmt.Sprintf("%v", obj) - } - var param,ok = obj.(MappedNullable) - if !ok { - return "" - } - dataMap,err := param.ToMap() - if err != nil { - return "" - } - return fmt.Sprintf("%v", dataMap[key]) -} - -// parameterAddToHeaderOrQuery adds the provided object to the request header or url query -// supporting deep object syntax -func parameterAddToHeaderOrQuery(headerOrQueryParams interface{}, keyPrefix string, obj interface{}, collectionType string) { - var v = reflect.ValueOf(obj) - var value = "" - if v == reflect.ValueOf(nil) { - value = "null" - } else { - switch v.Kind() { - case reflect.Invalid: - value = "invalid" - - case reflect.Struct: - if t,ok := obj.(MappedNullable); ok { - dataMap,err := t.ToMap() - if err != nil { - return - } - parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, dataMap, collectionType) - return - } - if t, ok := obj.(time.Time); ok { - parameterAddToHeaderOrQuery(headerOrQueryParams, keyPrefix, t.Format(time.RFC3339), collectionType) - return - } - value = v.Type().String() + " value" - case reflect.Slice: - var indValue = reflect.ValueOf(obj) - if indValue == reflect.ValueOf(nil) { - return - } - var lenIndValue = indValue.Len() - for i:=0;i 0 || (len(formFiles) > 0) { - if body != nil { - return nil, errors.New("Cannot specify postBody and multipart form at the same time.") - } - body = &bytes.Buffer{} - w := multipart.NewWriter(body) - - for k, v := range formParams { - for _, iv := range v { - if strings.HasPrefix(k, "@") { // file - err = addFile(w, k[1:], iv) - if err != nil { - return nil, err - } - } else { // form value - w.WriteField(k, iv) - } - } - } - for _, formFile := range formFiles { - if len(formFile.fileBytes) > 0 && formFile.fileName != "" { - w.Boundary() - part, err := w.CreateFormFile(formFile.formFileName, filepath.Base(formFile.fileName)) - if err != nil { - return nil, err - } - _, err = part.Write(formFile.fileBytes) - if err != nil { - return nil, err - } - } - } - - // Set the Boundary in the Content-Type - headerParams["Content-Type"] = w.FormDataContentType() - - // Set Content-Length - headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) - w.Close() - } - - if strings.HasPrefix(headerParams["Content-Type"], "application/x-www-form-urlencoded") && len(formParams) > 0 { - if body != nil { - return nil, errors.New("Cannot specify postBody and x-www-form-urlencoded form at the same time.") - } - body = &bytes.Buffer{} - body.WriteString(formParams.Encode()) - // Set Content-Length - headerParams["Content-Length"] = fmt.Sprintf("%d", body.Len()) - } - - // Setup path and query parameters - url, err := url.Parse(path) - if err != nil { - return nil, err - } - - // Override request host, if applicable - if c.cfg.Host != "" { - url.Host = c.cfg.Host - } - - // Override request scheme, if applicable - if c.cfg.Scheme != "" { - url.Scheme = c.cfg.Scheme - } - - // Adding Query Param - query := url.Query() - for k, v := range queryParams { - for _, iv := range v { - query.Add(k, iv) - } - } - - // Encode the parameters. - url.RawQuery = queryParamSplit.ReplaceAllStringFunc(query.Encode(), func(s string) string { - pieces := strings.Split(s, "=") - pieces[0] = queryDescape.Replace(pieces[0]) - return strings.Join(pieces, "=") - }) - - // Generate a new request - if body != nil { - localVarRequest, err = http.NewRequest(method, url.String(), body) - } else { - localVarRequest, err = http.NewRequest(method, url.String(), nil) - } - if err != nil { - return nil, err - } - - // add header parameters, if any - if len(headerParams) > 0 { - headers := http.Header{} - for h, v := range headerParams { - headers[h] = []string{v} - } - localVarRequest.Header = headers - } - - // Add the user agent to the request. - localVarRequest.Header.Add("User-Agent", c.cfg.UserAgent) - - if ctx != nil { - // add context to the request - localVarRequest = localVarRequest.WithContext(ctx) - - // Walk through any authentication. - - } - - for header, value := range c.cfg.DefaultHeader { - localVarRequest.Header.Add(header, value) - } - return localVarRequest, nil -} - -func (c *APIClient) decode(v interface{}, b []byte, contentType string) (err error) { - if len(b) == 0 { - return nil - } - if s, ok := v.(*string); ok { - *s = string(b) - return nil - } - if f, ok := v.(*os.File); ok { - f, err = os.CreateTemp("", "HttpClientFile") - if err != nil { - return - } - _, err = f.Write(b) - if err != nil { - return - } - _, err = f.Seek(0, io.SeekStart) - return - } - if f, ok := v.(**os.File); ok { - *f, err = os.CreateTemp("", "HttpClientFile") - if err != nil { - return - } - _, err = (*f).Write(b) - if err != nil { - return - } - _, err = (*f).Seek(0, io.SeekStart) - return - } - if xmlCheck.MatchString(contentType) { - if err = xml.Unmarshal(b, v); err != nil { - return err - } - return nil - } - if jsonCheck.MatchString(contentType) { - if actualObj, ok := v.(interface{ GetActualInstance() interface{} }); ok { // oneOf, anyOf schemas - if unmarshalObj, ok := actualObj.(interface{ UnmarshalJSON([]byte) error }); ok { // make sure it has UnmarshalJSON defined - if err = unmarshalObj.UnmarshalJSON(b); err != nil { - return err - } - } else { - return errors.New("Unknown type with GetActualInstance but no unmarshalObj.UnmarshalJSON defined") - } - } else if err = json.Unmarshal(b, v); err != nil { // simple model - return err - } - return nil - } - return errors.New("undefined response type") -} - -// Add a file to the multipart request -func addFile(w *multipart.Writer, fieldName, path string) error { - file, err := os.Open(filepath.Clean(path)) - if err != nil { - return err - } - err = file.Close() - if err != nil { - return err - } - - part, err := w.CreateFormFile(fieldName, filepath.Base(path)) - if err != nil { - return err - } - _, err = io.Copy(part, file) - - return err -} - -// Prevent trying to import "fmt" -func reportError(format string, a ...interface{}) error { - return fmt.Errorf(format, a...) -} - -// A wrapper for strict JSON decoding -func newStrictDecoder(data []byte) *json.Decoder { - dec := json.NewDecoder(bytes.NewBuffer(data)) - dec.DisallowUnknownFields() - return dec -} - -// Set request body from an interface{} -func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err error) { - if bodyBuf == nil { - bodyBuf = &bytes.Buffer{} - } - - if reader, ok := body.(io.Reader); ok { - _, err = bodyBuf.ReadFrom(reader) - } else if fp, ok := body.(*os.File); ok { - _, err = bodyBuf.ReadFrom(fp) - } else if b, ok := body.([]byte); ok { - _, err = bodyBuf.Write(b) - } else if s, ok := body.(string); ok { - _, err = bodyBuf.WriteString(s) - } else if s, ok := body.(*string); ok { - _, err = bodyBuf.WriteString(*s) - } else if jsonCheck.MatchString(contentType) { - err = json.NewEncoder(bodyBuf).Encode(body) - } else if xmlCheck.MatchString(contentType) { - err = xml.NewEncoder(bodyBuf).Encode(body) - } - - if err != nil { - return nil, err - } - - if bodyBuf.Len() == 0 { - err = fmt.Errorf("invalid body type %s\n", contentType) - return nil, err - } - return bodyBuf, nil -} - -// detectContentType method is used to figure out `Request.Body` content type for request header -func detectContentType(body interface{}) string { - contentType := "text/plain; charset=utf-8" - kind := reflect.TypeOf(body).Kind() - - switch kind { - case reflect.Struct, reflect.Map, reflect.Ptr: - contentType = "application/json; charset=utf-8" - case reflect.String: - contentType = "text/plain; charset=utf-8" - default: - if b, ok := body.([]byte); ok { - contentType = http.DetectContentType(b) - } else if kind == reflect.Slice { - contentType = "application/json; charset=utf-8" - } - } - - return contentType -} - -// Ripped from https://github.com/gregjones/httpcache/blob/master/httpcache.go -type cacheControl map[string]string - -func parseCacheControl(headers http.Header) cacheControl { - cc := cacheControl{} - ccHeader := headers.Get("Cache-Control") - for _, part := range strings.Split(ccHeader, ",") { - part = strings.Trim(part, " ") - if part == "" { - continue - } - if strings.ContainsRune(part, '=') { - keyval := strings.Split(part, "=") - cc[strings.Trim(keyval[0], " ")] = strings.Trim(keyval[1], ",") - } else { - cc[part] = "" - } - } - return cc -} - -// CacheExpires helper function to determine remaining time before repeating a request. -func CacheExpires(r *http.Response) time.Time { - // Figure out when the cache expires. - var expires time.Time - now, err := time.Parse(time.RFC1123, r.Header.Get("date")) - if err != nil { - return time.Now() - } - respCacheControl := parseCacheControl(r.Header) - - if maxAge, ok := respCacheControl["max-age"]; ok { - lifetime, err := time.ParseDuration(maxAge + "s") - if err != nil { - expires = now - } else { - expires = now.Add(lifetime) - } - } else { - expiresHeader := r.Header.Get("Expires") - if expiresHeader != "" { - expires, err = time.Parse(time.RFC1123, expiresHeader) - if err != nil { - expires = now - } - } - } - return expires -} - -func strlen(s string) int { - return utf8.RuneCountInString(s) -} - -// GenericOpenAPIError Provides access to the body, error and model on returned errors. -type GenericOpenAPIError struct { - body []byte - error string - model interface{} -} - -// Error returns non-empty string if there was an error. -func (e GenericOpenAPIError) Error() string { - return e.error -} - -// Body returns the raw bytes of the response -func (e GenericOpenAPIError) Body() []byte { - return e.body -} - -// Model returns the unpacked model of the error -func (e GenericOpenAPIError) Model() interface{} { - return e.model -} - -// format error message using title and detail when model implements rfc7807 -func formatErrorMessage(status string, v interface{}) string { - str := "" - metaValue := reflect.ValueOf(v).Elem() - - if metaValue.Kind() == reflect.Struct { - field := metaValue.FieldByName("Title") - if field != (reflect.Value{}) { - str = fmt.Sprintf("%s", field.Interface()) - } - - field = metaValue.FieldByName("Detail") - if field != (reflect.Value{}) { - str = fmt.Sprintf("%s (%s)", str, field.Interface()) - } - } - - return strings.TrimSpace(fmt.Sprintf("%s %s", status, str)) -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/configuration.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/configuration.go deleted file mode 100644 index 27b99ff25b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/configuration.go +++ /dev/null @@ -1,215 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "context" - "fmt" - "net/http" - "strings" -) - -// contextKeys are used to identify the type of value in the context. -// Since these are string, it is possible to get a short description of the -// context key for logging and debugging using key.String(). - -type contextKey string - -func (c contextKey) String() string { - return "auth " + string(c) -} - -var ( - // ContextServerIndex uses a server configuration from the index. - ContextServerIndex = contextKey("serverIndex") - - // ContextOperationServerIndices uses a server configuration from the index mapping. - ContextOperationServerIndices = contextKey("serverOperationIndices") - - // ContextServerVariables overrides a server configuration variables. - ContextServerVariables = contextKey("serverVariables") - - // ContextOperationServerVariables overrides a server configuration variables using operation specific values. - ContextOperationServerVariables = contextKey("serverOperationVariables") -) - -// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth -type BasicAuth struct { - UserName string `json:"userName,omitempty"` - Password string `json:"password,omitempty"` -} - -// APIKey provides API key based authentication to a request passed via context using ContextAPIKey -type APIKey struct { - Key string - Prefix string -} - -// ServerVariable stores the information about a server variable -type ServerVariable struct { - Description string - DefaultValue string - EnumValues []string -} - -// ServerConfiguration stores the information about a server -type ServerConfiguration struct { - URL string - Description string - Variables map[string]ServerVariable -} - -// ServerConfigurations stores multiple ServerConfiguration items -type ServerConfigurations []ServerConfiguration - -// Configuration stores the configuration of the API client -type Configuration struct { - Host string `json:"host,omitempty"` - Scheme string `json:"scheme,omitempty"` - DefaultHeader map[string]string `json:"defaultHeader,omitempty"` - UserAgent string `json:"userAgent,omitempty"` - Debug bool `json:"debug,omitempty"` - Servers ServerConfigurations - OperationServers map[string]ServerConfigurations - HTTPClient *http.Client -} - -// NewConfiguration returns a new Configuration object -func NewConfiguration() *Configuration { - cfg := &Configuration{ - DefaultHeader: make(map[string]string), - UserAgent: "OpenAPI-Generator/latest/go", - Debug: false, - Servers: ServerConfigurations{ - { - URL: "", - Description: "No description provided", - }, - }, - OperationServers: map[string]ServerConfigurations{ - }, - } - return cfg -} - -// AddDefaultHeader adds a new HTTP header to the default header in the request -func (c *Configuration) AddDefaultHeader(key string, value string) { - c.DefaultHeader[key] = value -} - -// URL formats template on a index using given variables -func (sc ServerConfigurations) URL(index int, variables map[string]string) (string, error) { - if index < 0 || len(sc) <= index { - return "", fmt.Errorf("index %v out of range %v", index, len(sc)-1) - } - server := sc[index] - url := server.URL - - // go through variables and replace placeholders - for name, variable := range server.Variables { - if value, ok := variables[name]; ok { - found := bool(len(variable.EnumValues) == 0) - for _, enumValue := range variable.EnumValues { - if value == enumValue { - found = true - } - } - if !found { - return "", fmt.Errorf("the variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues) - } - url = strings.Replace(url, "{"+name+"}", value, -1) - } else { - url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1) - } - } - return url, nil -} - -// ServerURL returns URL based on server settings -func (c *Configuration) ServerURL(index int, variables map[string]string) (string, error) { - return c.Servers.URL(index, variables) -} - -func getServerIndex(ctx context.Context) (int, error) { - si := ctx.Value(ContextServerIndex) - if si != nil { - if index, ok := si.(int); ok { - return index, nil - } - return 0, reportError("Invalid type %T should be int", si) - } - return 0, nil -} - -func getServerOperationIndex(ctx context.Context, endpoint string) (int, error) { - osi := ctx.Value(ContextOperationServerIndices) - if osi != nil { - if operationIndices, ok := osi.(map[string]int); !ok { - return 0, reportError("Invalid type %T should be map[string]int", osi) - } else { - index, ok := operationIndices[endpoint] - if ok { - return index, nil - } - } - } - return getServerIndex(ctx) -} - -func getServerVariables(ctx context.Context) (map[string]string, error) { - sv := ctx.Value(ContextServerVariables) - if sv != nil { - if variables, ok := sv.(map[string]string); ok { - return variables, nil - } - return nil, reportError("ctx value of ContextServerVariables has invalid type %T should be map[string]string", sv) - } - return nil, nil -} - -func getServerOperationVariables(ctx context.Context, endpoint string) (map[string]string, error) { - osv := ctx.Value(ContextOperationServerVariables) - if osv != nil { - if operationVariables, ok := osv.(map[string]map[string]string); !ok { - return nil, reportError("ctx value of ContextOperationServerVariables has invalid type %T should be map[string]map[string]string", osv) - } else { - variables, ok := operationVariables[endpoint] - if ok { - return variables, nil - } - } - } - return getServerVariables(ctx) -} - -// ServerURLWithContext returns a new server URL given an endpoint -func (c *Configuration) ServerURLWithContext(ctx context.Context, endpoint string) (string, error) { - sc, ok := c.OperationServers[endpoint] - if !ok { - sc = c.Servers - } - - if ctx == nil { - return sc.URL(0, nil) - } - - index, err := getServerOperationIndex(ctx, endpoint) - if err != nil { - return "", err - } - - variables, err := getServerOperationVariables(ctx, endpoint) - if err != nil { - return "", err - } - - return sc.URL(index, variables) -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Account.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Account.md deleted file mode 100644 index 57157a528f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Account.md +++ /dev/null @@ -1,129 +0,0 @@ -# Account - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | **string** | | -**AccountName** | **string** | | -**CreatedAt** | **time.Time** | | -**Metadata** | Pointer to **map[string]string** | | [optional] - -## Methods - -### NewAccount - -`func NewAccount(id string, accountName string, createdAt time.Time, ) *Account` - -NewAccount instantiates a new Account object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewAccountWithDefaults - -`func NewAccountWithDefaults() *Account` - -NewAccountWithDefaults instantiates a new Account object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Account) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Account) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Account) SetId(v string)` - -SetId sets Id field to given value. - - -### GetAccountName - -`func (o *Account) GetAccountName() string` - -GetAccountName returns the AccountName field if non-nil, zero value otherwise. - -### GetAccountNameOk - -`func (o *Account) GetAccountNameOk() (*string, bool)` - -GetAccountNameOk returns a tuple with the AccountName field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetAccountName - -`func (o *Account) SetAccountName(v string)` - -SetAccountName sets AccountName field to given value. - - -### GetCreatedAt - -`func (o *Account) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *Account) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *Account) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - - -### GetMetadata - -`func (o *Account) GetMetadata() map[string]string` - -GetMetadata returns the Metadata field if non-nil, zero value otherwise. - -### GetMetadataOk - -`func (o *Account) GetMetadataOk() (*map[string]string, bool)` - -GetMetadataOk returns a tuple with the Metadata field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetMetadata - -`func (o *Account) SetMetadata(v map[string]string)` - -SetMetadata sets Metadata field to given value. - -### HasMetadata - -`func (o *Account) HasMetadata() bool` - -HasMetadata returns a boolean if a field has been set. - -### SetMetadataNil - -`func (o *Account) SetMetadataNil(b bool)` - - SetMetadataNil sets the value for Metadata to be an explicit nil - -### UnsetMetadata -`func (o *Account) UnsetMetadata()` - -UnsetMetadata ensures that no value is present for Metadata, not even an explicit nil - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balance.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balance.md deleted file mode 100644 index 10aef2295e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balance.md +++ /dev/null @@ -1,72 +0,0 @@ -# Balance - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Amount** | **string** | | -**Currency** | **string** | | - -## Methods - -### NewBalance - -`func NewBalance(amount string, currency string, ) *Balance` - -NewBalance instantiates a new Balance object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewBalanceWithDefaults - -`func NewBalanceWithDefaults() *Balance` - -NewBalanceWithDefaults instantiates a new Balance object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetAmount - -`func (o *Balance) GetAmount() string` - -GetAmount returns the Amount field if non-nil, zero value otherwise. - -### GetAmountOk - -`func (o *Balance) GetAmountOk() (*string, bool)` - -GetAmountOk returns a tuple with the Amount field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetAmount - -`func (o *Balance) SetAmount(v string)` - -SetAmount sets Amount field to given value. - - -### GetCurrency - -`func (o *Balance) GetCurrency() string` - -GetCurrency returns the Currency field if non-nil, zero value otherwise. - -### GetCurrencyOk - -`func (o *Balance) GetCurrencyOk() (*string, bool)` - -GetCurrencyOk returns a tuple with the Currency field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCurrency - -`func (o *Balance) SetCurrency(v string)` - -SetCurrency sets Currency field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balances.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balances.md deleted file mode 100644 index dd764e805a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Balances.md +++ /dev/null @@ -1,114 +0,0 @@ -# Balances - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | **string** | | -**AccountID** | **string** | | -**At** | **time.Time** | | -**Balances** | [**[]Balance**](Balance.md) | | - -## Methods - -### NewBalances - -`func NewBalances(id string, accountID string, at time.Time, balances []Balance, ) *Balances` - -NewBalances instantiates a new Balances object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewBalancesWithDefaults - -`func NewBalancesWithDefaults() *Balances` - -NewBalancesWithDefaults instantiates a new Balances object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Balances) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Balances) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Balances) SetId(v string)` - -SetId sets Id field to given value. - - -### GetAccountID - -`func (o *Balances) GetAccountID() string` - -GetAccountID returns the AccountID field if non-nil, zero value otherwise. - -### GetAccountIDOk - -`func (o *Balances) GetAccountIDOk() (*string, bool)` - -GetAccountIDOk returns a tuple with the AccountID field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetAccountID - -`func (o *Balances) SetAccountID(v string)` - -SetAccountID sets AccountID field to given value. - - -### GetAt - -`func (o *Balances) GetAt() time.Time` - -GetAt returns the At field if non-nil, zero value otherwise. - -### GetAtOk - -`func (o *Balances) GetAtOk() (*time.Time, bool)` - -GetAtOk returns a tuple with the At field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetAt - -`func (o *Balances) SetAt(v time.Time)` - -SetAt sets At field to given value. - - -### GetBalances - -`func (o *Balances) GetBalances() []Balance` - -GetBalances returns the Balances field if non-nil, zero value otherwise. - -### GetBalancesOk - -`func (o *Balances) GetBalancesOk() (*[]Balance, bool)` - -GetBalancesOk returns a tuple with the Balances field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetBalances - -`func (o *Balances) SetBalances(v []Balance)` - -SetBalances sets Balances field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Beneficiary.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Beneficiary.md deleted file mode 100644 index d967209a9b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Beneficiary.md +++ /dev/null @@ -1,129 +0,0 @@ -# Beneficiary - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | **string** | | -**CreatedAt** | **time.Time** | | -**OwnerName** | **string** | | -**Metadata** | Pointer to **map[string]string** | | [optional] - -## Methods - -### NewBeneficiary - -`func NewBeneficiary(id string, createdAt time.Time, ownerName string, ) *Beneficiary` - -NewBeneficiary instantiates a new Beneficiary object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewBeneficiaryWithDefaults - -`func NewBeneficiaryWithDefaults() *Beneficiary` - -NewBeneficiaryWithDefaults instantiates a new Beneficiary object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Beneficiary) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Beneficiary) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Beneficiary) SetId(v string)` - -SetId sets Id field to given value. - - -### GetCreatedAt - -`func (o *Beneficiary) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *Beneficiary) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *Beneficiary) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - - -### GetOwnerName - -`func (o *Beneficiary) GetOwnerName() string` - -GetOwnerName returns the OwnerName field if non-nil, zero value otherwise. - -### GetOwnerNameOk - -`func (o *Beneficiary) GetOwnerNameOk() (*string, bool)` - -GetOwnerNameOk returns a tuple with the OwnerName field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetOwnerName - -`func (o *Beneficiary) SetOwnerName(v string)` - -SetOwnerName sets OwnerName field to given value. - - -### GetMetadata - -`func (o *Beneficiary) GetMetadata() map[string]string` - -GetMetadata returns the Metadata field if non-nil, zero value otherwise. - -### GetMetadataOk - -`func (o *Beneficiary) GetMetadataOk() (*map[string]string, bool)` - -GetMetadataOk returns a tuple with the Metadata field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetMetadata - -`func (o *Beneficiary) SetMetadata(v map[string]string)` - -SetMetadata sets Metadata field to given value. - -### HasMetadata - -`func (o *Beneficiary) HasMetadata() bool` - -HasMetadata returns a boolean if a field has been set. - -### SetMetadataNil - -`func (o *Beneficiary) SetMetadataNil(b bool)` - - SetMetadataNil sets the value for Metadata to be an explicit nil - -### UnsetMetadata -`func (o *Beneficiary) UnsetMetadata()` - -UnsetMetadata ensures that no value is present for Metadata, not even an explicit nil - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/DefaultApi.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/DefaultApi.md deleted file mode 100644 index 1c8673441d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/DefaultApi.md +++ /dev/null @@ -1,293 +0,0 @@ -# \DefaultApi - -All URIs are relative to *http://localhost* - -Method | HTTP request | Description -------------- | ------------- | ------------- -[**GetAccountBalances**](DefaultApi.md#GetAccountBalances) | **Get** /accounts/{accountId}/balances | Get account balance -[**GetAccounts**](DefaultApi.md#GetAccounts) | **Get** /accounts | Get all accounts -[**GetBeneficiaries**](DefaultApi.md#GetBeneficiaries) | **Get** /beneficiaries | Get all beneficiaries -[**GetTransactions**](DefaultApi.md#GetTransactions) | **Get** /transactions | Get all transactions - - - -## GetAccountBalances - -> Balances GetAccountBalances(ctx, accountId).Execute() - -Get account balance - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - openapiclient "github.com/formancehq/payments/genericclient" -) - -func main() { - accountId := "accountId_example" // string | - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.GetAccountBalances(context.Background(), accountId).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.GetAccountBalances``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `GetAccountBalances`: Balances - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.GetAccountBalances`: %v\n", resp) -} -``` - -### Path Parameters - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- -**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. -**accountId** | **string** | | - -### Other Parameters - -Other parameters are passed through a pointer to a apiGetAccountBalancesRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - - -### Return type - -[**Balances**](Balances.md) - -### Authorization - -No authorization required - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## GetAccounts - -> []Account GetAccounts(ctx).PageSize(pageSize).Page(page).Sort(sort).CreatedAtFrom(createdAtFrom).Execute() - -Get all accounts - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - "time" - openapiclient "github.com/formancehq/payments/genericclient" -) - -func main() { - pageSize := int64(100) // int64 | Number of items per page (optional) (default to 100) - page := int64(1) // int64 | Page number (optional) (default to 1) - sort := "createdAt:asc" // string | Sort order (optional) - createdAtFrom := time.Now() // time.Time | Filter by created at date (optional) - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.GetAccounts(context.Background()).PageSize(pageSize).Page(page).Sort(sort).CreatedAtFrom(createdAtFrom).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.GetAccounts``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `GetAccounts`: []Account - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.GetAccounts`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiGetAccountsRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **pageSize** | **int64** | Number of items per page | [default to 100] - **page** | **int64** | Page number | [default to 1] - **sort** | **string** | Sort order | - **createdAtFrom** | **time.Time** | Filter by created at date | - -### Return type - -[**[]Account**](Account.md) - -### Authorization - -No authorization required - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## GetBeneficiaries - -> []Beneficiary GetBeneficiaries(ctx).PageSize(pageSize).Page(page).Sort(sort).CreatedAtFrom(createdAtFrom).Execute() - -Get all beneficiaries - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - "time" - openapiclient "github.com/formancehq/payments/genericclient" -) - -func main() { - pageSize := int64(100) // int64 | Number of items per page (optional) (default to 100) - page := int64(1) // int64 | Page number (optional) (default to 1) - sort := "createdAt:asc" // string | Sort order (optional) - createdAtFrom := time.Now() // time.Time | Filter by created at date (optional) - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.GetBeneficiaries(context.Background()).PageSize(pageSize).Page(page).Sort(sort).CreatedAtFrom(createdAtFrom).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.GetBeneficiaries``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `GetBeneficiaries`: []Beneficiary - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.GetBeneficiaries`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiGetBeneficiariesRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **pageSize** | **int64** | Number of items per page | [default to 100] - **page** | **int64** | Page number | [default to 1] - **sort** | **string** | Sort order | - **createdAtFrom** | **time.Time** | Filter by created at date | - -### Return type - -[**[]Beneficiary**](Beneficiary.md) - -### Authorization - -No authorization required - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - - -## GetTransactions - -> []Transaction GetTransactions(ctx).PageSize(pageSize).Page(page).Sort(sort).UpdatedAtFrom(updatedAtFrom).Execute() - -Get all transactions - -### Example - -```go -package main - -import ( - "context" - "fmt" - "os" - "time" - openapiclient "github.com/formancehq/payments/genericclient" -) - -func main() { - pageSize := int64(100) // int64 | Number of items per page (optional) (default to 100) - page := int64(1) // int64 | Page number (optional) (default to 1) - sort := "createdAt:asc" // string | Sort order (optional) - updatedAtFrom := time.Now() // time.Time | Filter by updated at date (optional) - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) - resp, r, err := apiClient.DefaultApi.GetTransactions(context.Background()).PageSize(pageSize).Page(page).Sort(sort).UpdatedAtFrom(updatedAtFrom).Execute() - if err != nil { - fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.GetTransactions``: %v\n", err) - fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) - } - // response from `GetTransactions`: []Transaction - fmt.Fprintf(os.Stdout, "Response from `DefaultApi.GetTransactions`: %v\n", resp) -} -``` - -### Path Parameters - - - -### Other Parameters - -Other parameters are passed through a pointer to a apiGetTransactionsRequest struct via the builder pattern - - -Name | Type | Description | Notes -------------- | ------------- | ------------- | ------------- - **pageSize** | **int64** | Number of items per page | [default to 100] - **page** | **int64** | Page number | [default to 1] - **sort** | **string** | Sort order | - **updatedAtFrom** | **time.Time** | Filter by updated at date | - -### Return type - -[**[]Transaction**](Transaction.md) - -### Authorization - -No authorization required - -### HTTP request headers - -- **Content-Type**: Not defined -- **Accept**: application/json - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) -[[Back to Model list]](../README.md#documentation-for-models) -[[Back to README]](../README.md) - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Error.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Error.md deleted file mode 100644 index a6b1b02c9b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Error.md +++ /dev/null @@ -1,72 +0,0 @@ -# Error - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Title** | **string** | | -**Detail** | **string** | | - -## Methods - -### NewError - -`func NewError(title string, detail string, ) *Error` - -NewError instantiates a new Error object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewErrorWithDefaults - -`func NewErrorWithDefaults() *Error` - -NewErrorWithDefaults instantiates a new Error object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetTitle - -`func (o *Error) GetTitle() string` - -GetTitle returns the Title field if non-nil, zero value otherwise. - -### GetTitleOk - -`func (o *Error) GetTitleOk() (*string, bool)` - -GetTitleOk returns a tuple with the Title field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetTitle - -`func (o *Error) SetTitle(v string)` - -SetTitle sets Title field to given value. - - -### GetDetail - -`func (o *Error) GetDetail() string` - -GetDetail returns the Detail field if non-nil, zero value otherwise. - -### GetDetailOk - -`func (o *Error) GetDetailOk() (*string, bool)` - -GetDetailOk returns a tuple with the Detail field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDetail - -`func (o *Error) SetDetail(v string)` - -SetDetail sets Detail field to given value. - - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Transaction.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Transaction.md deleted file mode 100644 index 834e33b9e8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/Transaction.md +++ /dev/null @@ -1,317 +0,0 @@ -# Transaction - -## Properties - -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -**Id** | **string** | | -**RelatedTransactionID** | Pointer to **string** | | [optional] -**CreatedAt** | **time.Time** | | -**UpdatedAt** | **time.Time** | | -**Currency** | **string** | | -**Scheme** | Pointer to **string** | | [optional] -**Type** | [**TransactionType**](TransactionType.md) | | -**Status** | [**TransactionStatus**](TransactionStatus.md) | | -**Amount** | **string** | | -**SourceAccountID** | Pointer to **string** | | [optional] -**DestinationAccountID** | Pointer to **string** | | [optional] -**Metadata** | Pointer to **map[string]string** | | [optional] - -## Methods - -### NewTransaction - -`func NewTransaction(id string, createdAt time.Time, updatedAt time.Time, currency string, type_ TransactionType, status TransactionStatus, amount string, ) *Transaction` - -NewTransaction instantiates a new Transaction object -This constructor will assign default values to properties that have it defined, -and makes sure properties required by API are set, but the set of arguments -will change when the set of required properties is changed - -### NewTransactionWithDefaults - -`func NewTransactionWithDefaults() *Transaction` - -NewTransactionWithDefaults instantiates a new Transaction object -This constructor will only assign default values to properties that have it defined, -but it doesn't guarantee that properties required by API are set - -### GetId - -`func (o *Transaction) GetId() string` - -GetId returns the Id field if non-nil, zero value otherwise. - -### GetIdOk - -`func (o *Transaction) GetIdOk() (*string, bool)` - -GetIdOk returns a tuple with the Id field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetId - -`func (o *Transaction) SetId(v string)` - -SetId sets Id field to given value. - - -### GetRelatedTransactionID - -`func (o *Transaction) GetRelatedTransactionID() string` - -GetRelatedTransactionID returns the RelatedTransactionID field if non-nil, zero value otherwise. - -### GetRelatedTransactionIDOk - -`func (o *Transaction) GetRelatedTransactionIDOk() (*string, bool)` - -GetRelatedTransactionIDOk returns a tuple with the RelatedTransactionID field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetRelatedTransactionID - -`func (o *Transaction) SetRelatedTransactionID(v string)` - -SetRelatedTransactionID sets RelatedTransactionID field to given value. - -### HasRelatedTransactionID - -`func (o *Transaction) HasRelatedTransactionID() bool` - -HasRelatedTransactionID returns a boolean if a field has been set. - -### GetCreatedAt - -`func (o *Transaction) GetCreatedAt() time.Time` - -GetCreatedAt returns the CreatedAt field if non-nil, zero value otherwise. - -### GetCreatedAtOk - -`func (o *Transaction) GetCreatedAtOk() (*time.Time, bool)` - -GetCreatedAtOk returns a tuple with the CreatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCreatedAt - -`func (o *Transaction) SetCreatedAt(v time.Time)` - -SetCreatedAt sets CreatedAt field to given value. - - -### GetUpdatedAt - -`func (o *Transaction) GetUpdatedAt() time.Time` - -GetUpdatedAt returns the UpdatedAt field if non-nil, zero value otherwise. - -### GetUpdatedAtOk - -`func (o *Transaction) GetUpdatedAtOk() (*time.Time, bool)` - -GetUpdatedAtOk returns a tuple with the UpdatedAt field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetUpdatedAt - -`func (o *Transaction) SetUpdatedAt(v time.Time)` - -SetUpdatedAt sets UpdatedAt field to given value. - - -### GetCurrency - -`func (o *Transaction) GetCurrency() string` - -GetCurrency returns the Currency field if non-nil, zero value otherwise. - -### GetCurrencyOk - -`func (o *Transaction) GetCurrencyOk() (*string, bool)` - -GetCurrencyOk returns a tuple with the Currency field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetCurrency - -`func (o *Transaction) SetCurrency(v string)` - -SetCurrency sets Currency field to given value. - - -### GetScheme - -`func (o *Transaction) GetScheme() string` - -GetScheme returns the Scheme field if non-nil, zero value otherwise. - -### GetSchemeOk - -`func (o *Transaction) GetSchemeOk() (*string, bool)` - -GetSchemeOk returns a tuple with the Scheme field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetScheme - -`func (o *Transaction) SetScheme(v string)` - -SetScheme sets Scheme field to given value. - -### HasScheme - -`func (o *Transaction) HasScheme() bool` - -HasScheme returns a boolean if a field has been set. - -### GetType - -`func (o *Transaction) GetType() TransactionType` - -GetType returns the Type field if non-nil, zero value otherwise. - -### GetTypeOk - -`func (o *Transaction) GetTypeOk() (*TransactionType, bool)` - -GetTypeOk returns a tuple with the Type field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetType - -`func (o *Transaction) SetType(v TransactionType)` - -SetType sets Type field to given value. - - -### GetStatus - -`func (o *Transaction) GetStatus() TransactionStatus` - -GetStatus returns the Status field if non-nil, zero value otherwise. - -### GetStatusOk - -`func (o *Transaction) GetStatusOk() (*TransactionStatus, bool)` - -GetStatusOk returns a tuple with the Status field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetStatus - -`func (o *Transaction) SetStatus(v TransactionStatus)` - -SetStatus sets Status field to given value. - - -### GetAmount - -`func (o *Transaction) GetAmount() string` - -GetAmount returns the Amount field if non-nil, zero value otherwise. - -### GetAmountOk - -`func (o *Transaction) GetAmountOk() (*string, bool)` - -GetAmountOk returns a tuple with the Amount field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetAmount - -`func (o *Transaction) SetAmount(v string)` - -SetAmount sets Amount field to given value. - - -### GetSourceAccountID - -`func (o *Transaction) GetSourceAccountID() string` - -GetSourceAccountID returns the SourceAccountID field if non-nil, zero value otherwise. - -### GetSourceAccountIDOk - -`func (o *Transaction) GetSourceAccountIDOk() (*string, bool)` - -GetSourceAccountIDOk returns a tuple with the SourceAccountID field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetSourceAccountID - -`func (o *Transaction) SetSourceAccountID(v string)` - -SetSourceAccountID sets SourceAccountID field to given value. - -### HasSourceAccountID - -`func (o *Transaction) HasSourceAccountID() bool` - -HasSourceAccountID returns a boolean if a field has been set. - -### GetDestinationAccountID - -`func (o *Transaction) GetDestinationAccountID() string` - -GetDestinationAccountID returns the DestinationAccountID field if non-nil, zero value otherwise. - -### GetDestinationAccountIDOk - -`func (o *Transaction) GetDestinationAccountIDOk() (*string, bool)` - -GetDestinationAccountIDOk returns a tuple with the DestinationAccountID field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetDestinationAccountID - -`func (o *Transaction) SetDestinationAccountID(v string)` - -SetDestinationAccountID sets DestinationAccountID field to given value. - -### HasDestinationAccountID - -`func (o *Transaction) HasDestinationAccountID() bool` - -HasDestinationAccountID returns a boolean if a field has been set. - -### GetMetadata - -`func (o *Transaction) GetMetadata() map[string]string` - -GetMetadata returns the Metadata field if non-nil, zero value otherwise. - -### GetMetadataOk - -`func (o *Transaction) GetMetadataOk() (*map[string]string, bool)` - -GetMetadataOk returns a tuple with the Metadata field if it's non-nil, zero value otherwise -and a boolean to check if the value has been set. - -### SetMetadata - -`func (o *Transaction) SetMetadata(v map[string]string)` - -SetMetadata sets Metadata field to given value. - -### HasMetadata - -`func (o *Transaction) HasMetadata() bool` - -HasMetadata returns a boolean if a field has been set. - -### SetMetadataNil - -`func (o *Transaction) SetMetadataNil(b bool)` - - SetMetadataNil sets the value for Metadata to be an explicit nil - -### UnsetMetadata -`func (o *Transaction) UnsetMetadata()` - -UnsetMetadata ensures that no value is present for Metadata, not even an explicit nil - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionStatus.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionStatus.md deleted file mode 100644 index 6a25fbcad9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionStatus.md +++ /dev/null @@ -1,15 +0,0 @@ -# TransactionStatus - -## Enum - - -* `PENDING` (value: `"PENDING"`) - -* `SUCCEEDED` (value: `"SUCCEEDED"`) - -* `FAILED` (value: `"FAILED"`) - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionType.md b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionType.md deleted file mode 100644 index a1df99c8f8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/docs/TransactionType.md +++ /dev/null @@ -1,15 +0,0 @@ -# TransactionType - -## Enum - - -* `PAYIN` (value: `"PAYIN"`) - -* `PAYOUT` (value: `"PAYOUT"`) - -* `TRANSFER` (value: `"TRANSFER"`) - - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/git_push.sh b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/git_push.sh deleted file mode 100644 index aa98ffa9cb..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/git_push.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ -# -# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" - -git_user_id=$1 -git_repo_id=$2 -release_note=$3 -git_host=$4 - -if [ "$git_host" = "" ]; then - git_host="github.com" - echo "[INFO] No command line input provided. Set \$git_host to $git_host" -fi - -if [ "$git_user_id" = "" ]; then - git_user_id="formancehq" - echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" -fi - -if [ "$git_repo_id" = "" ]; then - git_repo_id="payments" - echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" -fi - -if [ "$release_note" = "" ]; then - release_note="Minor update" - echo "[INFO] No command line input provided. Set \$release_note to $release_note" -fi - -# Initialize the local directory as a Git repository -git init - -# Adds the files in the local repository and stages them for commit. -git add . - -# Commits the tracked changes and prepares them to be pushed to a remote repository. -git commit -m "$release_note" - -# Sets the new remote -git_remote=$(git remote) -if [ "$git_remote" = "" ]; then # git remote not defined - - if [ "$GIT_TOKEN" = "" ]; then - echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." - git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git - else - git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git - fi - -fi - -git pull origin master - -# Pushes (Forces) the changes in the local repository up to the remote repository -echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" -git push origin master 2>&1 | grep -v 'To https' diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/go.mod b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/go.mod deleted file mode 100644 index 8ffaa15379..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/formancehq/payments/genericclient - -go 1.18 diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/go.sum b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/go.sum deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_account.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_account.go deleted file mode 100644 index 9cf515289b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_account.go +++ /dev/null @@ -1,209 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "time" -) - -// checks if the Account type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Account{} - -// Account struct for Account -type Account struct { - Id string `json:"id"` - AccountName string `json:"accountName"` - CreatedAt time.Time `json:"createdAt"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// NewAccount instantiates a new Account object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewAccount(id string, accountName string, createdAt time.Time) *Account { - this := Account{} - this.Id = id - this.AccountName = accountName - this.CreatedAt = createdAt - return &this -} - -// NewAccountWithDefaults instantiates a new Account object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewAccountWithDefaults() *Account { - this := Account{} - return &this -} - -// GetId returns the Id field value -func (o *Account) GetId() string { - if o == nil { - var ret string - return ret - } - - return o.Id -} - -// GetIdOk returns a tuple with the Id field value -// and a boolean to check if the value has been set. -func (o *Account) GetIdOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Id, true -} - -// SetId sets field value -func (o *Account) SetId(v string) { - o.Id = v -} - -// GetAccountName returns the AccountName field value -func (o *Account) GetAccountName() string { - if o == nil { - var ret string - return ret - } - - return o.AccountName -} - -// GetAccountNameOk returns a tuple with the AccountName field value -// and a boolean to check if the value has been set. -func (o *Account) GetAccountNameOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.AccountName, true -} - -// SetAccountName sets field value -func (o *Account) SetAccountName(v string) { - o.AccountName = v -} - -// GetCreatedAt returns the CreatedAt field value -func (o *Account) GetCreatedAt() time.Time { - if o == nil { - var ret time.Time - return ret - } - - return o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value -// and a boolean to check if the value has been set. -func (o *Account) GetCreatedAtOk() (*time.Time, bool) { - if o == nil { - return nil, false - } - return &o.CreatedAt, true -} - -// SetCreatedAt sets field value -func (o *Account) SetCreatedAt(v time.Time) { - o.CreatedAt = v -} - -// GetMetadata returns the Metadata field value if set, zero value otherwise (both if not set or set to explicit null). -func (o *Account) GetMetadata() map[string]string { - if o == nil { - var ret map[string]string - return ret - } - return o.Metadata -} - -// GetMetadataOk returns a tuple with the Metadata field value if set, nil otherwise -// and a boolean to check if the value has been set. -// NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *Account) GetMetadataOk() (*map[string]string, bool) { - if o == nil || IsNil(o.Metadata) { - return nil, false - } - return &o.Metadata, true -} - -// HasMetadata returns a boolean if a field has been set. -func (o *Account) HasMetadata() bool { - if o != nil && IsNil(o.Metadata) { - return true - } - - return false -} - -// SetMetadata gets a reference to the given map[string]string and assigns it to the Metadata field. -func (o *Account) SetMetadata(v map[string]string) { - o.Metadata = v -} - -func (o Account) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Account) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["id"] = o.Id - toSerialize["accountName"] = o.AccountName - toSerialize["createdAt"] = o.CreatedAt - if o.Metadata != nil { - toSerialize["metadata"] = o.Metadata - } - return toSerialize, nil -} - -type NullableAccount struct { - value *Account - isSet bool -} - -func (v NullableAccount) Get() *Account { - return v.value -} - -func (v *NullableAccount) Set(val *Account) { - v.value = val - v.isSet = true -} - -func (v NullableAccount) IsSet() bool { - return v.isSet -} - -func (v *NullableAccount) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableAccount(val *Account) *NullableAccount { - return &NullableAccount{value: val, isSet: true} -} - -func (v NullableAccount) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableAccount) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balance.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balance.go deleted file mode 100644 index b19651a845..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balance.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" -) - -// checks if the Balance type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Balance{} - -// Balance struct for Balance -type Balance struct { - Amount string `json:"amount"` - Currency string `json:"currency"` -} - -// NewBalance instantiates a new Balance object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewBalance(amount string, currency string) *Balance { - this := Balance{} - this.Amount = amount - this.Currency = currency - return &this -} - -// NewBalanceWithDefaults instantiates a new Balance object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewBalanceWithDefaults() *Balance { - this := Balance{} - return &this -} - -// GetAmount returns the Amount field value -func (o *Balance) GetAmount() string { - if o == nil { - var ret string - return ret - } - - return o.Amount -} - -// GetAmountOk returns a tuple with the Amount field value -// and a boolean to check if the value has been set. -func (o *Balance) GetAmountOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Amount, true -} - -// SetAmount sets field value -func (o *Balance) SetAmount(v string) { - o.Amount = v -} - -// GetCurrency returns the Currency field value -func (o *Balance) GetCurrency() string { - if o == nil { - var ret string - return ret - } - - return o.Currency -} - -// GetCurrencyOk returns a tuple with the Currency field value -// and a boolean to check if the value has been set. -func (o *Balance) GetCurrencyOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Currency, true -} - -// SetCurrency sets field value -func (o *Balance) SetCurrency(v string) { - o.Currency = v -} - -func (o Balance) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Balance) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["amount"] = o.Amount - toSerialize["currency"] = o.Currency - return toSerialize, nil -} - -type NullableBalance struct { - value *Balance - isSet bool -} - -func (v NullableBalance) Get() *Balance { - return v.value -} - -func (v *NullableBalance) Set(val *Balance) { - v.value = val - v.isSet = true -} - -func (v NullableBalance) IsSet() bool { - return v.isSet -} - -func (v *NullableBalance) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableBalance(val *Balance) *NullableBalance { - return &NullableBalance{value: val, isSet: true} -} - -func (v NullableBalance) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableBalance) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balances.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balances.go deleted file mode 100644 index e106b17c14..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_balances.go +++ /dev/null @@ -1,199 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "time" -) - -// checks if the Balances type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Balances{} - -// Balances struct for Balances -type Balances struct { - Id string `json:"id"` - AccountID string `json:"accountID"` - At time.Time `json:"at"` - Balances []Balance `json:"balances"` -} - -// NewBalances instantiates a new Balances object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewBalances(id string, accountID string, at time.Time, balances []Balance) *Balances { - this := Balances{} - this.Id = id - this.AccountID = accountID - this.At = at - this.Balances = balances - return &this -} - -// NewBalancesWithDefaults instantiates a new Balances object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewBalancesWithDefaults() *Balances { - this := Balances{} - return &this -} - -// GetId returns the Id field value -func (o *Balances) GetId() string { - if o == nil { - var ret string - return ret - } - - return o.Id -} - -// GetIdOk returns a tuple with the Id field value -// and a boolean to check if the value has been set. -func (o *Balances) GetIdOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Id, true -} - -// SetId sets field value -func (o *Balances) SetId(v string) { - o.Id = v -} - -// GetAccountID returns the AccountID field value -func (o *Balances) GetAccountID() string { - if o == nil { - var ret string - return ret - } - - return o.AccountID -} - -// GetAccountIDOk returns a tuple with the AccountID field value -// and a boolean to check if the value has been set. -func (o *Balances) GetAccountIDOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.AccountID, true -} - -// SetAccountID sets field value -func (o *Balances) SetAccountID(v string) { - o.AccountID = v -} - -// GetAt returns the At field value -func (o *Balances) GetAt() time.Time { - if o == nil { - var ret time.Time - return ret - } - - return o.At -} - -// GetAtOk returns a tuple with the At field value -// and a boolean to check if the value has been set. -func (o *Balances) GetAtOk() (*time.Time, bool) { - if o == nil { - return nil, false - } - return &o.At, true -} - -// SetAt sets field value -func (o *Balances) SetAt(v time.Time) { - o.At = v -} - -// GetBalances returns the Balances field value -func (o *Balances) GetBalances() []Balance { - if o == nil { - var ret []Balance - return ret - } - - return o.Balances -} - -// GetBalancesOk returns a tuple with the Balances field value -// and a boolean to check if the value has been set. -func (o *Balances) GetBalancesOk() ([]Balance, bool) { - if o == nil { - return nil, false - } - return o.Balances, true -} - -// SetBalances sets field value -func (o *Balances) SetBalances(v []Balance) { - o.Balances = v -} - -func (o Balances) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Balances) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["id"] = o.Id - toSerialize["accountID"] = o.AccountID - toSerialize["at"] = o.At - toSerialize["balances"] = o.Balances - return toSerialize, nil -} - -type NullableBalances struct { - value *Balances - isSet bool -} - -func (v NullableBalances) Get() *Balances { - return v.value -} - -func (v *NullableBalances) Set(val *Balances) { - v.value = val - v.isSet = true -} - -func (v NullableBalances) IsSet() bool { - return v.isSet -} - -func (v *NullableBalances) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableBalances(val *Balances) *NullableBalances { - return &NullableBalances{value: val, isSet: true} -} - -func (v NullableBalances) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableBalances) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_beneficiary.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_beneficiary.go deleted file mode 100644 index 9677bd5af4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_beneficiary.go +++ /dev/null @@ -1,209 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "time" -) - -// checks if the Beneficiary type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Beneficiary{} - -// Beneficiary struct for Beneficiary -type Beneficiary struct { - Id string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - OwnerName string `json:"ownerName"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// NewBeneficiary instantiates a new Beneficiary object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewBeneficiary(id string, createdAt time.Time, ownerName string) *Beneficiary { - this := Beneficiary{} - this.Id = id - this.CreatedAt = createdAt - this.OwnerName = ownerName - return &this -} - -// NewBeneficiaryWithDefaults instantiates a new Beneficiary object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewBeneficiaryWithDefaults() *Beneficiary { - this := Beneficiary{} - return &this -} - -// GetId returns the Id field value -func (o *Beneficiary) GetId() string { - if o == nil { - var ret string - return ret - } - - return o.Id -} - -// GetIdOk returns a tuple with the Id field value -// and a boolean to check if the value has been set. -func (o *Beneficiary) GetIdOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Id, true -} - -// SetId sets field value -func (o *Beneficiary) SetId(v string) { - o.Id = v -} - -// GetCreatedAt returns the CreatedAt field value -func (o *Beneficiary) GetCreatedAt() time.Time { - if o == nil { - var ret time.Time - return ret - } - - return o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value -// and a boolean to check if the value has been set. -func (o *Beneficiary) GetCreatedAtOk() (*time.Time, bool) { - if o == nil { - return nil, false - } - return &o.CreatedAt, true -} - -// SetCreatedAt sets field value -func (o *Beneficiary) SetCreatedAt(v time.Time) { - o.CreatedAt = v -} - -// GetOwnerName returns the OwnerName field value -func (o *Beneficiary) GetOwnerName() string { - if o == nil { - var ret string - return ret - } - - return o.OwnerName -} - -// GetOwnerNameOk returns a tuple with the OwnerName field value -// and a boolean to check if the value has been set. -func (o *Beneficiary) GetOwnerNameOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.OwnerName, true -} - -// SetOwnerName sets field value -func (o *Beneficiary) SetOwnerName(v string) { - o.OwnerName = v -} - -// GetMetadata returns the Metadata field value if set, zero value otherwise (both if not set or set to explicit null). -func (o *Beneficiary) GetMetadata() map[string]string { - if o == nil { - var ret map[string]string - return ret - } - return o.Metadata -} - -// GetMetadataOk returns a tuple with the Metadata field value if set, nil otherwise -// and a boolean to check if the value has been set. -// NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *Beneficiary) GetMetadataOk() (*map[string]string, bool) { - if o == nil || IsNil(o.Metadata) { - return nil, false - } - return &o.Metadata, true -} - -// HasMetadata returns a boolean if a field has been set. -func (o *Beneficiary) HasMetadata() bool { - if o != nil && IsNil(o.Metadata) { - return true - } - - return false -} - -// SetMetadata gets a reference to the given map[string]string and assigns it to the Metadata field. -func (o *Beneficiary) SetMetadata(v map[string]string) { - o.Metadata = v -} - -func (o Beneficiary) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Beneficiary) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["id"] = o.Id - toSerialize["createdAt"] = o.CreatedAt - toSerialize["ownerName"] = o.OwnerName - if o.Metadata != nil { - toSerialize["metadata"] = o.Metadata - } - return toSerialize, nil -} - -type NullableBeneficiary struct { - value *Beneficiary - isSet bool -} - -func (v NullableBeneficiary) Get() *Beneficiary { - return v.value -} - -func (v *NullableBeneficiary) Set(val *Beneficiary) { - v.value = val - v.isSet = true -} - -func (v NullableBeneficiary) IsSet() bool { - return v.isSet -} - -func (v *NullableBeneficiary) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableBeneficiary(val *Beneficiary) *NullableBeneficiary { - return &NullableBeneficiary{value: val, isSet: true} -} - -func (v NullableBeneficiary) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableBeneficiary) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_error.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_error.go deleted file mode 100644 index 085c21f12e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_error.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" -) - -// checks if the Error type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Error{} - -// Error struct for Error -type Error struct { - Title string `json:"Title"` - Detail string `json:"Detail"` -} - -// NewError instantiates a new Error object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewError(title string, detail string) *Error { - this := Error{} - this.Title = title - this.Detail = detail - return &this -} - -// NewErrorWithDefaults instantiates a new Error object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewErrorWithDefaults() *Error { - this := Error{} - return &this -} - -// GetTitle returns the Title field value -func (o *Error) GetTitle() string { - if o == nil { - var ret string - return ret - } - - return o.Title -} - -// GetTitleOk returns a tuple with the Title field value -// and a boolean to check if the value has been set. -func (o *Error) GetTitleOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Title, true -} - -// SetTitle sets field value -func (o *Error) SetTitle(v string) { - o.Title = v -} - -// GetDetail returns the Detail field value -func (o *Error) GetDetail() string { - if o == nil { - var ret string - return ret - } - - return o.Detail -} - -// GetDetailOk returns a tuple with the Detail field value -// and a boolean to check if the value has been set. -func (o *Error) GetDetailOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Detail, true -} - -// SetDetail sets field value -func (o *Error) SetDetail(v string) { - o.Detail = v -} - -func (o Error) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Error) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["Title"] = o.Title - toSerialize["Detail"] = o.Detail - return toSerialize, nil -} - -type NullableError struct { - value *Error - isSet bool -} - -func (v NullableError) Get() *Error { - return v.value -} - -func (v *NullableError) Set(val *Error) { - v.value = val - v.isSet = true -} - -func (v NullableError) IsSet() bool { - return v.isSet -} - -func (v *NullableError) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableError(val *Error) *NullableError { - return &NullableError{value: val, isSet: true} -} - -func (v NullableError) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableError) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction.go deleted file mode 100644 index 25b75a670b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction.go +++ /dev/null @@ -1,461 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "time" -) - -// checks if the Transaction type satisfies the MappedNullable interface at compile time -var _ MappedNullable = &Transaction{} - -// Transaction struct for Transaction -type Transaction struct { - Id string `json:"id"` - RelatedTransactionID *string `json:"relatedTransactionID,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Currency string `json:"currency"` - Scheme *string `json:"scheme,omitempty"` - Type TransactionType `json:"type"` - Status TransactionStatus `json:"status"` - Amount string `json:"amount"` - SourceAccountID *string `json:"sourceAccountID,omitempty"` - DestinationAccountID *string `json:"destinationAccountID,omitempty"` - Metadata map[string]string `json:"metadata,omitempty"` -} - -// NewTransaction instantiates a new Transaction object -// This constructor will assign default values to properties that have it defined, -// and makes sure properties required by API are set, but the set of arguments -// will change when the set of required properties is changed -func NewTransaction(id string, createdAt time.Time, updatedAt time.Time, currency string, type_ TransactionType, status TransactionStatus, amount string) *Transaction { - this := Transaction{} - this.Id = id - this.CreatedAt = createdAt - this.UpdatedAt = updatedAt - this.Currency = currency - this.Type = type_ - this.Status = status - this.Amount = amount - return &this -} - -// NewTransactionWithDefaults instantiates a new Transaction object -// This constructor will only assign default values to properties that have it defined, -// but it doesn't guarantee that properties required by API are set -func NewTransactionWithDefaults() *Transaction { - this := Transaction{} - return &this -} - -// GetId returns the Id field value -func (o *Transaction) GetId() string { - if o == nil { - var ret string - return ret - } - - return o.Id -} - -// GetIdOk returns a tuple with the Id field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetIdOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Id, true -} - -// SetId sets field value -func (o *Transaction) SetId(v string) { - o.Id = v -} - -// GetRelatedTransactionID returns the RelatedTransactionID field value if set, zero value otherwise. -func (o *Transaction) GetRelatedTransactionID() string { - if o == nil || IsNil(o.RelatedTransactionID) { - var ret string - return ret - } - return *o.RelatedTransactionID -} - -// GetRelatedTransactionIDOk returns a tuple with the RelatedTransactionID field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Transaction) GetRelatedTransactionIDOk() (*string, bool) { - if o == nil || IsNil(o.RelatedTransactionID) { - return nil, false - } - return o.RelatedTransactionID, true -} - -// HasRelatedTransactionID returns a boolean if a field has been set. -func (o *Transaction) HasRelatedTransactionID() bool { - if o != nil && !IsNil(o.RelatedTransactionID) { - return true - } - - return false -} - -// SetRelatedTransactionID gets a reference to the given string and assigns it to the RelatedTransactionID field. -func (o *Transaction) SetRelatedTransactionID(v string) { - o.RelatedTransactionID = &v -} - -// GetCreatedAt returns the CreatedAt field value -func (o *Transaction) GetCreatedAt() time.Time { - if o == nil { - var ret time.Time - return ret - } - - return o.CreatedAt -} - -// GetCreatedAtOk returns a tuple with the CreatedAt field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetCreatedAtOk() (*time.Time, bool) { - if o == nil { - return nil, false - } - return &o.CreatedAt, true -} - -// SetCreatedAt sets field value -func (o *Transaction) SetCreatedAt(v time.Time) { - o.CreatedAt = v -} - -// GetUpdatedAt returns the UpdatedAt field value -func (o *Transaction) GetUpdatedAt() time.Time { - if o == nil { - var ret time.Time - return ret - } - - return o.UpdatedAt -} - -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil { - return nil, false - } - return &o.UpdatedAt, true -} - -// SetUpdatedAt sets field value -func (o *Transaction) SetUpdatedAt(v time.Time) { - o.UpdatedAt = v -} - -// GetCurrency returns the Currency field value -func (o *Transaction) GetCurrency() string { - if o == nil { - var ret string - return ret - } - - return o.Currency -} - -// GetCurrencyOk returns a tuple with the Currency field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetCurrencyOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Currency, true -} - -// SetCurrency sets field value -func (o *Transaction) SetCurrency(v string) { - o.Currency = v -} - -// GetScheme returns the Scheme field value if set, zero value otherwise. -func (o *Transaction) GetScheme() string { - if o == nil || IsNil(o.Scheme) { - var ret string - return ret - } - return *o.Scheme -} - -// GetSchemeOk returns a tuple with the Scheme field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Transaction) GetSchemeOk() (*string, bool) { - if o == nil || IsNil(o.Scheme) { - return nil, false - } - return o.Scheme, true -} - -// HasScheme returns a boolean if a field has been set. -func (o *Transaction) HasScheme() bool { - if o != nil && !IsNil(o.Scheme) { - return true - } - - return false -} - -// SetScheme gets a reference to the given string and assigns it to the Scheme field. -func (o *Transaction) SetScheme(v string) { - o.Scheme = &v -} - -// GetType returns the Type field value -func (o *Transaction) GetType() TransactionType { - if o == nil { - var ret TransactionType - return ret - } - - return o.Type -} - -// GetTypeOk returns a tuple with the Type field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetTypeOk() (*TransactionType, bool) { - if o == nil { - return nil, false - } - return &o.Type, true -} - -// SetType sets field value -func (o *Transaction) SetType(v TransactionType) { - o.Type = v -} - -// GetStatus returns the Status field value -func (o *Transaction) GetStatus() TransactionStatus { - if o == nil { - var ret TransactionStatus - return ret - } - - return o.Status -} - -// GetStatusOk returns a tuple with the Status field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetStatusOk() (*TransactionStatus, bool) { - if o == nil { - return nil, false - } - return &o.Status, true -} - -// SetStatus sets field value -func (o *Transaction) SetStatus(v TransactionStatus) { - o.Status = v -} - -// GetAmount returns the Amount field value -func (o *Transaction) GetAmount() string { - if o == nil { - var ret string - return ret - } - - return o.Amount -} - -// GetAmountOk returns a tuple with the Amount field value -// and a boolean to check if the value has been set. -func (o *Transaction) GetAmountOk() (*string, bool) { - if o == nil { - return nil, false - } - return &o.Amount, true -} - -// SetAmount sets field value -func (o *Transaction) SetAmount(v string) { - o.Amount = v -} - -// GetSourceAccountID returns the SourceAccountID field value if set, zero value otherwise. -func (o *Transaction) GetSourceAccountID() string { - if o == nil || IsNil(o.SourceAccountID) { - var ret string - return ret - } - return *o.SourceAccountID -} - -// GetSourceAccountIDOk returns a tuple with the SourceAccountID field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Transaction) GetSourceAccountIDOk() (*string, bool) { - if o == nil || IsNil(o.SourceAccountID) { - return nil, false - } - return o.SourceAccountID, true -} - -// HasSourceAccountID returns a boolean if a field has been set. -func (o *Transaction) HasSourceAccountID() bool { - if o != nil && !IsNil(o.SourceAccountID) { - return true - } - - return false -} - -// SetSourceAccountID gets a reference to the given string and assigns it to the SourceAccountID field. -func (o *Transaction) SetSourceAccountID(v string) { - o.SourceAccountID = &v -} - -// GetDestinationAccountID returns the DestinationAccountID field value if set, zero value otherwise. -func (o *Transaction) GetDestinationAccountID() string { - if o == nil || IsNil(o.DestinationAccountID) { - var ret string - return ret - } - return *o.DestinationAccountID -} - -// GetDestinationAccountIDOk returns a tuple with the DestinationAccountID field value if set, nil otherwise -// and a boolean to check if the value has been set. -func (o *Transaction) GetDestinationAccountIDOk() (*string, bool) { - if o == nil || IsNil(o.DestinationAccountID) { - return nil, false - } - return o.DestinationAccountID, true -} - -// HasDestinationAccountID returns a boolean if a field has been set. -func (o *Transaction) HasDestinationAccountID() bool { - if o != nil && !IsNil(o.DestinationAccountID) { - return true - } - - return false -} - -// SetDestinationAccountID gets a reference to the given string and assigns it to the DestinationAccountID field. -func (o *Transaction) SetDestinationAccountID(v string) { - o.DestinationAccountID = &v -} - -// GetMetadata returns the Metadata field value if set, zero value otherwise (both if not set or set to explicit null). -func (o *Transaction) GetMetadata() map[string]string { - if o == nil { - var ret map[string]string - return ret - } - return o.Metadata -} - -// GetMetadataOk returns a tuple with the Metadata field value if set, nil otherwise -// and a boolean to check if the value has been set. -// NOTE: If the value is an explicit nil, `nil, true` will be returned -func (o *Transaction) GetMetadataOk() (*map[string]string, bool) { - if o == nil || IsNil(o.Metadata) { - return nil, false - } - return &o.Metadata, true -} - -// HasMetadata returns a boolean if a field has been set. -func (o *Transaction) HasMetadata() bool { - if o != nil && IsNil(o.Metadata) { - return true - } - - return false -} - -// SetMetadata gets a reference to the given map[string]string and assigns it to the Metadata field. -func (o *Transaction) SetMetadata(v map[string]string) { - o.Metadata = v -} - -func (o Transaction) MarshalJSON() ([]byte, error) { - toSerialize,err := o.ToMap() - if err != nil { - return []byte{}, err - } - return json.Marshal(toSerialize) -} - -func (o Transaction) ToMap() (map[string]interface{}, error) { - toSerialize := map[string]interface{}{} - toSerialize["id"] = o.Id - if !IsNil(o.RelatedTransactionID) { - toSerialize["relatedTransactionID"] = o.RelatedTransactionID - } - toSerialize["createdAt"] = o.CreatedAt - toSerialize["updatedAt"] = o.UpdatedAt - toSerialize["currency"] = o.Currency - if !IsNil(o.Scheme) { - toSerialize["scheme"] = o.Scheme - } - toSerialize["type"] = o.Type - toSerialize["status"] = o.Status - toSerialize["amount"] = o.Amount - if !IsNil(o.SourceAccountID) { - toSerialize["sourceAccountID"] = o.SourceAccountID - } - if !IsNil(o.DestinationAccountID) { - toSerialize["destinationAccountID"] = o.DestinationAccountID - } - if o.Metadata != nil { - toSerialize["metadata"] = o.Metadata - } - return toSerialize, nil -} - -type NullableTransaction struct { - value *Transaction - isSet bool -} - -func (v NullableTransaction) Get() *Transaction { - return v.value -} - -func (v *NullableTransaction) Set(val *Transaction) { - v.value = val - v.isSet = true -} - -func (v NullableTransaction) IsSet() bool { - return v.isSet -} - -func (v *NullableTransaction) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableTransaction(val *Transaction) *NullableTransaction { - return &NullableTransaction{value: val, isSet: true} -} - -func (v NullableTransaction) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableTransaction) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_status.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_status.go deleted file mode 100644 index 135e86e7a0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_status.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "fmt" -) - -// TransactionStatus the model 'TransactionStatus' -type TransactionStatus string - -// List of TransactionStatus -const ( - PENDING TransactionStatus = "PENDING" - SUCCEEDED TransactionStatus = "SUCCEEDED" - FAILED TransactionStatus = "FAILED" -) - -// All allowed values of TransactionStatus enum -var AllowedTransactionStatusEnumValues = []TransactionStatus{ - "PENDING", - "SUCCEEDED", - "FAILED", -} - -func (v *TransactionStatus) UnmarshalJSON(src []byte) error { - var value string - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := TransactionStatus(value) - for _, existing := range AllowedTransactionStatusEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil - } - } - - return fmt.Errorf("%+v is not a valid TransactionStatus", value) -} - -// NewTransactionStatusFromValue returns a pointer to a valid TransactionStatus -// for the value passed as argument, or an error if the value passed is not allowed by the enum -func NewTransactionStatusFromValue(v string) (*TransactionStatus, error) { - ev := TransactionStatus(v) - if ev.IsValid() { - return &ev, nil - } else { - return nil, fmt.Errorf("invalid value '%v' for TransactionStatus: valid values are %v", v, AllowedTransactionStatusEnumValues) - } -} - -// IsValid return true if the value is valid for the enum, false otherwise -func (v TransactionStatus) IsValid() bool { - for _, existing := range AllowedTransactionStatusEnumValues { - if existing == v { - return true - } - } - return false -} - -// Ptr returns reference to TransactionStatus value -func (v TransactionStatus) Ptr() *TransactionStatus { - return &v -} - -type NullableTransactionStatus struct { - value *TransactionStatus - isSet bool -} - -func (v NullableTransactionStatus) Get() *TransactionStatus { - return v.value -} - -func (v *NullableTransactionStatus) Set(val *TransactionStatus) { - v.value = val - v.isSet = true -} - -func (v NullableTransactionStatus) IsSet() bool { - return v.isSet -} - -func (v *NullableTransactionStatus) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableTransactionStatus(val *TransactionStatus) *NullableTransactionStatus { - return &NullableTransactionStatus{value: val, isSet: true} -} - -func (v NullableTransactionStatus) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableTransactionStatus) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_type.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_type.go deleted file mode 100644 index 0b6ee10aa5..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/model_transaction_type.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "fmt" -) - -// TransactionType the model 'TransactionType' -type TransactionType string - -// List of TransactionType -const ( - PAYIN TransactionType = "PAYIN" - PAYOUT TransactionType = "PAYOUT" - TRANSFER TransactionType = "TRANSFER" -) - -// All allowed values of TransactionType enum -var AllowedTransactionTypeEnumValues = []TransactionType{ - "PAYIN", - "PAYOUT", - "TRANSFER", -} - -func (v *TransactionType) UnmarshalJSON(src []byte) error { - var value string - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := TransactionType(value) - for _, existing := range AllowedTransactionTypeEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil - } - } - - return fmt.Errorf("%+v is not a valid TransactionType", value) -} - -// NewTransactionTypeFromValue returns a pointer to a valid TransactionType -// for the value passed as argument, or an error if the value passed is not allowed by the enum -func NewTransactionTypeFromValue(v string) (*TransactionType, error) { - ev := TransactionType(v) - if ev.IsValid() { - return &ev, nil - } else { - return nil, fmt.Errorf("invalid value '%v' for TransactionType: valid values are %v", v, AllowedTransactionTypeEnumValues) - } -} - -// IsValid return true if the value is valid for the enum, false otherwise -func (v TransactionType) IsValid() bool { - for _, existing := range AllowedTransactionTypeEnumValues { - if existing == v { - return true - } - } - return false -} - -// Ptr returns reference to TransactionType value -func (v TransactionType) Ptr() *TransactionType { - return &v -} - -type NullableTransactionType struct { - value *TransactionType - isSet bool -} - -func (v NullableTransactionType) Get() *TransactionType { - return v.value -} - -func (v *NullableTransactionType) Set(val *TransactionType) { - v.value = val - v.isSet = true -} - -func (v NullableTransactionType) IsSet() bool { - return v.isSet -} - -func (v *NullableTransactionType) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableTransactionType(val *TransactionType) *NullableTransactionType { - return &NullableTransactionType{value: val, isSet: true} -} - -func (v NullableTransactionType) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableTransactionType) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/response.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/response.go deleted file mode 100644 index 404b486b0b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/response.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "net/http" -) - -// APIResponse stores the API response returned by the server. -type APIResponse struct { - *http.Response `json:"-"` - Message string `json:"message,omitempty"` - // Operation is the name of the OpenAPI operation. - Operation string `json:"operation,omitempty"` - // RequestURL is the request URL. This value is always available, even if the - // embedded *http.Response is nil. - RequestURL string `json:"url,omitempty"` - // Method is the HTTP method used for the request. This value is always - // available, even if the embedded *http.Response is nil. - Method string `json:"method,omitempty"` - // Payload holds the contents of the response body (which may be nil or empty). - // This is provided here as the raw response.Body() reader will have already - // been drained. - Payload []byte `json:"-"` -} - -// NewAPIResponse returns a new APIResponse object. -func NewAPIResponse(r *http.Response) *APIResponse { - - response := &APIResponse{Response: r} - return response -} - -// NewAPIResponseWithError returns a new APIResponse object with the provided error message. -func NewAPIResponseWithError(errorMessage string) *APIResponse { - - response := &APIResponse{Message: errorMessage} - return response -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/utils.go b/components/payments/cmd/connectors/internal/connectors/generic/client/generated/utils.go deleted file mode 100644 index eb4d077e94..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generated/utils.go +++ /dev/null @@ -1,347 +0,0 @@ -/* -GENERIC connector API - -No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - -API version: v0.1 -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. - -package genericclient - -import ( - "encoding/json" - "reflect" - "time" -) - -// PtrBool is a helper routine that returns a pointer to given boolean value. -func PtrBool(v bool) *bool { return &v } - -// PtrInt is a helper routine that returns a pointer to given integer value. -func PtrInt(v int) *int { return &v } - -// PtrInt32 is a helper routine that returns a pointer to given integer value. -func PtrInt32(v int32) *int32 { return &v } - -// PtrInt64 is a helper routine that returns a pointer to given integer value. -func PtrInt64(v int64) *int64 { return &v } - -// PtrFloat32 is a helper routine that returns a pointer to given float value. -func PtrFloat32(v float32) *float32 { return &v } - -// PtrFloat64 is a helper routine that returns a pointer to given float value. -func PtrFloat64(v float64) *float64 { return &v } - -// PtrString is a helper routine that returns a pointer to given string value. -func PtrString(v string) *string { return &v } - -// PtrTime is helper routine that returns a pointer to given Time value. -func PtrTime(v time.Time) *time.Time { return &v } - -type NullableBool struct { - value *bool - isSet bool -} - -func (v NullableBool) Get() *bool { - return v.value -} - -func (v *NullableBool) Set(val *bool) { - v.value = val - v.isSet = true -} - -func (v NullableBool) IsSet() bool { - return v.isSet -} - -func (v *NullableBool) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableBool(val *bool) *NullableBool { - return &NullableBool{value: val, isSet: true} -} - -func (v NullableBool) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableBool) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableInt struct { - value *int - isSet bool -} - -func (v NullableInt) Get() *int { - return v.value -} - -func (v *NullableInt) Set(val *int) { - v.value = val - v.isSet = true -} - -func (v NullableInt) IsSet() bool { - return v.isSet -} - -func (v *NullableInt) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableInt(val *int) *NullableInt { - return &NullableInt{value: val, isSet: true} -} - -func (v NullableInt) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableInt) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableInt32 struct { - value *int32 - isSet bool -} - -func (v NullableInt32) Get() *int32 { - return v.value -} - -func (v *NullableInt32) Set(val *int32) { - v.value = val - v.isSet = true -} - -func (v NullableInt32) IsSet() bool { - return v.isSet -} - -func (v *NullableInt32) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableInt32(val *int32) *NullableInt32 { - return &NullableInt32{value: val, isSet: true} -} - -func (v NullableInt32) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableInt32) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableInt64 struct { - value *int64 - isSet bool -} - -func (v NullableInt64) Get() *int64 { - return v.value -} - -func (v *NullableInt64) Set(val *int64) { - v.value = val - v.isSet = true -} - -func (v NullableInt64) IsSet() bool { - return v.isSet -} - -func (v *NullableInt64) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableInt64(val *int64) *NullableInt64 { - return &NullableInt64{value: val, isSet: true} -} - -func (v NullableInt64) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableInt64) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableFloat32 struct { - value *float32 - isSet bool -} - -func (v NullableFloat32) Get() *float32 { - return v.value -} - -func (v *NullableFloat32) Set(val *float32) { - v.value = val - v.isSet = true -} - -func (v NullableFloat32) IsSet() bool { - return v.isSet -} - -func (v *NullableFloat32) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableFloat32(val *float32) *NullableFloat32 { - return &NullableFloat32{value: val, isSet: true} -} - -func (v NullableFloat32) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableFloat32) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableFloat64 struct { - value *float64 - isSet bool -} - -func (v NullableFloat64) Get() *float64 { - return v.value -} - -func (v *NullableFloat64) Set(val *float64) { - v.value = val - v.isSet = true -} - -func (v NullableFloat64) IsSet() bool { - return v.isSet -} - -func (v *NullableFloat64) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableFloat64(val *float64) *NullableFloat64 { - return &NullableFloat64{value: val, isSet: true} -} - -func (v NullableFloat64) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableFloat64) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableString struct { - value *string - isSet bool -} - -func (v NullableString) Get() *string { - return v.value -} - -func (v *NullableString) Set(val *string) { - v.value = val - v.isSet = true -} - -func (v NullableString) IsSet() bool { - return v.isSet -} - -func (v *NullableString) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableString(val *string) *NullableString { - return &NullableString{value: val, isSet: true} -} - -func (v NullableString) MarshalJSON() ([]byte, error) { - return json.Marshal(v.value) -} - -func (v *NullableString) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -type NullableTime struct { - value *time.Time - isSet bool -} - -func (v NullableTime) Get() *time.Time { - return v.value -} - -func (v *NullableTime) Set(val *time.Time) { - v.value = val - v.isSet = true -} - -func (v NullableTime) IsSet() bool { - return v.isSet -} - -func (v *NullableTime) Unset() { - v.value = nil - v.isSet = false -} - -func NewNullableTime(val *time.Time) *NullableTime { - return &NullableTime{value: val, isSet: true} -} - -func (v NullableTime) MarshalJSON() ([]byte, error) { - return v.value.MarshalJSON() -} - -func (v *NullableTime) UnmarshalJSON(src []byte) error { - v.isSet = true - return json.Unmarshal(src, &v.value) -} - -// IsNil checks if an input is nil -func IsNil(i interface{}) bool { - if i == nil { - return true - } - switch reflect.TypeOf(i).Kind() { - case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice: - return reflect.ValueOf(i).IsNil() - case reflect.Array: - return reflect.ValueOf(i).IsZero() - } - return false -} - -type MappedNullable interface { - ToMap() (map[string]interface{}, error) -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/generic-openapi.yaml b/components/payments/cmd/connectors/internal/connectors/generic/client/generic-openapi.yaml deleted file mode 100644 index 4782cf9d38..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/generic-openapi.yaml +++ /dev/null @@ -1,298 +0,0 @@ -openapi: 3.0.3 -info: - title: GENERIC connector API - version: "v0.1" - -# ---------------------- PATHS ---------------------- -paths: - /accounts: - get: - summary: Get all accounts - operationId: getAccounts - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Page' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/CreatedAtFrom' - responses: - 200: - $ref: '#/components/responses/Accounts' - default: - $ref: '#/components/responses/ErrorResponse' - - /accounts/{accountId}/balances: - get: - summary: Get account balance - operationId: getAccountBalances - parameters: - - $ref: '#/components/parameters/AccountId' - responses: - 200: - $ref: '#/components/responses/Balances' - default: - $ref: '#/components/responses/ErrorResponse' - - /beneficiaries: - get: - summary: Get all beneficiaries - operationId: getBeneficiaries - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Page' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/CreatedAtFrom' - responses: - 200: - $ref: '#/components/responses/Beneficiaries' - default: - $ref: '#/components/responses/ErrorResponse' - - /transactions: - get: - summary: Get all transactions - operationId: getTransactions - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Page' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/UpdatedAtFrom' - responses: - 200: - $ref: '#/components/responses/Transactions' - default: - $ref: '#/components/responses/ErrorResponse' - -# ---------------------- COMPONENTS ---------------------- -components: - # ---------------------- PARAMETERS ---------------------- - parameters: - AccountId: - name: accountId - in: path - required: true - schema: - type: string - PageSize: - name: pageSize - in: query - description: Number of items per page - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - default: 100 - Page: - name: page - in: query - description: Page number - example: 1 - schema: - type: integer - format: int64 - minimum: 1 - default: 1 - Sort: - name: sort - in: query - description: Sort order - example: createdAt:asc - schema: - type: string - CreatedAtFrom: - name: createdAtFrom - in: query - description: Filter by created at date - schema: - type: string - format: date-time - UpdatedAtFrom: - name: updatedAtFrom - in: query - description: Filter by updated at date - schema: - type: string - format: date-time - - # ---------------------- RESPONSES ---------------------- - responses: - NoContent: - description: No content - - ErrorResponse: - description: General error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - - Accounts: - description: List of accounts - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Account' - - Balances: - description: Account balances - content: - application/json: - schema: - $ref: '#/components/schemas/Balances' - - Beneficiaries: - description: List of beneficiaries - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Beneficiary' - - Transactions: - description: List of transactions - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Transaction' - - # ---------------------- SCHEMAS ---------------------- - schemas: - Error: - type: object - required: - - Title - - Detail - properties: - Title: - type: string - Detail: - type: string - - Account: - type: object - required: - - id - - accountName - - createdAt - properties: - id: - type: string - accountName: - type: string - createdAt: - type: string - format: date-time - metadata: - $ref: '#/components/schemas/Metadata' - - Balances: - type: object - required: - - id - - accountID - - at - - balances - properties: - id: - type: string - accountID: - type: string - at: - type: string - format: date-time - balances: - type: array - items: - $ref: '#/components/schemas/Balance' - - Balance: - type: object - required: - - amount - - currency - properties: - amount: - type: string - currency: - type: string - - Beneficiary: - type: object - required: - - id - - createdAt - - ownerName - properties: - id: - type: string - createdAt: - type: string - format: date-time - ownerName: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - - - Transaction: - type: object - required: - - id - - createdAt - - updatedAt - - currency - - type - - status - - amount - properties: - id: - type: string - relatedTransactionID: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - currency: - type: string - scheme: - type: string - type: - $ref: '#/components/schemas/TransactionType' - status: - $ref: '#/components/schemas/TransactionStatus' - amount: - type: string - sourceAccountID: - type: string - destinationAccountID: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - - Metadata: - type: object - additionalProperties: - type: string - nullable: true - - TransactionType: - type: string - enum: - - PAYIN - - PAYOUT - - TRANSFER - - TransactionStatus: - type: string - enum: - - PENDING - - SUCCEEDED - - FAILED \ No newline at end of file diff --git a/components/payments/cmd/connectors/internal/connectors/generic/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/generic/client/transactions.go deleted file mode 100644 index 2e0ca3c6a4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/client/transactions.go +++ /dev/null @@ -1,30 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/genericclient" -) - -func (c *Client) ListTransactions(ctx context.Context, page, pageSize int64, updatedAtFrom time.Time) ([]genericclient.Transaction, error) { - f := connectors.ClientMetrics(ctx, "generic", "list_transactions") - now := time.Now() - defer f(ctx, now) - - req := c.apiClient.DefaultApi.GetTransactions(ctx). - Page(page). - PageSize(pageSize) - - if !updatedAtFrom.IsZero() { - req = req.UpdatedAtFrom(updatedAtFrom) - } - - transactions, _, err := req.Execute() - if err != nil { - return nil, err - } - - return transactions, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/config.go b/components/payments/cmd/connectors/internal/connectors/generic/config.go deleted file mode 100644 index a74b1125fb..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/config.go +++ /dev/null @@ -1,53 +0,0 @@ -package generic - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - pageSize = 100 - defaultPollingPeriod = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - Endpoint string `json:"endpoint" yaml:"endpoint" bson:"endpoint"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -func (c Config) String() string { - return fmt.Sprintf("endpoint=%s", c.Endpoint) -} - -func (c Config) Validate() error { - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("endpoint", configtemplate.TypeString, "", false) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", false) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/connector.go b/components/payments/cmd/connectors/internal/connectors/generic/connector.go deleted file mode 100644 index cfd5d0aabe..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/connector.go +++ /dev/null @@ -1,119 +0,0 @@ -package generic - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderGeneric - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch users and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - c.cfg = cfg - - if c.cfg.Endpoint == "" { - // Nothing more to do - return nil - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - if c.cfg.Endpoint == "" { - // Nothing to do - return nil - } - - mainDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), mainDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/currencies.go b/components/payments/cmd/connectors/internal/connectors/generic/currencies.go deleted file mode 100644 index 501980dc2b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/currencies.go +++ /dev/null @@ -1,7 +0,0 @@ -package generic - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - supportedCurrenciesWithDecimal = currency.ISO4217Currencies -) diff --git a/components/payments/cmd/connectors/internal/connectors/generic/errors.go b/components/payments/cmd/connectors/internal/connectors/generic/errors.go deleted file mode 100644 index 707e9c68d9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package generic - -import "errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingEndpoint is returned when the endpoint is missing. - ErrMissingEndpoint = errors.New("missing endpoint from config") - - // ErrMissingName is returned when the name is missing. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/generic/loader.go b/components/payments/cmd/connectors/internal/connectors/generic/loader.go deleted file mode 100644 index 29342d85a8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/loader.go +++ /dev/null @@ -1,46 +0,0 @@ -package generic - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(store *storage.Storage) *mux.Router { - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_accounts.go deleted file mode 100644 index 73bf267b42..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_accounts.go +++ /dev/null @@ -1,130 +0,0 @@ -package generic - -import ( - "context" - "encoding/json" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchAccounts(client *client.Client, config *Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "generic.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - err := ingestAccounts(ctx, connectorID, client, ingester, scheduler) - if err != nil { - otel.RecordError(span, err) - return err - } - - taskTransactions, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions from client", - Key: taskNameFetchTransactions, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskTransactions, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func ingestAccounts( - ctx context.Context, - connectorID models.ConnectorID, - client *client.Client, - ingester ingestion.Ingester, - scheduler task.Scheduler, -) error { - - balancesTasks := make([]models.TaskDescriptor, 0) - for page := 1; ; page++ { - accounts, err := client.ListAccounts(ctx, int64(page), pageSize) - if err != nil { - return err - } - - if len(accounts) == 0 { - break - } - - accountsBatch := make([]*models.Account, 0, len(accounts)) - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: account.Id, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: account.CreatedAt, - Reference: account.Id, - AccountName: account.AccountName, - Type: models.AccountTypeInternal, - Metadata: account.Metadata, - RawData: raw, - }) - - balanceTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch balances from client", - Key: taskNameFetchBalances, - AccountID: account.Id, - }) - if err != nil { - return err - } - - balancesTasks = append(balancesTasks, balanceTask) - - } - - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch(accountsBatch)); err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - for _, balanceTask := range balancesTasks { - if err := scheduler.Schedule(ctx, balanceTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - } - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_balances.go b/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_balances.go deleted file mode 100644 index 32ea2cf759..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_balances.go +++ /dev/null @@ -1,88 +0,0 @@ -package generic - -import ( - "context" - "fmt" - "math/big" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/genericclient" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchBalances(client *client.Client, config *Config, accountID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "generic.taskFetchBalances", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - balances, err := client.GetBalances(ctx, accountID) - if err != nil { - // retryable error already handled by the client - otel.RecordError(span, err) - return err - } - - if err := ingestBalancesBatch(ctx, connectorID, ingester, accountID, balances); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestBalancesBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accountID string, - balances *genericclient.Balances, -) error { - if balances == nil { - return nil - } - - balancesBatch := make([]*models.Balance, 0, len(balances.Balances)) - for _, balance := range balances.Balances { - var amount big.Int - _, ok := amount.SetString(balance.Amount, 10) - if !ok { - return fmt.Errorf("failed to parse amount: %s", balance.Amount) - } - - balancesBatch = append(balancesBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Currency), - Balance: &amount, - CreatedAt: balances.At, - LastUpdatedAt: balances.At, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestBalances(ctx, ingestion.BalanceBatch(balancesBatch), false); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_beneficiaries.go deleted file mode 100644 index 7dadc85bfe..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_beneficiaries.go +++ /dev/null @@ -1,106 +0,0 @@ -package generic - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchBeneficiariesState struct { - LastCreatedAt time.Time -} - -func taskFetchBeneficiaries(client *client.Client, config *Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "generic.taskFetchBeneficiaries", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchBeneficiariesState{}) - - newState, err := ingestBeneficiaries(ctx, connectorID, client, ingester, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestBeneficiaries( - ctx context.Context, - connectorID models.ConnectorID, - client *client.Client, - ingester ingestion.Ingester, - state fetchBeneficiariesState, -) (fetchBeneficiariesState, error) { - newState := fetchBeneficiariesState{ - LastCreatedAt: state.LastCreatedAt, - } - - for page := 1; ; page++ { - beneficiaries, err := client.ListBeneficiaries(ctx, int64(page), pageSize, state.LastCreatedAt) - if err != nil { - return fetchBeneficiariesState{}, err - } - - if len(beneficiaries) == 0 { - break - } - - beneficiaryBatch := make([]*models.Account, 0, len(beneficiaries)) - for _, beneficiary := range beneficiaries { - raw, err := json.Marshal(beneficiary) - if err != nil { - return fetchBeneficiariesState{}, err - } - - beneficiaryBatch = append(beneficiaryBatch, &models.Account{ - ID: models.AccountID{ - Reference: beneficiary.Id, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: beneficiary.CreatedAt, - Reference: beneficiary.Id, - AccountName: beneficiary.OwnerName, - Type: models.AccountTypeExternal, - Metadata: beneficiary.Metadata, - RawData: raw, - }) - - newState.LastCreatedAt = beneficiary.CreatedAt - } - - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch(beneficiaryBatch)); err != nil { - return fetchBeneficiariesState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - } - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_transactions.go deleted file mode 100644 index 2da4446633..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_fetch_transactions.go +++ /dev/null @@ -1,205 +0,0 @@ -package generic - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/genericclient" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchTransactionsState struct { - LastUpdatedAt time.Time `json:"last_updated_at"` -} - -func taskFetchTransactions(client *client.Client, config *Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "generic.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchTransactionsState{}) - - newState, err := ingestTransactions(ctx, connectorID, client, ingester, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestTransactions( - ctx context.Context, - connectorID models.ConnectorID, - client *client.Client, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchTransactionsState, -) (fetchTransactionsState, error) { - newState := fetchTransactionsState{ - LastUpdatedAt: state.LastUpdatedAt, - } - - for page := 1; ; page++ { - transactions, err := client.ListTransactions(ctx, int64(page), pageSize, state.LastUpdatedAt) - if err != nil { - return fetchTransactionsState{}, err - } - - if len(transactions) == 0 { - break - } - - paymentBatch := make([]ingestion.PaymentBatchElement, 0, len(transactions)) - for _, transaction := range transactions { - elt, err := translate(ctx, connectorID, transaction) - if err != nil { - return fetchTransactionsState{}, err - } - - paymentBatch = append(paymentBatch, elt) - - newState.LastUpdatedAt = transaction.UpdatedAt - } - - if err := ingester.IngestPayments(ctx, ingestion.PaymentBatch(paymentBatch)); err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - } - - return newState, nil -} - -func translate( - ctx context.Context, - connectorID models.ConnectorID, - transaction genericclient.Transaction, -) (ingestion.PaymentBatchElement, error) { - paymentType := matchPaymentType(transaction.Type) - paymentStatus := matchPaymentStatus(transaction.Status) - - var amount big.Int - _, ok := amount.SetString(transaction.Amount, 10) - if !ok { - return ingestion.PaymentBatchElement{}, fmt.Errorf("failed to parse amount: %s", transaction.Amount) - } - - raw, err := json.Marshal(transaction) - if err != nil { - return ingestion.PaymentBatchElement{}, err - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transaction.Id, - Type: paymentType, - }, - ConnectorID: connectorID, - } - elt := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: paymentID, - ConnectorID: connectorID, - CreatedAt: transaction.CreatedAt, - Reference: transaction.Id, - Amount: &amount, - InitialAmount: &amount, - Type: paymentType, - Status: paymentStatus, - Scheme: models.PaymentSchemeOther, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transaction.Currency), - RawData: raw, - }, - } - - if transaction.SourceAccountID != nil && *transaction.SourceAccountID != "" { - elt.Payment.SourceAccountID = &models.AccountID{ - Reference: *transaction.SourceAccountID, - ConnectorID: connectorID, - } - } - - if transaction.DestinationAccountID != nil && *transaction.DestinationAccountID != "" { - elt.Payment.DestinationAccountID = &models.AccountID{ - Reference: *transaction.DestinationAccountID, - ConnectorID: connectorID, - } - } - - for k, v := range transaction.Metadata { - elt.Payment.Metadata = append(elt.Payment.Metadata, &models.PaymentMetadata{ - PaymentID: paymentID, - CreatedAt: transaction.CreatedAt, - Key: k, - Value: v, - Changelog: []models.MetadataChangelog{ - { - CreatedAt: transaction.CreatedAt, - Value: v, - }, - }, - }) - - } - - return elt, nil -} - -func matchPaymentType( - transactionType genericclient.TransactionType, -) models.PaymentType { - switch transactionType { - case genericclient.PAYIN: - return models.PaymentTypePayIn - case genericclient.PAYOUT: - return models.PaymentTypePayOut - case genericclient.TRANSFER: - return models.PaymentTypeTransfer - default: - return models.PaymentTypeOther - } -} - -func matchPaymentStatus( - status genericclient.TransactionStatus, -) models.PaymentStatus { - switch status { - case genericclient.PENDING: - return models.PaymentStatusPending - case genericclient.FAILED: - return models.PaymentStatusFailed - case genericclient.SUCCEEDED: - return models.PaymentStatusSucceeded - default: - return models.PaymentStatusOther - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_main.go b/components/payments/cmd/connectors/internal/connectors/generic/task_main.go deleted file mode 100644 index 3b721a464d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_main.go +++ /dev/null @@ -1,68 +0,0 @@ -package generic - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "generoc.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return err - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - taskBeneficiaries, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch beneficiaries from client", - Key: taskNameFetchBeneficiaries, - }) - if err != nil { - otel.RecordError(span, err) - return err - } - - err = scheduler.Schedule(ctx, taskBeneficiaries, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/generic/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/generic/task_resolve.go deleted file mode 100644 index 1616816757..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/generic/task_resolve.go +++ /dev/null @@ -1,52 +0,0 @@ -package generic - -import ( - "fmt" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskNameMain = "main" - taskNameFetchAccounts = "fetch-accounts" - taskNameFetchBalances = "fetch-balances" - taskNameFetchBeneficiaries = "fetch-beneficiaries" - taskNameFetchTransactions = "fetch-transactions" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - AccountID string `json:"account_id" yaml:"account_id" bson:"account_id"` -} - -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - genericClient := client.NewClient( - config.APIKey, - config.Endpoint, - logger, - ) - - return func(taskDescriptor TaskDescriptor) task.Task { - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchAccounts: - return taskFetchAccounts(genericClient, &config) - case taskNameFetchBeneficiaries: - return taskFetchBeneficiaries(genericClient, &config) - case taskNameFetchBalances: - return taskFetchBalances(genericClient, &config, taskDescriptor.AccountID) - case taskNameFetchTransactions: - return taskFetchTransactions(genericClient, &config) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/bank_accounts.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/bank_accounts.go deleted file mode 100644 index 78e5eb262e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/bank_accounts.go +++ /dev/null @@ -1,198 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type OwnerAddress struct { - AddressLine1 string `json:"AddressLine1,omitempty"` - AddressLine2 string `json:"AddressLine2,omitempty"` - City string `json:"City,omitempty"` - // Region is needed if country is either US, CA or MX - Region string `json:"Region,omitempty"` - PostalCode string `json:"PostalCode,omitempty"` - // ISO 3166-1 alpha-2 format. - Country string `json:"Country,omitempty"` -} - -type CreateIBANBankAccountRequest struct { - OwnerName string `json:"OwnerName"` - OwnerAddress *OwnerAddress `json:"OwnerAddress,omitempty"` - IBAN string `json:"IBAN,omitempty"` - BIC string `json:"BIC,omitempty"` - // Metadata - Tag string `json:"Tag,omitempty"` -} - -func (c *Client) CreateIBANBankAccount(ctx context.Context, userID string, req *CreateIBANBankAccountRequest) (*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "create_iban_bank_account") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts/iban", c.endpoint, c.clientID, userID) - return c.createBankAccount(ctx, endpoint, req) -} - -type CreateUSBankAccountRequest struct { - OwnerName string `json:"OwnerName"` - OwnerAddress *OwnerAddress `json:"OwnerAddress,omitempty"` - AccountNumber string `json:"AccountNumber"` - ABA string `json:"ABA"` - DepositAccountType string `json:"DepositAccountType,omitempty"` - Tag string `json:"Tag,omitempty"` -} - -func (c *Client) CreateUSBankAccount(ctx context.Context, userID string, req *CreateUSBankAccountRequest) (*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "create_us_bank_account") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts/us", c.endpoint, c.clientID, userID) - return c.createBankAccount(ctx, endpoint, req) -} - -type CreateCABankAccountRequest struct { - OwnerName string `json:"OwnerName"` - OwnerAddress *OwnerAddress `json:"OwnerAddress,omitempty"` - AccountNumber string `json:"AccountNumber"` - InstitutionNumber string `json:"InstitutionNumber"` - BranchCode string `json:"BranchCode"` - BankName string `json:"BankName"` - Tag string `json:"Tag,omitempty"` -} - -func (c *Client) CreateCABankAccount(ctx context.Context, userID string, req *CreateCABankAccountRequest) (*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "create_ca_bank_account") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts/ca", c.endpoint, c.clientID, userID) - return c.createBankAccount(ctx, endpoint, req) -} - -type CreateGBBankAccountRequest struct { - OwnerName string `json:"OwnerName"` - OwnerAddress *OwnerAddress `json:"OwnerAddress,omitempty"` - AccountNumber string `json:"AccountNumber"` - SortCode string `json:"SortCode"` - Tag string `json:"Tag,omitempty"` -} - -func (c *Client) CreateGBBankAccount(ctx context.Context, userID string, req *CreateGBBankAccountRequest) (*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "create_gb_bank_account") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts/gb", c.endpoint, c.clientID, userID) - return c.createBankAccount(ctx, endpoint, req) -} - -type CreateOtherBankAccountRequest struct { - OwnerName string `json:"OwnerName"` - OwnerAddress *OwnerAddress `json:"OwnerAddress,omitempty"` - AccountNumber string `json:"AccountNumber"` - BIC string `json:"BIC,omitempty"` - Country string `json:"Country,omitempty"` - Tag string `json:"Tag,omitempty"` -} - -func (c *Client) CreateOtherBankAccount(ctx context.Context, userID string, req *CreateOtherBankAccountRequest) (*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "create_other_bank_account") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts/other", c.endpoint, c.clientID, userID) - return c.createBankAccount(ctx, endpoint, req) -} - -func (c *Client) createBankAccount(ctx context.Context, endpoint string, req any) (*BankAccount, error) { - body, err := json.Marshal(req) - if err != nil { - return nil, fmt.Errorf("failed to marshal bank account request: %w", err) - } - - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create bank account request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(httpReq) - if err != nil { - return nil, fmt.Errorf("failed to create bank account: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - // Never retry bank account creation - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var bankAccount BankAccount - if err := json.NewDecoder(resp.Body).Decode(&bankAccount); err != nil { - return nil, fmt.Errorf("failed to unmarshal bank account response body: %w", err) - } - - return &bankAccount, nil -} - -type BankAccount struct { - ID string `json:"Id"` - OwnerName string `json:"OwnerName"` - CreationDate int64 `json:"CreationDate"` -} - -func (c *Client) GetBankAccounts(ctx context.Context, userID string, page, pageSize int) ([]*BankAccount, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "list_bank_accounts") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/bankaccounts", c.endpoint, c.clientID, userID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", strconv.Itoa(pageSize)) - q.Add("page", fmt.Sprint(page)) - q.Add("Sort", "CreationDate:ASC") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallets: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var bankAccounts []*BankAccount - if err := json.NewDecoder(resp.Body).Decode(&bankAccounts); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return bankAccounts, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/client.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/client.go deleted file mode 100644 index 8beec103c1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/client.go +++ /dev/null @@ -1,52 +0,0 @@ -package client - -import ( - "context" - "net/http" - "strings" - "time" - - "github.com/formancehq/go-libs/logging" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "golang.org/x/oauth2/clientcredentials" -) - -// TODO(polo): Fetch Client wallets (FEES, ...) in the future -type Client struct { - httpClient *http.Client - - clientID string - endpoint string - - logger logging.Logger -} - -func newHTTPClient(clientID, apiKey, endpoint string) *http.Client { - config := clientcredentials.Config{ - ClientID: clientID, - ClientSecret: apiKey, - TokenURL: endpoint + "/v2.01/oauth/token", - } - - httpClient := config.Client(context.Background()) - - return &http.Client{ - Timeout: 10 * time.Second, - Transport: otelhttp.NewTransport(httpClient.Transport), - } -} - -func NewClient(clientID, apiKey, endpoint string, logger logging.Logger) (*Client, error) { - endpoint = strings.TrimSuffix(endpoint, "/") - - c := &Client{ - httpClient: newHTTPClient(clientID, apiKey, endpoint), - - clientID: clientID, - endpoint: endpoint, - - logger: logger, - } - - return c, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/dispute.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/dispute.go deleted file mode 100644 index 92a17b3ef8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/dispute.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Dispute struct { - Id string `json:"Id"` - Tag string `json:"Tag"` - InitialTransactionId string `json:"InitialTransactionId"` - InitialTransactionType string `json:"InitialTransactionType"` - InitialTransactionNature string `json:"InitialTransactionNature"` - DisputeType string `json:"DisputeType"` - ContestDeadlineDate int64 `json:"ContestDeadlineDate"` - DisputedFunds Funds `json:"DisputedFunds"` - ContestedFunds Funds `json:"ContestedFunds"` - Status string `json:"Status"` - StatusMessage string `json:"StatusMessage"` - DisputeReason string `json:"DisputeReason"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - CreationDate int64 `json:"CreationDate"` - ClosedDate int64 `json:"ClosedDate"` -} - -func (c *Client) GetDispute(ctx context.Context, disputeID string) (*Dispute, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_dispute") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/disputes/%s", c.endpoint, c.clientID, disputeID) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("failed to create get dispute request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get dispute: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var dispute Dispute - if err := json.NewDecoder(resp.Body).Decode(&dispute); err != nil { - return nil, fmt.Errorf("failed to unmarshal dispute response body: %w", err) - } - - return &dispute, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/error.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/error.go deleted file mode 100644 index b57fa36fa4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/error.go +++ /dev/null @@ -1,79 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/pkg/errors" -) - -type mangopayError struct { - StatusCode int `json:"-"` - Message string `json:"Message"` - Type string `json:"Type"` - Errors map[string]string `json:"Errors"` - WithRetry bool `json:"-"` -} - -func (me *mangopayError) Error() error { - var errorMessage string - if len(me.Errors) > 0 { - for _, message := range me.Errors { - errorMessage = message - break - } - } - - var err error - if errorMessage == "" { - err = fmt.Errorf("unexpected status code: %d", me.StatusCode) - } else { - err = fmt.Errorf("%d: %s", me.StatusCode, errorMessage) - } - - if me.WithRetry { - return checkStatusCodeError(me.StatusCode, err) - } else { - return errors.Wrap(task.ErrNonRetryable, err.Error()) - } -} - -func unmarshalErrorWithRetry(statusCode int, body io.ReadCloser) *mangopayError { - var ce mangopayError - _ = json.NewDecoder(body).Decode(&ce) - - ce.StatusCode = statusCode - ce.WithRetry = true - - return &ce -} - -func unmarshalErrorWithoutRetry(statusCode int, body io.ReadCloser) *mangopayError { - var ce mangopayError - _ = json.NewDecoder(body).Decode(&ce) - - ce.StatusCode = statusCode - ce.WithRetry = false - - return &ce -} - -func checkStatusCodeError(statusCode int, err error) error { - switch statusCode { - case http.StatusTooEarly, http.StatusRequestTimeout: - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusTooManyRequests: - // Retry rate limit errors - // TODO(polo): add rate limit handling - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusInternalServerError, http.StatusBadGateway, - http.StatusServiceUnavailable, http.StatusGatewayTimeout: - // Retry internal errors - return errors.Wrap(task.ErrRetryable, err.Error()) - default: - return errors.Wrap(task.ErrNonRetryable, err.Error()) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/metadata.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/metadata.go deleted file mode 100644 index a6f4074691..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/metadata.go +++ /dev/null @@ -1,14 +0,0 @@ -package client - -const ( - mangopayMetadataSpecNamespace = "com.mangopay.spec/" - - MangopayUserIDMetadataKey = mangopayMetadataSpecNamespace + "userID" - MangopayTagMetadataKey = mangopayMetadataSpecNamespace + "tag" - MangopayABAMetadataKey = mangopayMetadataSpecNamespace + "aba" - MangopayDepositAccountTypeMetadataKey = mangopayMetadataSpecNamespace + "depositAccountType" - MangopayInstitutionNumberMetadataKey = mangopayMetadataSpecNamespace + "institutionNumber" - MangopayBranchCodeMetadataKey = mangopayMetadataSpecNamespace + "branchCode" - MangopayBankNameMetadataKey = mangopayMetadataSpecNamespace + "bankName" - MangopaySortCodeMetadataKey = mangopayMetadataSpecNamespace + "sortCode" -) diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/payin.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/payin.go deleted file mode 100644 index 4f2209457b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/payin.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type PayinResponse struct { - ID string `json:"Id"` - Tag string `json:"Tag"` - CreationDate int64 `json:"CreationDate"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - AuthorId string `json:"AuthorId"` - CreditedUserId string `json:"CreditedUserId"` - DebitedFunds Funds `json:"DebitedFunds"` - CreditedFunds Funds `json:"CreditedFunds"` - Fees Funds `json:"Fees"` - Status string `json:"Status"` - ExecutionDate int64 `json:"ExecutionDate"` - Type string `json:"Type"` - CreditedWalletID string `json:"CreditedWalletId"` - PaymentType string `json:"PaymentType"` - ExecutionType string `json:"ExecutionType"` -} - -func (c *Client) GetPayin(ctx context.Context, payinID string) (*PayinResponse, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_payin") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/payins/%s", c.endpoint, c.clientID, payinID) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("failed to create get payin request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payin: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var payinResponse PayinResponse - if err := json.NewDecoder(resp.Body).Decode(&payinResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal payin response body: %w", err) - } - - return &payinResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/payout.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/payout.go deleted file mode 100644 index c83a1a4675..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/payout.go +++ /dev/null @@ -1,123 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type PayoutRequest struct { - AuthorID string `json:"AuthorId"` - DebitedFunds Funds `json:"DebitedFunds"` - Fees Funds `json:"Fees"` - DebitedWalletID string `json:"DebitedWalletId"` - BankAccountID string `json:"BankAccountId"` - BankWireRef string `json:"BankWireRef,omitempty"` - PayoutModeRequested string `json:"PayoutModeRequested,omitempty"` -} - -type PayoutResponse struct { - ID string `json:"Id"` - ModeRequest string `json:"ModeRequested"` - ModeApplied string `json:"ModeApplied"` - FallbackReason string `json:"FallbackReason"` - CreationDate int64 `json:"CreationDate"` - AuthorID string `json:"AuthorId"` - DebitedFunds Funds `json:"DebitedFunds"` - Fees Funds `json:"Fees"` - CreditedFunds Funds `json:"CreditedFunds"` - Status string `json:"Status"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - Type string `json:"Type"` - Nature string `json:"Nature"` - ExecutionDate int64 `json:"ExecutionDate"` - BankAccountID string `json:"BankAccountId"` - DebitedWalletID string `json:"DebitedWalletId"` - PaymentType string `json:"PaymentType"` - BankWireRef string `json:"BankWireRef"` -} - -func (c *Client) InitiatePayout(ctx context.Context, payoutRequest *PayoutRequest) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/payouts/bankwire", c.endpoint, c.clientID) - - body, err := json.Marshal(payoutRequest) - if err != nil { - return nil, fmt.Errorf("failed to marshal transfer request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallets: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - // Never retry payout initiation - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var payoutResponse PayoutResponse - if err := json.NewDecoder(resp.Body).Decode(&payoutResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return &payoutResponse, nil -} - -func (c *Client) GetPayout(ctx context.Context, payoutID string) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_payout") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/payouts/%s", c.endpoint, c.clientID, payoutID) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("failed to create get payout request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payout: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var payoutResponse PayoutResponse - if err := json.NewDecoder(resp.Body).Decode(&payoutResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal payout response body: %w", err) - } - - return &payoutResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/refund.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/refund.go deleted file mode 100644 index 293f3e3cec..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/refund.go +++ /dev/null @@ -1,67 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Refund struct { - ID string `json:"Id"` - Tag string `json:"Tag"` - CreationDate int64 `json:"CreationDate"` - AuthorId string `json:"AuthorId"` - CreditedUserId string `json:"CreditedUserId"` - DebitedFunds Funds `json:"DebitedFunds"` - CreditedFunds Funds `json:"CreditedFunds"` - Fees Funds `json:"Fees"` - Status string `json:"Status"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - ExecutionDate int64 `json:"ExecutionDate"` - Type string `json:"Type"` - DebitedWalletId string `json:"DebitedWalletId"` - CreditedWalletId string `json:"CreditedWalletId"` - InitialTransactionID string `json:"InitialTransactionId"` - InitialTransactionType string `json:"InitialTransactionType"` -} - -func (c *Client) GetRefund(ctx context.Context, refundID string) (*Refund, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_refund") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/refunds/%s", c.endpoint, c.clientID, refundID) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("failed to create get refund request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get refund: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var refund Refund - if err := json.NewDecoder(resp.Body).Decode(&refund); err != nil { - return nil, fmt.Errorf("failed to unmarshal refund response body: %w", err) - } - - return &refund, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/transactions.go deleted file mode 100644 index 82fe31ec70..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/transactions.go +++ /dev/null @@ -1,84 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Payment struct { - Id string `json:"Id"` - Tag string `json:"Tag"` - CreationDate int64 `json:"CreationDate"` - AuthorId string `json:"AuthorId"` - CreditedUserId string `json:"CreditedUserId"` - DebitedFunds struct { - Currency string `json:"Currency"` - Amount json.Number `json:"Amount"` - } `json:"DebitedFunds"` - CreditedFunds struct { - Currency string `json:"Currency"` - Amount json.Number `json:"Amount"` - } `json:"CreditedFunds"` - Fees struct { - Currency string `json:"Currency"` - Amount json.Number `json:"Amount"` - } `json:"Fees"` - Status string `json:"Status"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - ExecutionDate int64 `json:"ExecutionDate"` - Type string `json:"Type"` - Nature string `json:"Nature"` - CreditedWalletID string `json:"CreditedWalletId"` - DebitedWalletID string `json:"DebitedWalletId"` -} - -func (c *Client) GetTransactions(ctx context.Context, walletsID string, page, pageSize int, afterCreatedAt time.Time) ([]*Payment, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "list_transactions") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/wallets/%s/transactions", c.endpoint, c.clientID, walletsID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", strconv.Itoa(pageSize)) - q.Add("page", fmt.Sprint(page)) - q.Add("Sort", "CreationDate:ASC") - if !afterCreatedAt.IsZero() { - q.Add("AfterDate", strconv.FormatInt(afterCreatedAt.UTC().Unix(), 10)) - } - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get transactions: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var payments []*Payment - if err := json.NewDecoder(resp.Body).Decode(&payments); err != nil { - return nil, fmt.Errorf("failed to unmarshal transactions response body: %w", err) - } - - return payments, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/transfer.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/transfer.go deleted file mode 100644 index 31172b186e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/transfer.go +++ /dev/null @@ -1,122 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Funds struct { - Currency string `json:"Currency"` - Amount json.Number `json:"Amount"` -} - -type TransferRequest struct { - AuthorID string `json:"AuthorId"` - CreditedUserID string `json:"CreditedUserId,omitempty"` - DebitedFunds Funds `json:"DebitedFunds"` - Fees Funds `json:"Fees"` - DebitedWalletID string `json:"DebitedWalletId"` - CreditedWalletID string `json:"CreditedWalletId"` -} - -type TransferResponse struct { - ID string `json:"Id"` - CreationDate int64 `json:"CreationDate"` - AuthorID string `json:"AuthorId"` - CreditedUserID string `json:"CreditedUserId"` - DebitedFunds Funds `json:"DebitedFunds"` - Fees Funds `json:"Fees"` - CreditedFunds Funds `json:"CreditedFunds"` - Status string `json:"Status"` - ResultCode string `json:"ResultCode"` - ResultMessage string `json:"ResultMessage"` - Type string `json:"Type"` - ExecutionDate int64 `json:"ExecutionDate"` - Nature string `json:"Nature"` - DebitedWalletID string `json:"DebitedWalletId"` - CreditedWalletID string `json:"CreditedWalletId"` -} - -func (c *Client) InitiateWalletTransfer(ctx context.Context, transferRequest *TransferRequest) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "initiate_transfer") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/transfers", c.endpoint, c.clientID) - - body, err := json.Marshal(transferRequest) - if err != nil { - return nil, fmt.Errorf("failed to marshal transfer request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create transfer request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallets: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - // Never retry transfer initiation - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var transferResponse TransferResponse - if err := json.NewDecoder(resp.Body).Decode(&transferResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return &transferResponse, nil -} - -func (c *Client) GetWalletTransfer(ctx context.Context, transferID string) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_transfer") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/transfers/%s", c.endpoint, c.clientID, transferID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallets: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var transfer TransferResponse - if err := json.NewDecoder(resp.Body).Decode(&transfer); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return &transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/users.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/users.go deleted file mode 100644 index 7e782e83ed..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/users.go +++ /dev/null @@ -1,82 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type user struct { - ID string `json:"Id"` - CreationDate int64 `json:"CreationDate"` -} - -func (c *Client) GetAllUsers(ctx context.Context, lastPage int, pageSize int) ([]*user, int, error) { - var users []*user - var currentPage int - - for currentPage = lastPage; ; currentPage++ { - pagedUsers, err := c.getUsers(ctx, currentPage, pageSize) - if err != nil { - return nil, lastPage, err - } - - if len(pagedUsers) == 0 { - break - } - - users = append(users, pagedUsers...) - - if len(pagedUsers) < pageSize { - break - } - } - - return users, currentPage, nil -} - -func (c *Client) getUsers(ctx context.Context, page int, pageSize int) ([]*user, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "list_users") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users", c.endpoint, c.clientID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", strconv.Itoa(pageSize)) - q.Add("page", fmt.Sprint(page)) - q.Add("Sort", "CreationDate:ASC") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get users: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var users []*user - if err := json.NewDecoder(resp.Body).Decode(&users); err != nil { - return nil, fmt.Errorf("failed to unmarshal users response body: %w", err) - } - - return users, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/wallets.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/wallets.go deleted file mode 100644 index cbc0bb82d7..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/wallets.go +++ /dev/null @@ -1,100 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Wallet struct { - ID string `json:"Id"` - Owners []string `json:"Owners"` - Description string `json:"Description"` - CreationDate int64 `json:"CreationDate"` - Currency string `json:"Currency"` - Balance struct { - Currency string `json:"Currency"` - Amount json.Number `json:"Amount"` - } `json:"Balance"` -} - -func (c *Client) GetWallets(ctx context.Context, userID string, page, pageSize int) ([]*Wallet, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "list_wallets") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/users/%s/wallets", c.endpoint, c.clientID, userID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", strconv.Itoa(pageSize)) - q.Add("page", fmt.Sprint(page)) - q.Add("Sort", "CreationDate:ASC") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallets: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var wallets []*Wallet - if err := json.NewDecoder(resp.Body).Decode(&wallets); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return wallets, nil -} - -func (c *Client) GetWallet(ctx context.Context, walletID string) (*Wallet, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "get_wallets") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/wallets/%s", c.endpoint, c.clientID, walletID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create wallet request: %w", err) - } - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallet: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var wallet Wallet - if err := json.NewDecoder(resp.Body).Decode(&wallet); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallet response body: %w", err) - } - - return &wallet, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/client/webhooks.go b/components/payments/cmd/connectors/internal/connectors/mangopay/client/webhooks.go deleted file mode 100644 index dc89a279f4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/client/webhooks.go +++ /dev/null @@ -1,219 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type EventType string - -const ( - // Transfer - EventTypeTransferNormalCreated EventType = "TRANSFER_NORMAL_CREATED" - EventTypeTransferNormalFailed EventType = "TRANSFER_NORMAL_FAILED" - EventTypeTransferNormalSucceeded EventType = "TRANSFER_NORMAL_SUCCEEDED" - - // PayOut - EventTypePayoutNormalCreated EventType = "PAYOUT_NORMAL_CREATED" - EventTypePayoutNormalFailed EventType = "PAYOUT_NORMAL_FAILED" - EventTypePayoutNormalSucceeded EventType = "PAYOUT_NORMAL_SUCCEEDED" - EventTypePayoutInstantFailed EventType = "INSTANT_PAYOUT_FAILED" - EventTypePayoutInstantSucceeded EventType = "INSTANT_PAYOUT_SUCCEEDED" - - // PayIn - EventTypePayinNormalCreated EventType = "PAYIN_NORMAL_CREATED" - EventTypePayinNormalFailed EventType = "PAYIN_NORMAL_FAILED" - EventTypePayinNormalSucceeded EventType = "PAYIN_NORMAL_SUCCEEDED" - - // Refund - EventTypeTransferRefundCreated EventType = "TRANSFER_REFUND_CREATED" - EventTypeTransferRefundFailed EventType = "TRANSFER_REFUND_FAILED" - EventTypeTransferRefundSucceeded EventType = "TRANSFER_REFUND_SUCCEEDED" - EventTypePayinRefundCreated EventType = "PAYIN_REFUND_CREATED" - EventTypePayinRefundFailed EventType = "PAYIN_REFUND_FAILED" - EventTypePayinRefundSucceeded EventType = "PAYIN_REFUND_SUCCEEDED" - EventTypePayOutRefundCreated EventType = "PAYOUT_REFUND_CREATED" - EventTypePayOutRefundFailed EventType = "PAYOUT_REFUND_FAILED" - EventTypePayOutRefundSucceeded EventType = "PAYOUT_REFUND_SUCCEEDED" -) - -var ( - AllEventTypes = []EventType{ - EventTypeTransferNormalCreated, - EventTypeTransferNormalFailed, - EventTypeTransferNormalSucceeded, - EventTypePayoutNormalCreated, - EventTypePayoutNormalFailed, - EventTypePayoutNormalSucceeded, - EventTypePayoutInstantFailed, - EventTypePayoutInstantSucceeded, - EventTypePayinNormalCreated, - EventTypePayinNormalFailed, - EventTypePayinNormalSucceeded, - EventTypeTransferRefundCreated, - EventTypeTransferRefundFailed, - EventTypeTransferRefundSucceeded, - EventTypePayinRefundCreated, - EventTypePayinRefundFailed, - EventTypePayinRefundSucceeded, - EventTypePayOutRefundCreated, - EventTypePayOutRefundFailed, - EventTypePayOutRefundSucceeded, - } -) - -type Webhook struct { - ResourceID string `json:"ResourceId"` - EventType EventType `json:"EventType"` -} - -func (c *Client) UnmarshalWebhooks(req string) (*Webhook, error) { - res := Webhook{} - err := json.Unmarshal([]byte(req), &res) - if err != nil { - return nil, err - } - return &res, nil -} - -type Hook struct { - ID string `json:"Id"` - URL string `json:"Url"` - Status string `json:"Status"` - Validity string `json:"Validity"` - EventType EventType `json:"EventType"` -} - -func (c *Client) ListAllHooks(ctx context.Context) ([]*Hook, error) { - f := connectors.ClientMetrics(ctx, "mangopay", "list_hooks") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/v2.01/%s/hooks", c.endpoint, c.clientID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create hooks request: %w", err) - } - - q := req.URL.Query() - q.Add("per_page", "100") // Should be enough, since we're creating only a few - q.Add("Sort", "CreationDate:ASC") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get wallet: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var hooks []*Hook - if err := json.NewDecoder(resp.Body).Decode(&hooks); err != nil { - return nil, fmt.Errorf("failed to unmarshal hooks response body: %w", err) - } - - return hooks, nil -} - -type CreateHookRequest struct { - EventType EventType `json:"EventType"` - URL string `json:"Url"` -} - -func (c *Client) CreateHook(ctx context.Context, eventType EventType, URL string) error { - f := connectors.ClientMetrics(ctx, "mangopay", "create_hook") - now := time.Now() - defer f(ctx, now) - - body, err := json.Marshal(&CreateHookRequest{ - EventType: eventType, - URL: URL, - }) - if err != nil { - return fmt.Errorf("failed to marshal create hook request: %w", err) - } - - endpoint := fmt.Sprintf("%s/v2.01/%s/hooks", c.endpoint, c.clientID) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return fmt.Errorf("failed to create hooks request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("failed to create hook: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - return nil -} - -type UpdateHookRequest struct { - URL string `json:"Url"` - Status string `json:"Status"` -} - -func (c *Client) UpdateHook(ctx context.Context, hookID string, URL string) error { - f := connectors.ClientMetrics(ctx, "mangopay", "udpate_hook") - now := time.Now() - defer f(ctx, now) - - body, err := json.Marshal(&UpdateHookRequest{ - URL: URL, - Status: "ENABLED", - }) - if err != nil { - return fmt.Errorf("failed to marshal udpate hook request: %w", err) - } - - endpoint := fmt.Sprintf("%s/v2.01/%s/hooks/%s", c.endpoint, c.clientID, hookID) - req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint, bytes.NewBuffer(body)) - if err != nil { - return fmt.Errorf("failed to create update hooks request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("failed to update hook: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/config.go b/components/payments/cmd/connectors/internal/connectors/mangopay/config.go deleted file mode 100644 index b010120644..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/config.go +++ /dev/null @@ -1,67 +0,0 @@ -package mangopay - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - pageSize = 100 - defaultPollingPeriod = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - ClientID string `json:"clientID" yaml:"clientID" bson:"clientID"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - Endpoint string `json:"endpoint" yaml:"endpoint" bson:"endpoint"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -func (c Config) String() string { - return fmt.Sprintf("clientID=%s, apiKey=****", c.ClientID) -} - -func (c Config) Validate() error { - if c.ClientID == "" { - return ErrMissingClientID - } - - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.Endpoint == "" { - return ErrMissingEndpoint - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("clientID", configtemplate.TypeString, "", true) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("endpoint", configtemplate.TypeString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/connector.go b/components/payments/cmd/connectors/internal/connectors/mangopay/connector.go deleted file mode 100644 index b509c43620..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/connector.go +++ /dev/null @@ -1,166 +0,0 @@ -package mangopay - -import ( - "context" - "errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderMangopay - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch users and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config - - taskMemoryState taskMemoryState -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - // Create hooks on mangopay sync - createWebhookDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "First task to create webhooks", - Key: taskNameCreateWebhook, - }) - if err != nil { - return err - } - - if err := ctx.Scheduler().Schedule(ctx.Context(), createWebhookDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil { - return err - } - - mainDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), mainDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg, &c.taskMemoryState)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - descriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Create external bank account", - Key: taskNameCreateExternalBankAccount, - BankAccountID: bankAccount.ID, - }) - if err != nil { - return err - } - if err := ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - }); err != nil { - return err - } - - return nil -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/currencies.go b/components/payments/cmd/connectors/internal/connectors/mangopay/currencies.go deleted file mode 100644 index 261e1b780c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/currencies.go +++ /dev/null @@ -1,24 +0,0 @@ -package mangopay - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // c.f. https://mangopay.com/docs/api-basics/data-formats - supportedCurrenciesWithDecimal = map[string]int{ - "AED": currency.ISO4217Currencies["AED"], // UAE Dirham - "AUD": currency.ISO4217Currencies["AUD"], // Australian Dollar - "CAD": currency.ISO4217Currencies["CAD"], // Canadian Dollar - "CHF": currency.ISO4217Currencies["CHF"], // Swiss Franc - "CZK": currency.ISO4217Currencies["CZK"], // Czech Koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish Krone - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "GBP": currency.ISO4217Currencies["GBP"], // Pound Sterling - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong Dollar - "JPY": currency.ISO4217Currencies["JPY"], // Japan, Yen - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian Krone - "PLN": currency.ISO4217Currencies["PLN"], // Poland, Zloty - "SEK": currency.ISO4217Currencies["SEK"], // Swedish Krona - "USD": currency.ISO4217Currencies["USD"], // US Dollar - "ZAR": currency.ISO4217Currencies["ZAR"], // South Africa, Rand - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/errors.go b/components/payments/cmd/connectors/internal/connectors/mangopay/errors.go deleted file mode 100644 index 9328b7c6d3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/errors.go +++ /dev/null @@ -1,20 +0,0 @@ -package mangopay - -import "errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingClientID is returned when the clientID is missing. - ErrMissingClientID = errors.New("missing clientID from config") - - // ErrMissingAPIKey is returned when the apiKey is missing. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingEndpoint is returned when the endpoint is missing. - ErrMissingEndpoint = errors.New("missing endpoint from config") - - // ErrMissingName is returned when the name is missing. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/loader.go b/components/payments/cmd/connectors/internal/connectors/mangopay/loader.go deleted file mode 100644 index 67ee43baf0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/loader.go +++ /dev/null @@ -1,52 +0,0 @@ -package mangopay - -import ( - "net/http" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(store *storage.Storage) *mux.Router { - r := mux.NewRouter() - - r.Path("/").Methods(http.MethodPost).Handler(handleWebhooks(store)) - - return r -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_create_external_bank_account.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_create_external_bank_account.go deleted file mode 100644 index e49106f850..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_create_external_bank_account.go +++ /dev/null @@ -1,188 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "go.opentelemetry.io/otel/attribute" -) - -func taskCreateExternalBankAccount(mangopayClient *client.Client, bankAccountID uuid.UUID) task.Task { - return func( - ctx context.Context, - connectorID models.ConnectorID, - taskID models.TaskID, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskCreateExternalBankAccount", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("bankAccountID", bankAccountID.String()), - ) - defer span.End() - - bankAccount, err := storageReader.GetBankAccount(ctx, bankAccountID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := createExternalBankAccount(ctx, connectorID, mangopayClient, bankAccount, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func createExternalBankAccount( - ctx context.Context, - connectorID models.ConnectorID, - mangopayClient *client.Client, - bankAccount *models.BankAccount, - ingester ingestion.Ingester, -) error { - userID := models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayUserIDMetadataKey) - if userID == "" { - return fmt.Errorf("missing userID in bank account metadata") - } - - ownerAddress := client.OwnerAddress{ - AddressLine1: models.ExtractNamespacedMetadata(bankAccount.Metadata, models.BankAccountOwnerAddressLine1MetadataKey), - AddressLine2: models.ExtractNamespacedMetadata(bankAccount.Metadata, models.BankAccountOwnerAddressLine2MetadataKey), - City: models.ExtractNamespacedMetadata(bankAccount.Metadata, models.BankAccountOwnerCityMetadataKey), - Region: models.ExtractNamespacedMetadata(bankAccount.Metadata, models.BankAccountOwnerRegionMetadataKey), - PostalCode: models.ExtractNamespacedMetadata(bankAccount.Metadata, models.BankAccountOwnerPostalCodeMetadataKey), - Country: bankAccount.Country, - } - - var mangopayBankAccount *client.BankAccount - if bankAccount.IBAN != "" { - req := &client.CreateIBANBankAccountRequest{ - OwnerName: bankAccount.Name, - OwnerAddress: &ownerAddress, - IBAN: bankAccount.IBAN, - BIC: bankAccount.SwiftBicCode, - Tag: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayTagMetadataKey), - } - - var err error - mangopayBankAccount, err = mangopayClient.CreateIBANBankAccount(ctx, userID, req) - if err != nil { - return err - } - } else { - switch bankAccount.Country { - case "US": - req := &client.CreateUSBankAccountRequest{ - OwnerName: bankAccount.Name, - OwnerAddress: &ownerAddress, - AccountNumber: bankAccount.AccountNumber, - ABA: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayABAMetadataKey), - DepositAccountType: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayDepositAccountTypeMetadataKey), - Tag: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayTagMetadataKey), - } - - var err error - mangopayBankAccount, err = mangopayClient.CreateUSBankAccount(ctx, userID, req) - if err != nil { - return err - } - - case "CA": - req := &client.CreateCABankAccountRequest{ - OwnerName: bankAccount.Name, - OwnerAddress: &ownerAddress, - AccountNumber: bankAccount.AccountNumber, - InstitutionNumber: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayInstitutionNumberMetadataKey), - BranchCode: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayBranchCodeMetadataKey), - BankName: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayBankNameMetadataKey), - Tag: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayTagMetadataKey), - } - - var err error - mangopayBankAccount, err = mangopayClient.CreateCABankAccount(ctx, userID, req) - if err != nil { - return err - } - - case "GB": - req := &client.CreateGBBankAccountRequest{ - OwnerName: bankAccount.Name, - OwnerAddress: &ownerAddress, - AccountNumber: bankAccount.AccountNumber, - SortCode: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopaySortCodeMetadataKey), - Tag: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayTagMetadataKey), - } - - var err error - mangopayBankAccount, err = mangopayClient.CreateGBBankAccount(ctx, userID, req) - if err != nil { - return err - } - - default: - req := &client.CreateOtherBankAccountRequest{ - OwnerName: bankAccount.Name, - OwnerAddress: &ownerAddress, - AccountNumber: bankAccount.AccountNumber, - BIC: bankAccount.SwiftBicCode, - Country: bankAccount.Country, - Tag: models.ExtractNamespacedMetadata(bankAccount.Metadata, client.MangopayTagMetadataKey), - } - - var err error - mangopayBankAccount, err = mangopayClient.CreateOtherBankAccount(ctx, userID, req) - if err != nil { - return err - } - } - } - - if mangopayBankAccount != nil { - buf, err := json.Marshal(mangopayBankAccount) - if err != nil { - return err - } - - externalAccount := &models.Account{ - ID: models.AccountID{ - Reference: mangopayBankAccount.ID, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(mangopayBankAccount.CreationDate, 0), - Reference: mangopayBankAccount.ID, - ConnectorID: connectorID, - AccountName: mangopayBankAccount.OwnerName, - Type: models.AccountTypeExternal, - Metadata: map[string]string{ - "user_id": userID, - }, - RawData: buf, - } - - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch{externalAccount}); err != nil { - return err - } - - if err := ingester.LinkBankAccountWithAccount(ctx, bankAccount, &externalAccount.ID); err != nil { - return err - } - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_bank_accounts.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_bank_accounts.go deleted file mode 100644 index 1a46301b4a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_bank_accounts.go +++ /dev/null @@ -1,138 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchBankAccountsState struct { - LastPage int `json:"last_page"` - LastCreationDate time.Time `json:"last_creation_date"` -} - -func taskFetchBankAccounts(client *client.Client, config *Config, userID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskFetchBankAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("userID", userID), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchBankAccountsState{}) - if state.LastPage == 0 { - // If last page is 0, it means we haven't fetched any wallets yet. - // Mangopay pages starts at 1. - state.LastPage = 1 - } - - newState, err := ingestBankAccounts(ctx, client, userID, connectorID, scheduler, ingester, state) - if err != nil { - otel.RecordError(span, err) - // Retry is already handled by the function - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func ingestBankAccounts( - ctx context.Context, - client *client.Client, - userID string, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - state fetchBankAccountsState, -) (fetchBankAccountsState, error) { - var currentPage int - - newState := fetchBankAccountsState{ - LastCreationDate: state.LastCreationDate, - } - - for currentPage = state.LastPage; ; currentPage++ { - pagedBankAccounts, err := client.GetBankAccounts(ctx, userID, currentPage, pageSize) - if err != nil { - // The client is already deciding if the error is retryable or not. - return fetchBankAccountsState{}, err - } - - if len(pagedBankAccounts) == 0 { - break - } - - var accountBatch ingestion.AccountBatch - for _, bankAccount := range pagedBankAccounts { - creationDate := time.Unix(bankAccount.CreationDate, 0) - switch creationDate.Compare(state.LastCreationDate) { - case -1, 0: - // creationDate <= state.LastCreationDate, nothing to do, - // we already processed this bank account. - continue - default: - } - newState.LastCreationDate = creationDate - - buf, err := json.Marshal(bankAccount) - if err != nil { - return fetchBankAccountsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - accountBatch = append(accountBatch, &models.Account{ - ID: models.AccountID{ - Reference: bankAccount.ID, - ConnectorID: connectorID, - }, - CreatedAt: creationDate, - Reference: bankAccount.ID, - ConnectorID: connectorID, - AccountName: bankAccount.OwnerName, - Type: models.AccountTypeExternal, - Metadata: map[string]string{ - "user_id": userID, - }, - RawData: buf, - }) - - newState.LastCreationDate = creationDate - } - - if err := ingester.IngestAccounts(ctx, accountBatch); err != nil { - return fetchBankAccountsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedBankAccounts) < pageSize { - break - } - } - - newState.LastPage = currentPage - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_transactions.go deleted file mode 100644 index 6b4b23df23..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_transactions.go +++ /dev/null @@ -1,216 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchTransactionsState struct { - // Mangopay only allows us to sort/filter by creation date. - // So in order to have every last updates of transactions, we need to - // keep track of the first transaction with created status in order to - // refetch all transactions created after this one. - // Example: - // - SUCCEEDED - // - FAILED - // - CREATED -> We want to keep track of the creation date of this transaction since we want its updates - // - SUCCEEDED - // - CREATED - // - SUCCEEDED - FirstCreatedTransactionCreationDate time.Time `json:"first_created_transaction_creation_date"` -} - -func taskFetchTransactions(client *client.Client, config *Config, walletsID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("walletsID", walletsID), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchTransactionsState{}) - - newState, err := fetchTransactions(ctx, client, walletsID, connectorID, ingester, state) - if err != nil { - otel.RecordError(span, err) - // Retry is already handled by the function - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func fetchTransactions( - ctx context.Context, - client *client.Client, - walletsID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - state fetchTransactionsState, -) (fetchTransactionsState, error) { - newState := fetchTransactionsState{} - - var firstCreatedCreationDate time.Time - var lastCreationDate time.Time - for page := 1; ; page++ { - pagedPayments, err := client.GetTransactions(ctx, walletsID, page, pageSize, state.FirstCreatedTransactionCreationDate) - if err != nil { - // Client is already deciding if the error is retryable or not. - return fetchTransactionsState{}, err - } - - if len(pagedPayments) == 0 { - break - } - - batch := ingestion.PaymentBatch{} - for _, payment := range pagedPayments { - batchElement, err := processPayment(ctx, connectorID, payment) - if err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if batchElement.Payment != nil { - // State update - if firstCreatedCreationDate.IsZero() && - batchElement.Payment.Status == models.PaymentStatusPending { - firstCreatedCreationDate = batchElement.Payment.CreatedAt - } - - lastCreationDate = batchElement.Payment.CreatedAt - } - - batch = append(batch, batchElement) - } - - err = ingester.IngestPayments(ctx, batch) - if err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedPayments) < pageSize { - break - } - } - - newState.FirstCreatedTransactionCreationDate = firstCreatedCreationDate - if newState.FirstCreatedTransactionCreationDate.IsZero() { - // No new created payments, let's set the last creation date to the last - // transaction we fetched. - newState.FirstCreatedTransactionCreationDate = lastCreationDate - } - - return newState, nil -} - -func processPayment( - ctx context.Context, - connectorID models.ConnectorID, - payment *client.Payment, -) (ingestion.PaymentBatchElement, error) { - rawData, err := json.Marshal(payment) - if err != nil { - return ingestion.PaymentBatchElement{}, fmt.Errorf("failed to marshal transaction: %w", err) - } - - paymentType := matchPaymentType(payment.Type) - paymentStatus := matchPaymentStatus(payment.Status) - - var amount big.Int - _, ok := amount.SetString(payment.DebitedFunds.Amount.String(), 10) - if !ok { - return ingestion.PaymentBatchElement{}, fmt.Errorf("failed to parse amount %s", payment.DebitedFunds.Amount.String()) - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: payment.Id, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(payment.CreationDate, 0), - Reference: payment.Id, - Amount: &amount, - InitialAmount: &amount, - ConnectorID: connectorID, - Type: paymentType, - Status: paymentStatus, - Scheme: models.PaymentSchemeOther, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, payment.DebitedFunds.Currency), - RawData: rawData, - }, - } - - if payment.DebitedWalletID != "" { - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: payment.DebitedWalletID, - ConnectorID: connectorID, - } - } - - if payment.CreditedWalletID != "" { - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: payment.CreditedWalletID, - ConnectorID: connectorID, - } - } - - return batchElement, nil -} - -func matchPaymentType(paymentType string) models.PaymentType { - switch paymentType { - case "PAYIN": - return models.PaymentTypePayIn - case "PAYOUT": - return models.PaymentTypePayOut - case "TRANSFER": - return models.PaymentTypeTransfer - } - - return models.PaymentTypeOther -} - -func matchPaymentStatus(paymentStatus string) models.PaymentStatus { - switch paymentStatus { - case "CREATED": - return models.PaymentStatusPending - case "SUCCEEDED": - return models.PaymentStatusSucceeded - case "FAILED": - return models.PaymentStatusFailed - } - - return models.PaymentStatusOther -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_users.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_users.go deleted file mode 100644 index 9eb2669144..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_users.go +++ /dev/null @@ -1,133 +0,0 @@ -package mangopay - -import ( - "context" - "errors" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type fetchUsersState struct { - LastPage int `json:"last_page"` - LastCreationDate time.Time `json:"last_creation_date"` - - FetchCount int `json:"-"` -} - -func taskFetchUsers(client *client.Client, config *Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskFetchUsers", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchUsersState{}) - if state.LastPage == 0 { - // If last page is 0, it means we haven't fetched any users yet. - // Mangopay pages starts at 1. - state.LastPage = 1 - } - - newState, err := ingestUsers(ctx, client, config, connectorID, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestUsers( - ctx context.Context, - client *client.Client, - config *Config, - connectorID models.ConnectorID, - scheduler task.Scheduler, - state fetchUsersState, -) (fetchUsersState, error) { - users, lastPage, err := client.GetAllUsers(ctx, state.LastPage, pageSize) - if err != nil { - return fetchUsersState{}, err - } - - newState := fetchUsersState{ - LastPage: lastPage, - LastCreationDate: state.LastCreationDate, - } - - for _, user := range users { - userCreationDate := time.Unix(user.CreationDate, 0) - switch userCreationDate.Compare(state.LastCreationDate) { - case -1, 0: - // creationDate <= state.LastCreationDate, nothing to do, - // we already processed this user. - continue - default: - } - - walletsTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch wallets from client by user", - Key: taskNameFetchWallets, - UserID: user.ID, - }) - if err != nil { - return fetchUsersState{}, err - } - - err = scheduler.Schedule(ctx, walletsTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchUsersState{}, err - } - - // Bank accounts are never fetched using webhooks, so we need to keep - // polling them. - bankAccountsTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch bank accounts from client by user", - Key: taskNameFetchBankAccounts, - UserID: user.ID, - }) - if err != nil { - return fetchUsersState{}, err - } - - err = scheduler.Schedule(ctx, bankAccountsTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchUsersState{}, err - } - - newState.LastCreationDate = userCreationDate - } - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_wallets.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_wallets.go deleted file mode 100644 index 96bd97e8ba..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_fetch_wallets.go +++ /dev/null @@ -1,226 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "sync" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskFetchWallets in run inside a periodic task to fetch wallets from the client. -func taskFetchWallets( - client *client.Client, - config *Config, - taskMemoryState *taskMemoryState, - userID string, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskFetchWallets", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("userID", userID), - ) - defer span.End() - - err := ingestWallets(ctx, client, config, taskMemoryState, userID, connectorID, scheduler, ingester) - if err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestWallets( - ctx context.Context, - client *client.Client, - config *Config, - taskMemoryState *taskMemoryState, - userID string, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, -) error { - for currentPage := 1; ; currentPage++ { - pagedWallets, err := client.GetWallets(ctx, userID, currentPage, pageSize) - if err != nil { - // The client is already deciding if the error is retryable or not. - // Just return it. - return err - } - - if len(pagedWallets) == 0 { - break - } - - if err = handleWallets( - ctx, - config, - taskMemoryState, - userID, - connectorID, - ingester, - scheduler, - pagedWallets, - ); err != nil { - // Since we're just ingesting data, we can safely retry the task in - // case of error - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedWallets) < pageSize { - break - } - } - - return nil -} - -func handleWallets( - ctx context.Context, - config *Config, - taskMemoryState *taskMemoryState, - userID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - pagedWallets []*client.Wallet, -) error { - var accountBatch ingestion.AccountBatch - var balanceBatch ingestion.BalanceBatch - var transactionTasks []models.TaskDescriptor - var err error - for _, wallet := range pagedWallets { - transactionTasks, err = appendTransactionTask( - taskMemoryState, - transactionTasks, - userID, - wallet, - ) - if err != nil { - return err - } - - buf, err := json.Marshal(wallet) - if err != nil { - return err - } - - accountBatch = append(accountBatch, &models.Account{ - ID: models.AccountID{ - Reference: wallet.ID, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(wallet.CreationDate, 0), - Reference: wallet.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, wallet.Currency), - AccountName: wallet.Description, - // Wallets are internal accounts on our side, since we - // can have their balances. - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "user_id": userID, - }, - RawData: buf, - }) - - var amount big.Int - _, ok := amount.SetString(wallet.Balance.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount: %s", wallet.Balance.Amount.String()) - } - - now := time.Now() - balanceBatch = append(balanceBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: wallet.ID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, wallet.Balance.Currency), - Balance: &amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestAccounts(ctx, accountBatch); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, balanceBatch, false); err != nil { - return err - } - - for _, transactionTask := range transactionTasks { - err := scheduler.Schedule(ctx, transactionTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - } - - return nil -} - -func appendTransactionTask( - taskMemoryState *taskMemoryState, - transactionTasks []models.TaskDescriptor, - userID string, - wallet *client.Wallet, -) ([]models.TaskDescriptor, error) { - if taskMemoryState.fetchTransactionsOnce == nil { - taskMemoryState.fetchTransactionsOnce = make(map[string]*sync.Once) - } - - key := userID + wallet.ID - _, ok := taskMemoryState.fetchTransactionsOnce[key] - if !ok { - taskMemoryState.fetchTransactionsOnce[key] = &sync.Once{} - } - - once := taskMemoryState.fetchTransactionsOnce[key] - - var err error - once.Do(func() { - var transactionTask models.TaskDescriptor - transactionTask, err = models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions from client by user and wallets", - Key: taskNameFetchTransactions, - UserID: userID, - WalletID: wallet.ID, - }) - if err != nil { - return - } - - transactionTasks = append(transactionTasks, transactionTask) - }) - - return transactionTasks, err -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_main.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_main.go deleted file mode 100644 index 47cd876529..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_main.go +++ /dev/null @@ -1,50 +0,0 @@ -package mangopay - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskUsers, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch users from client", - Key: taskNameFetchUsers, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskUsers, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_payments.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_payments.go deleted file mode 100644 index ee8ee08c5d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_payments.go +++ /dev/null @@ -1,348 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "errors" - "regexp" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -var ( - bankWireRefPatternRegexp = regexp.MustCompile("[a-zA-Z0-9 ]*") -) - -func taskInitiatePayment(mangopayClient *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, mangopayClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - mangopayClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("no source account provided") - return err - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - userID, ok := transfer.SourceAccount.Metadata["user_id"] - if !ok || userID == "" { - err = errors.New("missing user_id in source account metadata") - return err - } - - // No need to modify the amount since it's already in the correct format - // and precision (checked before during API call) - var curr string - curr, _, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - bankWireRef := "" - if len(transfer.Description) <= 12 && bankWireRefPatternRegexp.MatchString(transfer.Description) { - bankWireRef = transfer.Description - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *client.TransferResponse - resp, err = mangopayClient.InitiateWalletTransfer(ctx, &client.TransferRequest{ - AuthorID: userID, - DebitedFunds: client.Funds{ - Currency: curr, - Amount: json.Number(transfer.Amount.String()), - }, - Fees: client.Funds{ - Currency: curr, - Amount: "0", - }, - DebitedWalletID: transfer.SourceAccountID.Reference, - CreditedWalletID: transfer.DestinationAccountID.Reference, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - var resp *client.PayoutResponse - resp, err = mangopayClient.InitiatePayout(ctx, &client.PayoutRequest{ - AuthorID: userID, - DebitedFunds: client.Funds{ - Currency: curr, - Amount: json.Number(transfer.Amount.String()), - }, - Fees: client.Funds{ - Currency: curr, - Amount: json.Number("0"), - }, - DebitedWalletID: transfer.SourceAccountID.Reference, - BankAccountID: transfer.DestinationAccountID.Reference, - BankWireRef: bankWireRef, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - mangopayClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", paymentID.String()), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := udpatePaymentStatus(ctx, mangopayClient, transfer, paymentID, connectorID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func udpatePaymentStatus( - ctx context.Context, - mangopayClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - connectorID models.ConnectorID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status string - var resultMessage string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - var resp *client.TransferResponse - resp, err = mangopayClient.GetWalletTransfer(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.ResultMessage - case models.TransferInitiationTypePayout: - var resp *client.PayoutResponse - resp, err = mangopayClient.GetPayout(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.ResultMessage - } - - switch status { - case "CREATED": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "SUCCEEDED": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - case "FAILED": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, resultMessage, time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_resolve.go deleted file mode 100644 index 86ecca0f92..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_resolve.go +++ /dev/null @@ -1,94 +0,0 @@ -package mangopay - -import ( - "fmt" - "sync" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/google/uuid" -) - -const ( - taskNameMain = "main" - taskNameFetchUsers = "fetch-users" - taskNameFetchTransactions = "fetch-transactions" - taskNameFetchWallets = "fetch-wallets" - taskNameFetchBankAccounts = "fetch-bank-accounts" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" - taskNameCreateExternalBankAccount = "create-external-bank-account" - taskNameCreateWebhook = "create-webhook" - taskNameHandleWebhook = "handle-webhook" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - UserID string `json:"userID" yaml:"userID" bson:"userID"` - WalletID string `json:"walletID" yaml:"walletID" bson:"walletID"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` - BankAccountID uuid.UUID `json:"bankAccountID,omitempty" yaml:"bankAccountID" bson:"bankAccountID"` - WebhookID uuid.UUID `json:"webhookId,omitempty" yaml:"webhookId" bson:"webhookId"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -// internal state not pushed in the database -type taskMemoryState struct { - // We want to fetch the transactions once per service start. - fetchTransactionsOnce map[string]*sync.Once -} - -// clientID, apiKey, endpoint string, logger logging -func resolveTasks(logger logging.Logger, config Config, taskMemoryState *taskMemoryState) func(taskDefinition TaskDescriptor) task.Task { - mangopayClient, err := client.NewClient( - config.ClientID, - config.APIKey, - config.Endpoint, - logger, - ) - if err != nil { - logger.Error(err) - - return func(taskDescriptor TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("cannot build mangopay client: %w", err) - } - } - } - - return func(taskDescriptor TaskDescriptor) task.Task { - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchUsers: - return taskFetchUsers(mangopayClient, &config) - case taskNameFetchBankAccounts: - return taskFetchBankAccounts(mangopayClient, &config, taskDescriptor.UserID) - case taskNameFetchTransactions: - return taskFetchTransactions(mangopayClient, &config, taskDescriptor.WalletID) - case taskNameFetchWallets: - return taskFetchWallets(mangopayClient, &config, taskMemoryState, taskDescriptor.UserID) - case taskNameCreateWebhook: - return taskCreateWebhooks(mangopayClient) - case taskNameHandleWebhook: - return taskHandleWebhooks(mangopayClient, taskDescriptor.WebhookID) - case taskNameInitiatePayment: - return taskInitiatePayment(mangopayClient, taskDescriptor.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(mangopayClient, taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt) - case taskNameCreateExternalBankAccount: - return taskCreateExternalBankAccount(mangopayClient, taskDescriptor.BankAccountID) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/mangopay/task_webhooks.go b/components/payments/cmd/connectors/internal/connectors/mangopay/task_webhooks.go deleted file mode 100644 index a20d9eaa07..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/mangopay/task_webhooks.go +++ /dev/null @@ -1,607 +0,0 @@ -package mangopay - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "net/http" - "os" - "time" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/mangopay/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/google/uuid" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -func handleWebhooks(store *storage.Storage) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - connectorContext := task.ConnectorContextFromContext(r.Context()) - webhookID := connectors.WebhookIDFromContext(r.Context()) - span := trace.SpanFromContext(r.Context()) - - // Mangopay does not send us the event inside the body, but using - // URL query. - eventType := r.URL.Query().Get("EventType") - resourceID := r.URL.Query().Get("RessourceId") - - hook := client.Webhook{ - ResourceID: resourceID, - EventType: client.EventType(eventType), - } - - body, err := json.Marshal(hook) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - if err := store.UpdateWebhookRequestBody(r.Context(), webhookID, body); err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - detachedCtx, _ := contextutil.DetachedWithTimeout(r.Context(), 30*time.Second) - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "handle webhook", - Key: taskNameHandleWebhook, - WebhookID: webhookID, - }) - if err != nil { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - - err = connectorContext.Scheduler().Schedule(detachedCtx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - api.InternalServerError(w, r, err) - return - } - } -} - -func taskCreateWebhooks(c *client.Client) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskCreateWebhooks", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - stackPublicURL := os.Getenv("STACK_PUBLIC_URL") - if stackPublicURL == "" { - err := errors.New("STACK_PUBLIC_URL is not set") - otel.RecordError(span, err) - return err - } - - webhookURL := fmt.Sprintf("%s/api/payments/connectors/webhooks/mangopay/%s/", stackPublicURL, &connectorID) - logger.Infof("creating webhook for mangopay with url %s", webhookURL) - - alreadyExistingHooks, err := c.ListAllHooks(ctx) - if err != nil { - otel.RecordError(span, err) - return err - } - - activeHooks := make(map[client.EventType]*client.Hook) - for _, hook := range alreadyExistingHooks { - // Mangopay allows only one active hook per event type. - if hook.Validity == "VALID" { - activeHooks[hook.EventType] = hook - } - } - - for _, eventType := range client.AllEventTypes { - if v, ok := activeHooks[eventType]; ok { - // Already created, continue - - if v.URL != webhookURL { - // If the URL is different, update it - err := c.UpdateHook(ctx, v.ID, webhookURL) - if err != nil { - otel.RecordError(span, err) - return err - } - - logger.Infof("updated webhook for mangopay with event type %s", eventType) - } - - continue - } - - // Otherwise, create it - err := c.CreateHook(ctx, eventType, webhookURL) - if err != nil { - otel.RecordError(span, err) - return err - } - - logger.Infof("created webhook for mangopay with event type %s", eventType) - } - - return nil - } -} - -func taskHandleWebhooks(c *client.Client, webhookID uuid.UUID) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - storageReader storage.Reader, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "mangopay.taskHandleWebhooks", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("webhookID", webhookID.String()), - ) - defer span.End() - - w, err := storageReader.GetWebhook(ctx, webhookID) - if err != nil { - otel.RecordError(span, err) - return err - } - - webhook, err := c.UnmarshalWebhooks((string(w.RequestBody))) - if err != nil { - otel.RecordError(span, err) - return err - } - - switch webhook.EventType { - case client.EventTypeTransferNormalCreated, - client.EventTypeTransferNormalFailed, - client.EventTypeTransferNormalSucceeded: - logger.WithField("webhook", webhook).Info("handling transfer webhook") - return handleTransfer( - ctx, - c, - connectorID, - webhook, - ingester, - ) - - case client.EventTypePayoutNormalCreated, - client.EventTypePayoutNormalFailed, - client.EventTypePayoutNormalSucceeded, - client.EventTypePayoutInstantFailed, - client.EventTypePayoutInstantSucceeded: - logger.WithField("webhook", webhook).Info("handling payout webhook") - return handlePayout( - ctx, - c, - connectorID, - webhook, - ingester, - ) - - case client.EventTypePayinNormalCreated, - client.EventTypePayinNormalSucceeded, - client.EventTypePayinNormalFailed: - logger.WithField("webhook", webhook).Info("handling payin webhook") - return handlePayIn( - ctx, - c, - connectorID, - webhook, - ingester, - ) - - case client.EventTypeTransferRefundFailed, - client.EventTypeTransferRefundSucceeded, - client.EventTypePayOutRefundFailed, - client.EventTypePayOutRefundSucceeded, - client.EventTypePayinRefundFailed, - client.EventTypePayinRefundSucceeded: - logger.WithField("webhook", webhook).Info("handling refunds webhook") - return handleRefunds( - ctx, - c, - connectorID, - webhook, - ingester, - storageReader, - ) - - case client.EventTypeTransferRefundCreated, - client.EventTypePayOutRefundCreated, - client.EventTypePayinRefundCreated: - // NOTE: we don't handle these events, as we are only interested in - // the refund successed or failures. - - default: - // ignore unknown events - logger.Errorf("unknown event type: %s", webhook.EventType) - return nil - } - - return nil - } -} - -func handleTransfer( - ctx context.Context, - c *client.Client, - connectorID models.ConnectorID, - webhook *client.Webhook, - ingester ingestion.Ingester, -) error { - transfer, err := c.GetWalletTransfer(ctx, webhook.ResourceID) - if err != nil { - return err - } - - if err := fetchWallet(ctx, c, connectorID, transfer.CreditedWalletID, ingester); err != nil { - return err - } - - if err := fetchWallet(ctx, c, connectorID, transfer.DebitedWalletID, ingester); err != nil { - return err - } - - var amount big.Int - _, ok := amount.SetString(transfer.DebitedFunds.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount %s", transfer.DebitedFunds.Amount.String()) - } - paymentStatus := matchPaymentStatus(transfer.Status) - raw, err := json.Marshal(transfer) - if err != nil { - return fmt.Errorf("failed to marshal transfer: %w", err) - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transfer.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(transfer.CreationDate, 0), - Reference: transfer.ID, - Amount: &amount, - InitialAmount: &amount, - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: paymentStatus, - Scheme: models.PaymentSchemeOther, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transfer.DebitedFunds.Currency), - RawData: raw, - } - - if transfer.DebitedWalletID != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: transfer.DebitedWalletID, - ConnectorID: connectorID, - } - } - - if transfer.CreditedWalletID != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: transfer.CreditedWalletID, - ConnectorID: connectorID, - } - } - - err = ingester.IngestPayments(ctx, []ingestion.PaymentBatchElement{{ - Payment: payment, - }}) - if err != nil { - return err - } - - return nil -} - -func handlePayIn( - ctx context.Context, - c *client.Client, - connectorID models.ConnectorID, - webhook *client.Webhook, - ingester ingestion.Ingester, -) error { - payin, err := c.GetPayin(ctx, webhook.ResourceID) - if err != nil { - return err - } - - // In case of a payin, there is no debited wallet id, so we can only - // fetch the credited wallet. - if err := fetchWallet(ctx, c, connectorID, payin.CreditedWalletID, ingester); err != nil { - return err - } - - var amount big.Int - _, ok := amount.SetString(payin.DebitedFunds.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount %s", payin.DebitedFunds.Amount.String()) - } - paymentStatus := matchPaymentStatus(payin.Status) - raw, err := json.Marshal(payin) - if err != nil { - return fmt.Errorf("failed to marshal transfer: %w", err) - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: payin.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(payin.CreationDate, 0), - Reference: payin.ID, - Amount: &amount, - InitialAmount: &amount, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: paymentStatus, - Scheme: models.PaymentSchemeOther, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, payin.DebitedFunds.Currency), - RawData: raw, - } - - if payin.CreditedWalletID != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: payin.CreditedWalletID, - ConnectorID: connectorID, - } - } - - err = ingester.IngestPayments(ctx, []ingestion.PaymentBatchElement{{ - Payment: payment, - }}) - if err != nil { - return err - } - - return nil -} - -func handlePayout( - ctx context.Context, - c *client.Client, - connectorID models.ConnectorID, - webhook *client.Webhook, - ingester ingestion.Ingester, -) error { - payout, err := c.GetPayout(ctx, webhook.ResourceID) - if err != nil { - return err - } - - // In case of a payout, there is no credited wallet id, so we can only - // fetch the debited wallet. - if err := fetchWallet(ctx, c, connectorID, payout.DebitedWalletID, ingester); err != nil { - return err - } - - var amount big.Int - _, ok := amount.SetString(payout.DebitedFunds.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount %s", payout.DebitedFunds.Amount.String()) - } - paymentStatus := matchPaymentStatus(payout.Status) - raw, err := json.Marshal(payout) - if err != nil { - return fmt.Errorf("failed to marshal transfer: %w", err) - } - - payment := &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: payout.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(payout.CreationDate, 0), - Reference: payout.ID, - Amount: &amount, - InitialAmount: &amount, - ConnectorID: connectorID, - Type: models.PaymentTypePayOut, - Status: paymentStatus, - Scheme: models.PaymentSchemeOther, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, payout.DebitedFunds.Currency), - RawData: raw, - } - - if payout.DebitedWalletID != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: payout.DebitedWalletID, - ConnectorID: connectorID, - } - } - - err = ingester.IngestPayments(ctx, []ingestion.PaymentBatchElement{{ - Payment: payment, - }}) - if err != nil { - return err - } - - return nil -} - -func handleRefunds( - ctx context.Context, - c *client.Client, - connectorID models.ConnectorID, - webhook *client.Webhook, - ingester ingestion.Ingester, - storageReader storage.Reader, -) error { - refund, err := c.GetRefund(ctx, webhook.ResourceID) - if err != nil { - return err - } - - var amountRefunded big.Int - _, ok := amountRefunded.SetString(refund.DebitedFunds.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount %s", refund.DebitedFunds.Amount.String()) - } - paymentType := matchPaymentType(refund.InitialTransactionType) - - var payment *models.Payment - if webhook.EventType == client.EventTypePayOutRefundSucceeded { - payment, err = storageReader.GetPayment(ctx, models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: refund.InitialTransactionID, - Type: paymentType, - }, - ConnectorID: connectorID, - }.String()) - if err != nil { - return err - } - - payment.Amount = payment.Amount.Sub(payment.Amount, &amountRefunded) - } - - paymentStatus := models.PaymentStatusRefundedFailure - if webhook.EventType == client.EventTypePayOutRefundSucceeded { - paymentStatus = models.PaymentStatusRefunded - } - - raw, err := json.Marshal(refund) - if err != nil { - return fmt.Errorf("failed to marshal refund: %w", err) - } - - adjustment := &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: refund.InitialTransactionID, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(refund.CreationDate, 0), - Reference: refund.ID, - Amount: &amountRefunded, - Status: paymentStatus, - RawData: raw, - } - - if err := ingester.IngestPayments(ctx, []ingestion.PaymentBatchElement{{ - Payment: payment, - Adjustment: adjustment, - }}); err != nil { - return err - } - - return nil -} - -func fetchWallet( - ctx context.Context, - c *client.Client, - connectorID models.ConnectorID, - walletID string, - ingester ingestion.Ingester, -) error { - if walletID == "" { - return nil - } - - wallet, err := c.GetWallet(ctx, walletID) - if err != nil { - return err - } - - raw, err := json.Marshal(wallet) - if err != nil { - return err - } - - userID := "" - if len(wallet.Owners) > 0 { - userID = wallet.Owners[0] - } - - account := &models.Account{ - ID: models.AccountID{ - Reference: wallet.ID, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(wallet.CreationDate, 0), - Reference: wallet.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, wallet.Currency), - AccountName: wallet.Description, - // Wallets are internal accounts on our side, since we - // can have their balances. - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "user_id": userID, - }, - RawData: raw, - } - - var amount big.Int - _, ok := amount.SetString(wallet.Balance.Amount.String(), 10) - if !ok { - return fmt.Errorf("failed to parse amount: %s", wallet.Balance.Amount.String()) - } - - now := time.Now() - balance := &models.Balance{ - AccountID: models.AccountID{ - Reference: wallet.ID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, wallet.Balance.Currency), - Balance: &amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - } - - if err := ingester.IngestAccounts(ctx, []*models.Account{account}); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, []*models.Balance{balance}, false); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/accounts.go deleted file mode 100644 index 5e98921332..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/accounts.go +++ /dev/null @@ -1,64 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -//nolint:tagliatelle // allow for clients -type Account struct { - ID string `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - Balance string `json:"balance"` - Currency string `json:"currency"` - CustomerID string `json:"customerId"` - Identifiers []struct { - AccountNumber string `json:"accountNumber"` - SortCode string `json:"sortCode"` - Type string `json:"type"` - } `json:"identifiers"` - DirectDebit bool `json:"directDebit"` - CreatedDate string `json:"createdDate"` -} - -func (m *Client) GetAccounts(ctx context.Context, page, pageSize int) (*responseWrapper[[]*Account], error) { - f := connectors.ClientMetrics(ctx, "modulr", "list_accounts") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.buildEndpoint("accounts"), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create accounts request: %w", err) - } - - q := req.URL.Query() - q.Add("page", strconv.Itoa(page)) - q.Add("size", strconv.Itoa(pageSize)) - q.Add("sortField", "createdDate") - q.Add("sortOrder", "asc") - req.URL.RawQuery = q.Encode() - - resp, err := m.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res responseWrapper[[]*Account] - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/beneficiaries.go deleted file mode 100644 index da3d6933dc..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/beneficiaries.go +++ /dev/null @@ -1,55 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Beneficiary struct { - ID string `json:"id"` - Name string `json:"name"` - Created string `json:"created"` -} - -func (m *Client) GetBeneficiaries(ctx context.Context, page, pageSize int, modifiedSince string) (*responseWrapper[[]*Beneficiary], error) { - f := connectors.ClientMetrics(ctx, "modulr", "list_beneficiaries") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.buildEndpoint("beneficiaries"), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create accounts request: %w", err) - } - - q := req.URL.Query() - q.Add("page", strconv.Itoa(page)) - q.Add("size", strconv.Itoa(pageSize)) - if modifiedSince != "" { - q.Add("modifiedSince", modifiedSince) - } - req.URL.RawQuery = q.Encode() - - resp, err := m.httpClient.Do(req) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res responseWrapper[[]*Beneficiary] - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/client.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/client.go deleted file mode 100644 index 7fd340c4fb..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/client.go +++ /dev/null @@ -1,72 +0,0 @@ -package client - -import ( - "fmt" - "net/http" - "strings" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/hmac" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -type apiTransport struct { - apiKey string - headers map[string]string - underlying http.RoundTripper -} - -func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Add("Authorization", t.apiKey) - - return t.underlying.RoundTrip(req) -} - -type responseWrapper[t any] struct { - Content t `json:"content"` - Size int `json:"size"` - TotalSize int `json:"totalSize"` - Page int `json:"page"` - TotalPages int `json:"totalPages"` -} - -type Client struct { - httpClient *http.Client - endpoint string -} - -func (m *Client) buildEndpoint(path string, args ...interface{}) string { - endpoint := strings.TrimSuffix(m.endpoint, "/") - return fmt.Sprintf("%s/%s", endpoint, fmt.Sprintf(path, args...)) -} - -const SandboxAPIEndpoint = "https://api-sandbox.modulrfinance.com/api-sandbox-token" - -func NewClient(apiKey, apiSecret, endpoint string) (*Client, error) { - if endpoint == "" { - endpoint = SandboxAPIEndpoint - } - - headers, err := hmac.GenerateHeaders(apiKey, apiSecret, "", false) - if err != nil { - return nil, fmt.Errorf("failed to generate headers: %w", err) - } - - return &Client{ - httpClient: &http.Client{ - Transport: &apiTransport{ - headers: headers, - apiKey: apiKey, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - }, - endpoint: endpoint, - }, nil -} - -type ErrorResponse struct { - Field string `json:"field"` - Code string `json:"code"` - Message string `json:"message"` - ErrorCode string `json:"errorCode"` - SourceService string `json:"sourceService"` -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/error.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/error.go deleted file mode 100644 index 1b4e117343..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/error.go +++ /dev/null @@ -1,83 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/pkg/errors" -) - -type modulrError struct { - StatusCode int `json:"-"` - Field string `json:"field"` - Code string `json:"code"` - Message string `json:"message"` - ErrorCode string `json:"errorCode"` - SourceService string `json:"sourceService"` - WithRetry bool `json:"-"` -} - -func (me *modulrError) Error() error { - var err error - if me.Message == "" { - err = fmt.Errorf("unexpected status code: %d", me.StatusCode) - } else { - err = fmt.Errorf("%d: %s", me.StatusCode, me.Message) - } - - if me.WithRetry { - return checkStatusCodeError(me.StatusCode, err) - } - - return errors.Wrap(task.ErrNonRetryable, err.Error()) -} - -func unmarshalError(statusCode int, body io.ReadCloser, withRetry bool) *modulrError { - var ces []modulrError - _ = json.NewDecoder(body).Decode(&ces) - - if len(ces) == 0 { - return &modulrError{ - StatusCode: statusCode, - WithRetry: withRetry, - } - } - - return &modulrError{ - StatusCode: statusCode, - Field: ces[0].Field, - Code: ces[0].Code, - Message: ces[0].Message, - ErrorCode: ces[0].ErrorCode, - SourceService: ces[0].SourceService, - WithRetry: withRetry, - } -} - -func unmarshalErrorWithRetry(statusCode int, body io.ReadCloser) *modulrError { - return unmarshalError(statusCode, body, true) -} - -func unmarshalErrorWithoutRetry(statusCode int, body io.ReadCloser) *modulrError { - return unmarshalError(statusCode, body, false) -} - -func checkStatusCodeError(statusCode int, err error) error { - switch statusCode { - case http.StatusTooEarly, http.StatusRequestTimeout: - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusTooManyRequests: - // Retry rate limit errors - // TODO(polo): add rate limit handling - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusInternalServerError, http.StatusBadGateway, - http.StatusServiceUnavailable, http.StatusGatewayTimeout: - // Retry internal errors - return errors.Wrap(task.ErrRetryable, err.Error()) - default: - return errors.Wrap(task.ErrNonRetryable, err.Error()) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/payout.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/payout.go deleted file mode 100644 index 04ab644174..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/payout.go +++ /dev/null @@ -1,83 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type PayoutRequest struct { - SourceAccountID string `json:"sourceAccountId"` - Destination struct { - Type string `json:"type"` - ID string `json:"id"` - } `json:"destination"` - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - Reference string `json:"reference"` - ExternalReference string `json:"externalReference"` -} - -type PayoutResponse struct { - ID string `json:"id"` - Status string `json:"status"` - CreatedDate string `json:"createdDate"` - ExternalReference string `json:"externalReference"` - ApprovalStatus string `json:"approvalStatus"` - Message string `json:"message"` -} - -func (c *Client) InitiatePayout(ctx context.Context, payoutRequest *PayoutRequest) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "modulr", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - body, err := json.Marshal(payoutRequest) - if err != nil { - return nil, err - } - - resp, err := c.httpClient.Post(c.buildEndpoint("payments"), "application/json", bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var res PayoutResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} - -func (c *Client) GetPayout(ctx context.Context, payoutID string) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "modulr", "get_payout") - now := time.Now() - defer f(ctx, now) - - resp, err := c.httpClient.Get(c.buildEndpoint("payments?id=%s", payoutID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res PayoutResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/transactions.go deleted file mode 100644 index 8a15baa81e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/transactions.go +++ /dev/null @@ -1,63 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -//nolint:tagliatelle // allow different styled tags in client -type Transaction struct { - ID string `json:"id"` - Type string `json:"type"` - Amount json.Number `json:"amount"` - Credit bool `json:"credit"` - SourceID string `json:"sourceId"` - Description string `json:"description"` - PostedDate string `json:"postedDate"` - TransactionDate string `json:"transactionDate"` - Account Account `json:"account"` - AdditionalInfo interface{} `json:"additionalInfo"` -} - -func (m *Client) GetTransactions(ctx context.Context, accountID string, page, pageSize int, fromTransactionDate string) (*responseWrapper[[]*Transaction], error) { - f := connectors.ClientMetrics(ctx, "modulr", "list_transactions") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, m.buildEndpoint("accounts/%s/transactions", accountID), http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create accounts request: %w", err) - } - - q := req.URL.Query() - q.Add("page", strconv.Itoa(page)) - q.Add("size", strconv.Itoa(pageSize)) - if fromTransactionDate != "" { - q.Add("fromTransactionDate", fromTransactionDate) - } - req.URL.RawQuery = q.Encode() - - resp, err := m.httpClient.Do(req) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res responseWrapper[[]*Transaction] - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/client/transfer.go b/components/payments/cmd/connectors/internal/connectors/modulr/client/transfer.go deleted file mode 100644 index 435370abf3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/client/transfer.go +++ /dev/null @@ -1,108 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type DestinationType string - -const ( - DestinationTypeAccount DestinationType = "ACCOUNT" - DestinationTypeBeneficiary DestinationType = "BENEFICIARY" -) - -type Destination struct { - Type string `json:"type"` - ID string `json:"id"` -} - -type TransferRequest struct { - SourceAccountID string `json:"sourceAccountId"` - Destination Destination `json:"destination"` - Currency string `json:"currency"` - Amount json.Number `json:"amount"` - Reference string `json:"reference"` - ExternalReference string `json:"externalReference"` - PaymentDate string `json:"paymentDate"` -} - -type getTransferResponse struct { - Content []*TransferResponse `json:"content"` -} - -type TransferResponse struct { - ID string `json:"id"` - Status string `json:"status"` - CreatedDate string `json:"createdDate"` - ExternalReference string `json:"externalReference"` - ApprovalStatus string `json:"approvalStatus"` - Message string `json:"message"` -} - -func (c *Client) InitiateTransfer(ctx context.Context, transferRequest *TransferRequest) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "modulr", "initiate_transfer") - now := time.Now() - defer f(ctx, now) - - body, err := json.Marshal(transferRequest) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, c.buildEndpoint("payments"), bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create transfer request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to initiate transfer: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var res TransferResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return &res, nil -} - -func (c *Client) GetTransfer(ctx context.Context, transferID string) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "modulr", "get_transfer") - now := time.Now() - defer f(ctx, now) - - resp, err := c.httpClient.Get(c.buildEndpoint("payments?id=%s", transferID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res getTransferResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - if len(res.Content) == 0 { - return nil, fmt.Errorf("transfer not found") - } - - return res.Content[0], nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/config.go b/components/payments/cmd/connectors/internal/connectors/modulr/config.go deleted file mode 100644 index 343e8b733a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/config.go +++ /dev/null @@ -1,69 +0,0 @@ -package modulr - -import ( - "encoding/json" - "fmt" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" -) - -const ( - defaultPollingPeriod = 2 * time.Minute - defaultPageSize = 100 -) - -type Config struct { - Name string `json:"name" bson:"name"` - APIKey string `json:"apiKey" bson:"apiKey"` - APISecret string `json:"apiSecret" bson:"apiSecret"` - Endpoint string `json:"endpoint" bson:"endpoint"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` - PageSize int `json:"pageSize" yaml:"pageSize" bson:"pageSize"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return fmt.Sprintf("endpoint=%s, apiSecret=***, apiKey=****", c.Endpoint) -} - -func (c Config) Validate() error { - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.APISecret == "" { - return ErrMissingAPISecret - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("apiSecret", configtemplate.TypeString, "", true) - cfg.AddParameter("endpoint", configtemplate.TypeString, client.SandboxAPIEndpoint, false) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - cfg.AddParameter("pageSize", configtemplate.TypeDurationUnsignedInteger, strconv.Itoa(defaultPageSize), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/connector.go b/components/payments/cmd/connectors/internal/connectors/modulr/connector.go deleted file mode 100644 index 23f43232e6..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/connector.go +++ /dev/null @@ -1,136 +0,0 @@ -package modulr - -import ( - "context" - - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const name = models.ConnectorProviderModulr - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch accounts and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_NOW, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - return nil - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/currencies.go b/components/payments/cmd/connectors/internal/connectors/modulr/currencies.go deleted file mode 100644 index d54ca96535..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/currencies.go +++ /dev/null @@ -1,20 +0,0 @@ -package modulr - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // c.f. https://modulr.readme.io/docs/international-payments - supportedCurrenciesWithDecimal = map[string]int{ - "GBP": currency.ISO4217Currencies["GBP"], // Pound Sterling - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "CZK": currency.ISO4217Currencies["CZK"], // Czech Koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish Krone - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian Krone - "PLN": currency.ISO4217Currencies["PLN"], // Poland, Zloty - "SEK": currency.ISO4217Currencies["SEK"], // Swedish Krona - "CHF": currency.ISO4217Currencies["CHF"], // Swiss Franc - "USD": currency.ISO4217Currencies["USD"], // US Dollar - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong Dollar - "JPY": currency.ISO4217Currencies["JPY"], // Japan, Yen - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/errors.go b/components/payments/cmd/connectors/internal/connectors/modulr/errors.go deleted file mode 100644 index f756d3c1f5..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/errors.go +++ /dev/null @@ -1,17 +0,0 @@ -package modulr - -import "github.com/pkg/errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingAPIKey is returned when the api key is missing from config. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingAPISecret is returned when the api secret is missing from config. - ErrMissingAPISecret = errors.New("missing apiSecret from config") - - // ErrMissingName is returned when the name is missing from config. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac.go b/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac.go deleted file mode 100644 index f3bb8e3111..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac.go +++ /dev/null @@ -1,59 +0,0 @@ -package hmac - -import ( - "time" - - "github.com/google/uuid" - "github.com/pkg/errors" -) - -const ( - authorizationHeader = "Authorization" - dateHeader = "Date" - emptyString = "" - nonceHeader = "x-mod-nonce" - retry = "x-mod-retry" - retryTrue = "true" - retryFalse = "false" -) - -var ErrInvalidCredentials = errors.New("invalid api credentials") - -func GenerateHeaders(apiKey, apiSecret, nonce string, hasRetry bool) (map[string]string, error) { - if apiKey == "" || apiSecret == "" { - return nil, ErrInvalidCredentials - } - - return constructHeadersMap(apiKey, apiSecret, nonce, hasRetry, time.Now()), nil -} - -func constructHeadersMap(apiKey, apiSecret, nonce string, hasRetry bool, - timestamp time.Time, -) map[string]string { - headers := make(map[string]string) - date := timestamp.Format(time.RFC1123) - nonce = generateNonceIfEmpty(nonce) - - headers[dateHeader] = date - headers[authorizationHeader] = buildSignature(apiKey, apiSecret, nonce, date) - headers[nonceHeader] = nonce - headers[retry] = parseRetryBool(hasRetry) - - return headers -} - -func generateNonceIfEmpty(nonce string) string { - if nonce == emptyString { - nonce = uuid.New().String() - } - - return nonce -} - -func parseRetryBool(hasRetry bool) string { - if hasRetry { - return retryTrue - } - - return retryFalse -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac_test.go b/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac_test.go deleted file mode 100644 index 456ae503a4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/hmac_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package hmac - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestGenerateReturnsAnHMACString(t *testing.T) { - t.Parallel() - - headers, _ := GenerateHeaders("api_key", "api_secret", "", false) - expectedSignature := "Signature keyId=\"api_key\",algorithm=\"hmac-sha1\",headers=\"date x-mod-nonce\",signature=\"" - assert.Equal(t, expectedSignature, headers["Authorization"][0:86], "generate should return the hmac headers") -} - -func TestGenerateReturnsADateHeader(t *testing.T) { - t.Parallel() - - timestamp := time.Date(2020, 1, 2, 15, 4, 5, 0, time.UTC) - - headers := constructHeadersMap("api_key", "api_secret", "", false, timestamp) - - expectedDate := "Thu, 02 Jan 2020 15:04:05 UTC" - - assert.Equal(t, expectedDate, headers["Date"]) -} - -func TestGenerateReturnsANonceHeaderWithExpectedValue(t *testing.T) { - t.Parallel() - - nonce := "thisIsTheNonce" - headers, _ := GenerateHeaders("api_key", "api_secret", nonce, false) - assert.Equal(t, nonce, headers["x-mod-nonce"]) -} - -func TestGenerateReturnsARetryHeaderWithTrueIfRetryIsExpected(t *testing.T) { - t.Parallel() - - headers, _ := GenerateHeaders("api_key", "api_secret", "", true) - assert.Equal(t, "true", headers["x-mod-retry"]) -} - -func TestGenerateReturnsARetryHeaderWithFalseIfRetryIsNotExpected(t *testing.T) { - t.Parallel() - - headers, _ := GenerateHeaders("api_key", "api_secret", "", false) - assert.Equal(t, "false", headers["x-mod-retry"]) -} - -func TestGenerateReturnsAGeneratedNonceHeaderIfNonceIsEmpty(t *testing.T) { - t.Parallel() - - headers, _ := GenerateHeaders("api_key", "api_secret", "", false) - assert.True(t, headers["x-mod-nonce"] != "", "x-mod-nonce header should have been populated") -} - -func TestGenerateThrowsErrorIfApiKeyIsNull(t *testing.T) { - t.Parallel() - - _, err := GenerateHeaders("", "api_secret", "", false) - assert.ErrorIs(t, err, ErrInvalidCredentials) -} - -func TestGenerateThrowsErrorIfApiSecretIsNull(t *testing.T) { - t.Parallel() - - _, err := GenerateHeaders("api_key", "", "", false) - assert.ErrorIs(t, err, ErrInvalidCredentials) -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_generator.go b/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_generator.go deleted file mode 100644 index 706589509c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_generator.go +++ /dev/null @@ -1,32 +0,0 @@ -package hmac - -import ( - "crypto/hmac" - "crypto/sha1" //nolint:gosec // we need sha1 for the hmac - "encoding/base64" - "net/url" -) - -const ( - algorithm = "algorithm=\"hmac-sha1\"," - datePrefix = "date: " - headers = "headers=\"date x-mod-nonce\"," - prefix = "signature=\"" - suffix = "\"" - newline = "\n" - nonceKey = "x-mod-nonce: " - keyIDPrefix = "Signature keyId=\"" -) - -func buildSignature(apiKey, apiSecret, nonce, date string) string { - keyID := keyIDPrefix + apiKey + "\"," - - mac := hmac.New(sha1.New, []byte(apiSecret)) - mac.Write([]byte(datePrefix + date + newline + nonceKey + nonce)) - - encodedMac := mac.Sum(nil) - base64Encoded := base64.StdEncoding.EncodeToString(encodedMac) - encodedSignature := prefix + url.QueryEscape(base64Encoded) + suffix - - return keyID + algorithm + headers + encodedSignature -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_test.go b/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_test.go deleted file mode 100644 index 1670098fa6..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/hmac/signature_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package hmac - -import ( - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestGenerateReturnsSignatureWithKeyId(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "", date()) - expectedPrefix := "Signature keyId=\"api_key\"," - hasKeyID := strings.HasPrefix(signature, expectedPrefix) - assert.True(t, hasKeyID, "HMAC signature must contain the keyId") -} - -func TestGenerateReturnsSignatureWithAlgorithm(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "", date()) - expectedAlgorithm := "algorithm=\"hmac-sha1\"," - actualValue := signature[26:48] - assert.Equal(t, expectedAlgorithm, actualValue, "HMAC signature must contain the algorithm used") -} - -func TestGenerateReturnsSignatureWithHeaders(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "", date()) - expectedHeaders := "headers=\"date x-mod-nonce\"," - actualValue := signature[48:75] - assert.Equal(t, expectedHeaders, actualValue, "HMAC signature must contain the headers") -} - -func TestGenerateReturnsSignatureWithSignatureValue(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "", date()) - expectedSignature := "signature=\"" - actualValue := signature[75:86] - assert.Equal(t, expectedSignature, actualValue, "HMAC signature must contain the signature") -} - -func TestGenerateReturnsHashedSignature(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "", date()) - actualValue := signature[86:117] - assert.True(t, actualValue != "", "Encoded HMAC signature should be present") -} - -func TestGenerateAcceptsANonce(t *testing.T) { - t.Parallel() - - signature := buildSignature("api_key", "api_secret", "nonce", date()) - actualValue := signature[86:116] - expected := "9V8gi5Mp9MsL%2FO7mV6qZlBM9%2FR" - assert.Equal(t, expected, actualValue, "HMAC signature must contain the signature") -} - -func date() string { - now, _ := time.Parse(time.RFC1123, "Mon, 02 Jan 2020 15:04:05 GMT") - - return now.String() -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/loader.go b/components/payments/cmd/connectors/internal/connectors/modulr/loader.go deleted file mode 100644 index c8b7a88c74..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/loader.go +++ /dev/null @@ -1,51 +0,0 @@ -package modulr - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - if cfg.PageSize == 0 { - cfg.PageSize = defaultPageSize - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_accounts.go deleted file mode 100644 index b1e8fcffb9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_accounts.go +++ /dev/null @@ -1,179 +0,0 @@ -package modulr - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchAccounts(config Config, client *client.Client) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - err := fetchAccounts( - ctx, - config, - client, - connectorID, - ingester, - scheduler, - ) - if err != nil { - otel.RecordError(span, err) - // Retry errors are handled by the function - return err - } - - return nil - } -} - -func fetchAccounts( - ctx context.Context, - config Config, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, -) error { - for page := 0; ; page++ { - pagedAccounts, err := client.GetAccounts( - ctx, - page, - config.PageSize, - ) - if err != nil { - // Retry errors are handled by the client - return err - } - - if len(pagedAccounts.Content) == 0 { - break - } - - if err := ingestAccountsBatch(ctx, connectorID, ingester, pagedAccounts.Content); err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - for _, account := range pagedAccounts.Content { - transactionsTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions from client by account", - Key: taskNameFetchTransactions, - AccountID: account.ID, - }) - if err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, transactionsTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - } - - if len(pagedAccounts.Content) < config.PageSize { - break - } - - if page+1 >= pagedAccounts.TotalPages { - // Modulr paging starts at 0, so the last page is TotalPages - 1. - break - } - } - - return nil -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []*client.Account, -) error { - accountsBatch := ingestion.AccountBatch{} - balancesBatch := ingestion.BalanceBatch{} - - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - openingDate, err := time.Parse(timeTemplate, account.CreatedDate) - if err != nil { - return err - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - CreatedAt: openingDate, - Reference: account.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, account.Currency), - AccountName: account.Name, - Type: models.AccountTypeInternal, - RawData: raw, - }) - - // No need to check if the currency is supported for accounts and - // balances. - precision := supportedCurrenciesWithDecimal[account.Currency] - - amount, err := currency.GetAmountWithPrecisionFromString(account.Balance, precision) - if err != nil { - return fmt.Errorf("failed to parse amount %s: %w", account.Balance, err) - } - - now := time.Now() - balancesBatch = append(balancesBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, account.Currency), - Balance: amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, balancesBatch, false); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_beneficiaries.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_beneficiaries.go deleted file mode 100644 index 6bcc7d953d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_beneficiaries.go +++ /dev/null @@ -1,198 +0,0 @@ -package modulr - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchBeneficiariesState struct { - LastCreated time.Time `json:"last_created"` -} - -func (s *fetchBeneficiariesState) UpdateLatest(latest *client.Beneficiary) error { - createdTime, err := time.Parse("2006-01-02T15:04:05.999-0700", latest.Created) - if err != nil { - return err - } - if createdTime.After(s.LastCreated) { - s.LastCreated = createdTime - } - return nil -} - -func (s *fetchBeneficiariesState) FindLatest(beneficiaries []*client.Beneficiary) error { - for _, beneficiary := range beneficiaries { - if err := s.UpdateLatest(beneficiary); err != nil { - return err - } - } - return nil -} - -func (s *fetchBeneficiariesState) IsNew(beneficiary *client.Beneficiary) (bool, error) { - createdTime, err := time.Parse("2006-01-02T15:04:05.999-0700", beneficiary.Created) - if err != nil { - return false, err - } - return createdTime.After(s.LastCreated), nil -} - -func (s *fetchBeneficiariesState) FilterNew(beneficiaries []*client.Beneficiary) ([]*client.Beneficiary, error) { - // beneficiaries are not assumed to be sorted by creation date. - result := make([]*client.Beneficiary, 0, len(beneficiaries)) - for _, beneficiary := range beneficiaries { - isNew, err := s.IsNew(beneficiary) - if err != nil { - return nil, err - } - if !isNew { - continue - } - result = append(result, beneficiary) - } - return result, nil -} - -func (s *fetchBeneficiariesState) GetFilterValue() string { - if s.LastCreated.IsZero() { - return "" - } - return s.LastCreated.Format("2006-01-02T15:04:05-0700") -} - -func taskFetchBeneficiaries(config Config, client *client.Client) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskFetchBeneficiaries", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state, err := fetchBeneficiaries( - ctx, - config, - client, - connectorID, - ingester, - scheduler, - task.MustResolveTo(ctx, resolver, fetchBeneficiariesState{}), - ) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, state); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func fetchBeneficiaries( - ctx context.Context, - config Config, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchBeneficiariesState, -) (fetchBeneficiariesState, error) { - newState := state - for page := 0; ; page++ { - pagedBeneficiaries, err := client.GetBeneficiaries( - ctx, - page, - config.PageSize, - state.GetFilterValue(), - ) - if err != nil { - // Retry errors are handled by the client - return newState, err - } - if len(pagedBeneficiaries.Content) == 0 { - break - } - beneficiaries, err := state.FilterNew(pagedBeneficiaries.Content) - if err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - if err := newState.FindLatest(beneficiaries); err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - if err := ingestBeneficiariesAccountsBatch(ctx, connectorID, ingester, beneficiaries); err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedBeneficiaries.Content) < config.PageSize { - break - } - - if page+1 >= pagedBeneficiaries.TotalPages { - // Modulr paging starts at 0, so the last page is TotalPages - 1. - break - } - } - - return newState, nil -} - -func ingestBeneficiariesAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - beneficiaries []*client.Beneficiary, -) error { - accountsBatch := ingestion.AccountBatch{} - for _, beneficiary := range beneficiaries { - raw, err := json.Marshal(beneficiary) - if err != nil { - return err - } - - openingDate, err := time.Parse(timeTemplate, beneficiary.Created) - if err != nil { - return err - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: beneficiary.ID, - ConnectorID: connectorID, - }, - CreatedAt: openingDate, - Reference: beneficiary.ID, - ConnectorID: connectorID, - AccountName: beneficiary.Name, - Type: models.AccountTypeExternal, - RawData: raw, - }) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_transactions.go deleted file mode 100644 index 3d6b1d9349..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_fetch_transactions.go +++ /dev/null @@ -1,275 +0,0 @@ -package modulr - -import ( - "context" - "encoding/json" - "fmt" - "strings" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchTransactionsState struct { - LastTransactionTime time.Time `json:"last_transaction_time"` -} - -func (s *fetchTransactionsState) UpdateLatest(latest *client.Transaction) error { - transactionTime, err := time.Parse("2006-01-02T15:04:05.999-0700", latest.TransactionDate) - if err != nil { - return err - } - if transactionTime.After(s.LastTransactionTime) { - s.LastTransactionTime = transactionTime - } - return nil -} - -func (s *fetchTransactionsState) FindLatest(transactions []*client.Transaction) error { - for _, transaction := range transactions { - if err := s.UpdateLatest(transaction); err != nil { - return err - } - } - return nil -} - -func (s *fetchTransactionsState) IsNew(transaction *client.Transaction) (bool, error) { - transactionTime, err := time.Parse("2006-01-02T15:04:05.999-0700", transaction.TransactionDate) - if err != nil { - return false, err - } - return transactionTime.After(s.LastTransactionTime), nil -} - -func (s *fetchTransactionsState) FilterNew(transactions []*client.Transaction) ([]*client.Transaction, error) { - result := make([]*client.Transaction, 0, len(transactions)) - for _, transaction := range transactions { - isNew, err := s.IsNew(transaction) - if err != nil { - return nil, err - } - if !isNew { - continue - } - result = append(result, transaction) - } - return result, nil -} - -func (s *fetchTransactionsState) GetFilterValue() string { - if s.LastTransactionTime.IsZero() { - return "" - } - return s.LastTransactionTime.Format("2006-01-02T15:04:05-0700") -} - -func taskFetchTransactions(config Config, client *client.Client, accountID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("accountID", accountID), - ) - defer span.End() - - state, err := fetchTransactions( - ctx, - config, - client, - accountID, - connectorID, - ingester, - task.MustResolveTo(ctx, resolver, fetchTransactionsState{}), - ) - if err != nil { - otel.RecordError(span, err) - // Retry errors are handled by the function - return err - } - - if err := ingester.UpdateTaskState(ctx, state); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func fetchTransactions( - ctx context.Context, - config Config, - client *client.Client, - accountID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - state fetchTransactionsState, -) (fetchTransactionsState, error) { - newState := state - for page := 0; ; page++ { - pagedTransactions, err := client.GetTransactions( - ctx, - accountID, - page, - config.PageSize, - state.GetFilterValue(), - ) - if err != nil { - // Retry errors are handled by the client - return newState, err - } - - if len(pagedTransactions.Content) == 0 { - break - } - - transactions, err := state.FilterNew(pagedTransactions.Content) - if err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - - batch, err := toBatch(connectorID, accountID, transactions) - if err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if err := ingester.IngestPayments(ctx, batch); err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if err := newState.FindLatest(transactions); err != nil { - return newState, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedTransactions.Content) < config.PageSize { - break - } - - if page+1 >= pagedTransactions.TotalPages { - // Modulr paging starts at 0, so the last page is TotalPages - 1. - break - } - } - - return newState, nil -} - -func toBatch( - connectorID models.ConnectorID, - accountID string, - transactions []*client.Transaction, -) (ingestion.PaymentBatch, error) { - batch := ingestion.PaymentBatch{} - - for _, transaction := range transactions { - - rawData, err := json.Marshal(transaction) - if err != nil { - return nil, fmt.Errorf("failed to marshal transaction: %w", err) - } - - paymentType := matchTransactionType(transaction.Type) - - precision, ok := supportedCurrenciesWithDecimal[transaction.Account.Currency] - if !ok { - continue - } - - amount, err := currency.GetAmountWithPrecisionFromString(transaction.Amount.String(), precision) - if err != nil { - return nil, fmt.Errorf("failed to parse amount %s: %w", transaction.Amount, err) - } - - createdAt, err := time.Parse(timeTemplate, transaction.PostedDate) - if err != nil { - return nil, fmt.Errorf("failed to parse posted date %s: %w", transaction.PostedDate, err) - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transaction.ID, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - CreatedAt: createdAt, - Reference: transaction.ID, - ConnectorID: connectorID, - Type: paymentType, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeOther, - Amount: amount, - InitialAmount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transaction.Account.Currency), - RawData: rawData, - }, - } - - switch paymentType { - case models.PaymentTypePayIn: - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - } - case models.PaymentTypePayOut: - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - } - default: - if transaction.Credit { - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - } - } else { - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - } - } - } - - batch = append(batch, batchElement) - } - - return batch, nil -} - -func matchTransactionType(transactionType string) models.PaymentType { - if transactionType == "PI_REV" || - transactionType == "PO_REV" || - transactionType == "ADHOC" || - transactionType == "INT_INTERC" { - return models.PaymentTypeOther - } - - if strings.HasPrefix(transactionType, "PI_") { - return models.PaymentTypePayIn - } - - if strings.HasPrefix(transactionType, "PO_") { - return models.PaymentTypePayOut - } - - return models.PaymentTypeOther -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_main.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_main.go deleted file mode 100644 index 87bf2d121c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_main.go +++ /dev/null @@ -1,70 +0,0 @@ -package modulr - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain(config Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - taskBeneficiaries, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch beneficiaries from client", - Key: taskNameFetchBeneficiaries, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskBeneficiaries, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_payments.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_payments.go deleted file mode 100644 index c856868278..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_payments.go +++ /dev/null @@ -1,341 +0,0 @@ -package modulr - -import ( - "context" - "encoding/json" - "regexp" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -var ( - ReferencePatternRegexp = regexp.MustCompile("[a-zA-Z0-9 ]*") -) - -func taskInitiatePayment(modulrClient *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, modulrClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - modulrClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("no source account provided") - return err - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - var curr string - var precision int - curr, precision, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - amount, err := currency.GetStringAmountFromBigIntWithPrecision(transfer.Amount, precision) - if err != nil { - return err - } - - description := "" - if len(transfer.Description) <= 18 && ReferencePatternRegexp.MatchString(transfer.Description) { - description = transfer.Description - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *client.TransferResponse - resp, err = modulrClient.InitiateTransfer(ctx, &client.TransferRequest{ - SourceAccountID: transfer.SourceAccountID.Reference, - Destination: client.Destination{ - Type: string(client.DestinationTypeAccount), - ID: transfer.DestinationAccountID.Reference, - }, - Currency: curr, - Amount: json.Number(amount), - Reference: description, - ExternalReference: description, - PaymentDate: time.Now().Add(24 * time.Hour).Format("2006-01-02"), - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - var resp *client.PayoutResponse - resp, err = modulrClient.InitiatePayout(ctx, &client.PayoutRequest{ - SourceAccountID: transfer.SourceAccountID.Reference, - Destination: client.Destination{ - Type: string(client.DestinationTypeBeneficiary), - ID: transfer.DestinationAccountID.Reference, - }, - Currency: curr, - Amount: json.Number(amount), - Reference: description, - ExternalReference: description, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - modulrClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "modulr.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", pID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, modulrClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - modulrClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status string - var resultMessage string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - var resp *client.TransferResponse - resp, err = modulrClient.GetTransfer(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.Message - case models.TransferInitiationTypePayout: - var resp *client.PayoutResponse - resp, err = modulrClient.GetPayout(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - resultMessage = resp.Message - } - - switch status { - case "SUBMITTED", "PENDING_FOR_DATE", "PENDING_FOR_FUNDS", "VALIDATED", "SCREENING_REQ": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "EXT_PROC", "PROCESSED", "RECONCILED": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - default: - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, resultMessage, time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/modulr/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/modulr/task_resolve.go deleted file mode 100644 index 74b4631423..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/modulr/task_resolve.go +++ /dev/null @@ -1,66 +0,0 @@ -package modulr - -import ( - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/modulr/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" - - "github.com/formancehq/go-libs/logging" -) - -const ( - taskNameMain = "main" - taskNameFetchTransactions = "fetch-transactions" - taskNameFetchAccounts = "fetch-accounts" - taskNameFetchBeneficiaries = "fetch-beneficiaries" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" -) - -const ( - timeTemplate = "2006-01-02T15:04:05-0700" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - AccountID string `json:"accountID" yaml:"accountID" bson:"accountID"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` -} - -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - modulrClient, err := client.NewClient(config.APIKey, config.APISecret, config.Endpoint) - if err != nil { - return func(taskDefinition TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("key '%s': %w", taskDefinition.Key, ErrMissingTask) - } - } - } - - return func(taskDefinition TaskDescriptor) task.Task { - switch taskDefinition.Key { - case taskNameMain: - return taskMain(config) - case taskNameFetchAccounts: - return taskFetchAccounts(config, modulrClient) - case taskNameFetchBeneficiaries: - return taskFetchBeneficiaries(config, modulrClient) - case taskNameInitiatePayment: - return taskInitiatePayment(modulrClient, taskDefinition.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(modulrClient, taskDefinition.TransferID, taskDefinition.PaymentID, taskDefinition.Attempt) - case taskNameFetchTransactions: - return taskFetchTransactions(config, modulrClient, taskDefinition.AccountID) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDefinition.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/accounts.go deleted file mode 100644 index c452a518f8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/accounts.go +++ /dev/null @@ -1,70 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type accountsResponse struct { - Accounts []*Account `json:"data"` -} - -type Account struct { - ID string `json:"id"` - Attributes struct { - AccountName string `json:"accountName"` - } `json:"attributes"` -} - -func (c *Client) GetAccounts(ctx context.Context, page int, pageSize int) ([]*Account, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "list_accounts") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts", c.endpoint) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create accounts request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - - q := req.URL.Query() - q.Add("page[size]", strconv.Itoa(pageSize)) - q.Add("page[number]", fmt.Sprint(page)) - q.Add("sortBy", "id.asc") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode == http.StatusNotFound { - return []*Account{}, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var accounts accountsResponse - if err := json.NewDecoder(resp.Body).Decode(&accounts); err != nil { - return nil, fmt.Errorf("failed to unmarshal accounts response body: %w", err) - } - - return accounts.Accounts, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/auth.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/auth.go deleted file mode 100644 index 2db18bc997..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/auth.go +++ /dev/null @@ -1,110 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -// Cannot use "golang.org/x/oauth2/clientcredentials" lib because moneycorp -// is only accepting request with "application/json" content type, and the lib -// sets it as application/x-www-form-urlencoded, giving us a 415 error. -type apiTransport struct { - logger logging.Logger - - clientID string - apiKey string - endpoint string - - accessToken string - accessTokenExpiresAt time.Time - - underlying *otelhttp.Transport -} - -func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if err := t.ensureAccessTokenIsValid(req.Context()); err != nil { - return nil, err - } - - req.Header.Add("Authorization", "Bearer "+t.accessToken) - - return t.underlying.RoundTrip(req) -} - -func (t *apiTransport) ensureAccessTokenIsValid(ctx context.Context) error { - if t.accessTokenExpiresAt.After(time.Now().Add(5 * time.Second)) { - return nil - } - - return t.login(ctx) -} - -type loginRequest struct { - ClientID string `json:"loginId"` - APIKey string `json:"apiKey"` -} - -type loginResponse struct { - Data struct { - AccessToken string `json:"accessToken"` - ExpiresIn int `json:"expiresIn"` - } `json:"data"` -} - -func (t *apiTransport) login(ctx context.Context) error { - lreq := loginRequest{ - ClientID: t.clientID, - APIKey: t.apiKey, - } - - requestBody, err := json.Marshal(lreq) - if err != nil { - return fmt.Errorf("failed to marshal login request: %w", err) - } - - f := connectors.ClientMetrics(ctx, "moneycorp", "login") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, - t.endpoint+"/login", bytes.NewBuffer(requestBody)) - if err != nil { - return fmt.Errorf("failed to create login request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed to login: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - t.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var res loginResponse - if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { - return fmt.Errorf("failed to decode login response: %w", err) - } - - t.accessToken = res.Data.AccessToken - t.accessTokenExpiresAt = time.Now().Add(time.Duration(res.Data.ExpiresIn) * time.Second) - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/balances.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/balances.go deleted file mode 100644 index 267193d821..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/balances.go +++ /dev/null @@ -1,67 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type balancesResponse struct { - Balances []*Balance `json:"data"` -} - -type Balance struct { - ID string `json:"id"` - Attributes struct { - CurrencyCode string `json:"currencyCode"` - OverallBalance json.Number `json:"overallBalance"` - AvailableBalance json.Number `json:"availableBalance"` - ClearedBalance json.Number `json:"clearedBalance"` - ReservedBalance json.Number `json:"reservedBalance"` - UnclearedBalance json.Number `json:"unclearedBalance"` - } `json:"attributes"` -} - -func (c *Client) GetAccountBalances(ctx context.Context, accountID string) ([]*Balance, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "list_account_balances") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/balances", c.endpoint, accountID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get account balances: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode == http.StatusNotFound { - return []*Balance{}, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var balances balancesResponse - if err := json.NewDecoder(resp.Body).Decode(&balances); err != nil { - return nil, fmt.Errorf("failed to unmarshal balances response body: %w", err) - } - - return balances.Balances, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/client.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/client.go deleted file mode 100644 index 0e9f9094c4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/client.go +++ /dev/null @@ -1,45 +0,0 @@ -package client - -import ( - "net/http" - "strings" - "time" - - "github.com/formancehq/go-libs/logging" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -type Client struct { - httpClient *http.Client - - endpoint string - - logger logging.Logger -} - -func newHTTPClient(clientID, apiKey, endpoint string, logger logging.Logger) *http.Client { - return &http.Client{ - Timeout: 10 * time.Second, - Transport: &apiTransport{ - logger: logger, - clientID: clientID, - apiKey: apiKey, - endpoint: endpoint, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - } -} - -func NewClient(clientID, apiKey, endpoint string, logger logging.Logger) (*Client, error) { - endpoint = strings.TrimSuffix(endpoint, "/") - - c := &Client{ - httpClient: newHTTPClient(clientID, apiKey, endpoint, logger), - - endpoint: endpoint, - - logger: logger, - } - - return c, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/error.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/error.go deleted file mode 100644 index eb6f3a463e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/error.go +++ /dev/null @@ -1,83 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/pkg/errors" -) - -type moneycorpErrors struct { - Errors []*moneycorpError `json:"errors"` -} - -type moneycorpError struct { - StatusCode int `json:"-"` - Code string `json:"code"` - Title string `json:"title"` - Detail string `json:"detail"` - WithRetry bool `json:"-"` -} - -func (me *moneycorpError) Error() error { - var err error - if me.Detail == "" { - err = fmt.Errorf("unexpected status code: %d", me.StatusCode) - } else { - err = fmt.Errorf("%d: %s", me.StatusCode, me.Detail) - } - - if me.WithRetry { - return checkStatusCodeError(me.StatusCode, err) - } - - return errors.Wrap(task.ErrNonRetryable, err.Error()) -} - -func unmarshalError(statusCode int, body io.ReadCloser, withRetry bool) *moneycorpError { - var ces moneycorpErrors - _ = json.NewDecoder(body).Decode(&ces) - - if len(ces.Errors) == 0 { - return &moneycorpError{ - StatusCode: statusCode, - WithRetry: withRetry, - } - } - - return &moneycorpError{ - StatusCode: statusCode, - Code: ces.Errors[0].Code, - Title: ces.Errors[0].Title, - Detail: ces.Errors[0].Detail, - WithRetry: withRetry, - } -} - -func unmarshalErrorWithoutRetry(statusCode int, body io.ReadCloser) *moneycorpError { - return unmarshalError(statusCode, body, false) -} - -func unmarshalErrorWithRetry(statusCode int, body io.ReadCloser) *moneycorpError { - return unmarshalError(statusCode, body, true) -} - -func checkStatusCodeError(statusCode int, err error) error { - switch statusCode { - case http.StatusTooEarly, http.StatusRequestTimeout: - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusTooManyRequests: - // Retry rate limit errors - // TODO(polo): add rate limit handling - return errors.Wrap(task.ErrRetryable, err.Error()) - case http.StatusInternalServerError, http.StatusBadGateway, - http.StatusServiceUnavailable, http.StatusGatewayTimeout: - // Retry internal errors - return errors.Wrap(task.ErrRetryable, err.Error()) - default: - return errors.Wrap(task.ErrNonRetryable, err.Error()) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/payout.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/payout.go deleted file mode 100644 index adb31ea342..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/payout.go +++ /dev/null @@ -1,134 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type payoutRequest struct { - Payout struct { - Attributes *PayoutRequest `json:"attributes"` - } `json:"data"` -} - -type PayoutRequest struct { - SourceAccountID string `json:"-"` - IdempotencyKey string `json:"-"` - RecipientID string `json:"recipientId"` - PaymentDate string `json:"paymentDate"` - PaymentAmount json.Number `json:"paymentAmount"` - PaymentCurrency string `json:"paymentCurrency"` - PaymentMethgod string `json:"paymentMethod"` - PaymentReference string `json:"paymentReference"` - ClientReference string `json:"clientReference"` - PaymentPurpose string `json:"paymentPurpose"` -} - -type payoutResponse struct { - Payout *PayoutResponse `json:"data"` -} - -type PayoutResponse struct { - ID string `json:"id"` - Attributes struct { - AccountID string `json:"accountId"` - PaymentAmount json.Number `json:"paymentAmount"` - PaymentCurrency string `json:"paymentCurrency"` - PaymentApproved bool `json:"paymentApproved"` - PaymentStatus string `json:"paymentStatus"` - PaymentMethod string `json:"paymentMethod"` - PaymentDate string `json:"paymentDate"` - PaymentValueDate string `json:"paymentValueDate"` - RecipientDetails struct { - RecipientID int32 `json:"recipientId"` - } `json:"recipientDetails"` - PaymentReference string `json:"paymentReference"` - ClientReference string `json:"clientReference"` - CreatedAt string `json:"createdAt"` - CreatedBy string `json:"createdBy"` - UpdatedAt string `json:"updatedAt"` - PaymentPurpose string `json:"paymentPurpose"` - } `json:"attributes"` -} - -func (c *Client) InitiatePayout(ctx context.Context, pr *PayoutRequest) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/payments", c.endpoint, pr.SourceAccountID) - - reqBody := &payoutRequest{} - reqBody.Payout.Attributes = pr - body, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("failed to marshal payout request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Idempotency-Key", pr.IdempotencyKey) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - // Never retry payout initiation - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var res payoutResponse - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - return res.Payout, nil -} - -func (c *Client) GetPayout(ctx context.Context, accountID string, payoutID string) (*PayoutResponse, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "get_payout") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/payments/%s", c.endpoint, accountID, payoutID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create get payout request request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get account balances: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var payoutResponse payoutResponse - if err := json.NewDecoder(resp.Body).Decode(&payoutResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return payoutResponse.Payout, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/recipients.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/recipients.go deleted file mode 100644 index ace7da2059..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/recipients.go +++ /dev/null @@ -1,72 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type recipientsResponse struct { - Recipients []*Recipient `json:"data"` -} - -type Recipient struct { - ID string `json:"id"` - Attributes struct { - BankAccountCurrency string `json:"bankAccountCurrency"` - CreatedAt string `json:"createdAt"` - BankAccountName string `json:"bankAccountName"` - } `json:"attributes"` -} - -func (c *Client) GetRecipients(ctx context.Context, accountID string, page int, pageSize int) ([]*Recipient, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "list_recipients") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/recipients", c.endpoint, accountID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create recipients request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - - q := req.URL.Query() - q.Add("page[size]", strconv.Itoa(pageSize)) - q.Add("page[number]", fmt.Sprint(page)) - q.Add("sortBy", "createdAt.asc") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode == http.StatusNotFound { - return []*Recipient{}, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var recipients recipientsResponse - if err := json.NewDecoder(resp.Body).Decode(&recipients); err != nil { - return nil, fmt.Errorf("failed to unmarshal recipients response body: %w", err) - } - - return recipients.Recipients, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transactions.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transactions.go deleted file mode 100644 index 48b56ff493..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transactions.go +++ /dev/null @@ -1,114 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type transactionsResponse struct { - Transactions []*Transaction `json:"data"` -} - -type fetchTransactionRequest struct { - Data struct { - Attributes struct { - TransactionDateTimeFrom string `json:"transactionDateTimeFrom"` - } `json:"attributes"` - } `json:"data"` -} - -type Transaction struct { - ID string `json:"id"` - Type string `json:"type"` - Attributes struct { - AccountID int32 `json:"accountId"` - CreatedAt string `json:"createdAt"` - Currency string `json:"transactionCurrency"` - Amount json.Number `json:"transactionAmount"` - Direction string `json:"transactionDirection"` - Type string `json:"transactionType"` - ClientReference string `json:"clientReference"` - TransactionReference string `json:"transactionReference"` - } `json:"attributes"` -} - -func (c *Client) GetTransactions(ctx context.Context, accountID string, page, pageSize int, lastCreatedAt time.Time) ([]*Transaction, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "list_transactions") - now := time.Now() - defer f(ctx, now) - - var body io.Reader - if !lastCreatedAt.IsZero() { - reqBody := fetchTransactionRequest{ - Data: struct { - Attributes struct { - TransactionDateTimeFrom string "json:\"transactionDateTimeFrom\"" - } "json:\"attributes\"" - }{ - Attributes: struct { - TransactionDateTimeFrom string "json:\"transactionDateTimeFrom\"" - }{ - TransactionDateTimeFrom: lastCreatedAt.Format("2006-01-02T15:04:05.999999999"), - }, - }, - } - - raw, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("failed to marshal transfer request: %w", err) - } - - body = bytes.NewBuffer(raw) - } else { - body = http.NoBody - } - - endpoint := fmt.Sprintf("%s/accounts/%s/transactions/find", c.endpoint, accountID) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body) - if err != nil { - return nil, fmt.Errorf("failed to create login request: %w", err) - } - - req.Header.Set("Content-Type", "application/json") - - q := req.URL.Query() - q.Add("page[size]", strconv.Itoa(pageSize)) - q.Add("page[number]", fmt.Sprint(page)) - q.Add("sortBy", "createdAt.asc") - req.URL.RawQuery = q.Encode() - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get transactions: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode == http.StatusNotFound { - return []*Transaction{}, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var transactions transactionsResponse - if err := json.NewDecoder(resp.Body).Decode(&transactions); err != nil { - return nil, fmt.Errorf("failed to unmarshal transactions response body: %w", err) - } - - return transactions.Transactions, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transfer.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transfer.go deleted file mode 100644 index 2badd511f9..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/client/transfer.go +++ /dev/null @@ -1,133 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type transferRequest struct { - Transfer struct { - Attributes *TransferRequest `json:"attributes"` - } `json:"data"` -} - -type TransferRequest struct { - SourceAccountID string `json:"-"` - IdempotencyKey string `json:"-"` - ReceivingAccountID string `json:"receivingAccountId"` - TransferAmount json.Number `json:"transferAmount"` - TransferCurrency string `json:"transferCurrency"` - TransferReference string `json:"transferReference,omitempty"` - ClientReference string `json:"clientReference,omitempty"` -} - -type transferResponse struct { - Transfer *TransferResponse `json:"data"` -} - -type TransferResponse struct { - ID string `json:"id"` - Attributes struct { - SendingAccountID int64 `json:"sendingAccountId"` - SendingAccountName string `json:"sendingAccountName"` - ReceivingAccountID int64 `json:"receivingAccountId"` - ReceivingAccountName string `json:"receivingAccountName"` - CreatedAt string `json:"createdAt"` - CreatedBy string `json:"createdBy"` - UpdatedAt string `json:"updatedAt"` - TransferReference string `json:"transferReference"` - ClientReference string `json:"clientReference"` - TransferDate string `json:"transferDate"` - TransferAmount json.Number `json:"transferAmount"` - TransferCurrency string `json:"transferCurrency"` - TransferStatus string `json:"transferStatus"` - } `json:"attributes"` -} - -func (c *Client) InitiateTransfer(ctx context.Context, tr *TransferRequest) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "initiate_transfer") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/transfers", c.endpoint, tr.SourceAccountID) - - reqBody := &transferRequest{} - reqBody.Transfer.Attributes = tr - body, err := json.Marshal(reqBody) - if err != nil { - return nil, fmt.Errorf("failed to marshal transfer request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body)) - if err != nil { - return nil, fmt.Errorf("failed to create transfer request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Idempotency-Key", tr.IdempotencyKey) - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get account balances: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusCreated { - // Never retry transfer initiation - return nil, unmarshalErrorWithoutRetry(resp.StatusCode, resp.Body).Error() - } - - var transferResponse transferResponse - if err := json.NewDecoder(resp.Body).Decode(&transferResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return transferResponse.Transfer, nil -} - -func (c *Client) GetTransfer(ctx context.Context, accountID string, transferID string) (*TransferResponse, error) { - f := connectors.ClientMetrics(ctx, "moneycorp", "get_transfer") - now := time.Now() - defer f(ctx, now) - - endpoint := fmt.Sprintf("%s/accounts/%s/transfers/%s", c.endpoint, accountID, transferID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create get transfer request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get account balances: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - c.logger.Error(err) - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalErrorWithRetry(resp.StatusCode, resp.Body).Error() - } - - var transferResponse transferResponse - if err := json.NewDecoder(resp.Body).Decode(&transferResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) - } - - return transferResponse.Transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/config.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/config.go deleted file mode 100644 index a59c08fa26..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/config.go +++ /dev/null @@ -1,67 +0,0 @@ -package moneycorp - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - pageSize = 100 - defaultPollingPeriod = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - ClientID string `json:"clientID" yaml:"clientID" bson:"clientID"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - Endpoint string `json:"endpoint" yaml:"endpoint" bson:"endpoint"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -func (c Config) String() string { - return fmt.Sprintf("clientID=%s, apiKey=****", c.ClientID) -} - -func (c Config) Validate() error { - if c.ClientID == "" { - return ErrMissingClientID - } - - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.Endpoint == "" { - return ErrMissingEndpoint - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("clientID", configtemplate.TypeString, "", true) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("endpoint", configtemplate.TypeString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/connector.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/connector.go deleted file mode 100644 index 1d2654771b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/connector.go +++ /dev/null @@ -1,136 +0,0 @@ -package moneycorp - -import ( - "context" - "errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderMoneycorp - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch accounts and transactions", - Key: taskNameMain, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - taskDescriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return resolveTasks(c.logger, c.cfg)(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/currencies.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/currencies.go deleted file mode 100644 index f0bff20789..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/currencies.go +++ /dev/null @@ -1,63 +0,0 @@ -package moneycorp - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - supportedCurrenciesWithDecimal = map[string]int{ - "AED": currency.ISO4217Currencies["AED"], // UAE Dirham - "AUD": currency.ISO4217Currencies["AUD"], // Australian Dollar - "BBD": currency.ISO4217Currencies["BBD"], // Barbados Dollar - "BGN": currency.ISO4217Currencies["BGN"], // Bulgarian lev - "BHD": currency.ISO4217Currencies["BHD"], // Bahraini dinar - "BWP": currency.ISO4217Currencies["BWP"], // Botswana pula - "CAD": currency.ISO4217Currencies["CAD"], // Canadian dollar - "CHF": currency.ISO4217Currencies["CHF"], // Swiss franc - "CZK": currency.ISO4217Currencies["CZK"], // Czech koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish krone - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "GBP": currency.ISO4217Currencies["GBP"], // Pound sterling - "GHS": currency.ISO4217Currencies["GHS"], // Ghanaian cedi - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong dollar - "ILS": currency.ISO4217Currencies["ILS"], // Israeli new shekel - "INR": currency.ISO4217Currencies["INR"], // Indian rupee - "JMD": currency.ISO4217Currencies["JMD"], // Jamaican dollar - "JPY": currency.ISO4217Currencies["JPY"], // Japanese yen - "KES": currency.ISO4217Currencies["KES"], // Kenyan shilling - "LKR": currency.ISO4217Currencies["LKR"], // Sri Lankan rupee - "MAD": currency.ISO4217Currencies["MAD"], // Moroccan dirham - "MUR": currency.ISO4217Currencies["MUR"], // Mauritian rupee - "MXN": currency.ISO4217Currencies["MXN"], // Mexican peso - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian krone - "NPR": currency.ISO4217Currencies["NPR"], // Nepalese rupee - "NZD": currency.ISO4217Currencies["NZD"], // New Zealand dollar - "OMR": currency.ISO4217Currencies["OMR"], // Omani rial - "PHP": currency.ISO4217Currencies["PHP"], // Philippine peso - "PKR": currency.ISO4217Currencies["PKR"], // Pakistani rupee - "PLN": currency.ISO4217Currencies["PLN"], // Polish złoty - "QAR": currency.ISO4217Currencies["QAR"], // Qatari riyal - "RON": currency.ISO4217Currencies["RON"], // Romanian leu - "RSD": currency.ISO4217Currencies["RSD"], // Serbian dinar - "SAR": currency.ISO4217Currencies["SAR"], // Saudi riyal - "SEK": currency.ISO4217Currencies["SEK"], // Swedish krona/kronor - "SGD": currency.ISO4217Currencies["SGD"], // Singapore dollar - "THB": currency.ISO4217Currencies["THB"], // Thai baht - "TRY": currency.ISO4217Currencies["TRY"], // Turkish lira - "TTD": currency.ISO4217Currencies["TTD"], // Trinidad and Tobago dollar - "UGX": currency.ISO4217Currencies["UGX"], // Ugandan shilling - "USD": currency.ISO4217Currencies["USD"], // United States dollar - "XCD": currency.ISO4217Currencies["XCD"], // East Caribbean dollar - "ZAR": currency.ISO4217Currencies["ZAR"], // South African rand - "ZMW": currency.ISO4217Currencies["ZMW"], // Zambian kwacha - - // All following currencies have not the same decimals given by - // Moneycorp compared to the ISO 4217 standard. - // Let's not handle them for now. - // "CNH": 2, // Chinese Yuan - // "IDR": 2, // Indonesian rupiah - // "ISK": 0, // Icelandic króna - // "HUF": 2, // Hungarian forint - // "JOD": 3, // Jordanian dinar - // "KWD": 3, // Kuwaiti dinar - // "TND": 3, // Tunisian dinar - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/errors.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/errors.go deleted file mode 100644 index 8e034ec98d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/errors.go +++ /dev/null @@ -1,20 +0,0 @@ -package moneycorp - -import "errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingClientID is returned when the clientID is missing. - ErrMissingClientID = errors.New("missing clientID from config") - - // ErrMissingAPIKey is returned when the apiKey is missing. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingEndpoint is returned when the endpoint is missing. - ErrMissingEndpoint = errors.New("missing endpoint from config") - - // ErrMissingName is returned when the name is missing. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/loader.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/loader.go deleted file mode 100644 index c44a03c58c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/loader.go +++ /dev/null @@ -1,47 +0,0 @@ -package moneycorp - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_accounts.go deleted file mode 100644 index 51bc589c5e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_accounts.go +++ /dev/null @@ -1,186 +0,0 @@ -package moneycorp - -import ( - "context" - "encoding/json" - "errors" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -type fetchAccountsState struct { - LastPage int `json:"last_page"` - // Moneycorp does not send the creation date for accounts, but we can still - // sort by ID created (which is incremental when creating accounts). - LastIDCreated string `json:"last_id_created"` -} - -func taskFetchAccounts(client *client.Client, config *Config) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskFetchAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchAccountsState{}) - - newState, err := fetchAccounts(ctx, config, client, connectorID, ingester, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchAccounts( - ctx context.Context, - config *Config, - client *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchAccountsState, -) (fetchAccountsState, error) { - newState := fetchAccountsState{ - LastPage: state.LastPage, - LastIDCreated: state.LastIDCreated, - } - - for page := state.LastPage; ; page++ { - newState.LastPage = page - - pagedAccounts, err := client.GetAccounts(ctx, page, pageSize) - if err != nil { - return fetchAccountsState{}, err - } - - if len(pagedAccounts) == 0 { - break - } - - batch := ingestion.AccountBatch{} - transactionTasks := []models.TaskDescriptor{} - balanceTasks := []models.TaskDescriptor{} - recipientTasks := []models.TaskDescriptor{} - for _, account := range pagedAccounts { - if account.ID <= state.LastIDCreated { - continue - } - - raw, err := json.Marshal(account) - if err != nil { - return fetchAccountsState{}, err - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - // Moneycorp does not send the opening date of the account - CreatedAt: time.Now().UTC(), - Reference: account.ID, - ConnectorID: connectorID, - AccountName: account.Attributes.AccountName, - Type: models.AccountTypeInternal, - RawData: raw, - }) - - transactionTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions from client by account", - Key: taskNameFetchTransactions, - AccountID: account.ID, - }) - if err != nil { - return fetchAccountsState{}, err - } - transactionTasks = append(transactionTasks, transactionTask) - - balanceTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch balances from client by account", - Key: taskNameFetchBalances, - AccountID: account.ID, - }) - if err != nil { - return fetchAccountsState{}, err - } - balanceTasks = append(balanceTasks, balanceTask) - - recipientTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch recipients from client", - Key: taskNameFetchRecipients, - AccountID: account.ID, - }) - if err != nil { - return fetchAccountsState{}, err - } - recipientTasks = append(recipientTasks, recipientTask) - - newState.LastIDCreated = account.ID - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return fetchAccountsState{}, err - } - - for _, transactionTask := range transactionTasks { - if err := scheduler.Schedule(ctx, transactionTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchAccountsState{}, err - } - } - - for _, balanceTask := range balanceTasks { - if err := scheduler.Schedule(ctx, balanceTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchAccountsState{}, err - } - } - - for _, recipientTask := range recipientTasks { - if err := scheduler.Schedule(ctx, recipientTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: config.PollingPeriod.Duration, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }); err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return fetchAccountsState{}, err - } - } - - if len(pagedAccounts) < pageSize { - break - } - } - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_balances.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_balances.go deleted file mode 100644 index 2f96463d33..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_balances.go +++ /dev/null @@ -1,97 +0,0 @@ -package moneycorp - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchBalances(client *client.Client, accountID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskFetchBalances", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("accountID", accountID), - ) - defer span.End() - - if err := fetchBalances(ctx, client, accountID, connectorID, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchBalances( - ctx context.Context, - client *client.Client, - accountID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, -) error { - balances, err := client.GetAccountBalances(ctx, accountID) - if err != nil { - // retryable error already handled by the client - return err - } - - if err := ingestBalancesBatch(ctx, connectorID, ingester, accountID, balances); err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil -} - -func ingestBalancesBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accountID string, - balances []*client.Balance, -) error { - batch := ingestion.BalanceBatch{} - for _, balance := range balances { - precision, err := currency.GetPrecision(supportedCurrenciesWithDecimal, balance.Attributes.CurrencyCode) - if err != nil { - return err - } - - amount, err := currency.GetAmountWithPrecisionFromString(balance.Attributes.AvailableBalance.String(), precision) - if err != nil { - return err - } - - now := time.Now() - batch = append(batch, &models.Balance{ - AccountID: models.AccountID{ - Reference: accountID, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Attributes.CurrencyCode), - Balance: amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - - return ingester.IngestBalances(ctx, batch, false) -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_recipients.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_recipients.go deleted file mode 100644 index 5e9823eaad..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_recipients.go +++ /dev/null @@ -1,135 +0,0 @@ -package moneycorp - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchRecipientsState struct { - LastPage int `json:"last_page"` - // Moneycorp does not allow us to sort by , but we can still - // sort by ID created (which is incremental when creating accounts). - LastCreatedAt time.Time `json:"last_created_at"` -} - -func taskFetchRecipients(client *client.Client, accountID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskFetchRecipients", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("accountID", accountID), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchRecipientsState{}) - - newState, err := fetchRecipients(ctx, client, accountID, connectorID, ingester, scheduler, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func fetchRecipients( - ctx context.Context, - client *client.Client, - accountID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - state fetchRecipientsState, -) (fetchRecipientsState, error) { - newState := fetchRecipientsState{ - LastPage: state.LastPage, - LastCreatedAt: state.LastCreatedAt, - } - - for page := 0; ; page++ { - newState.LastPage = page - - pagedRecipients, err := client.GetRecipients(ctx, accountID, page, pageSize) - if err != nil { - // Retryable errors already handled by the client - return fetchRecipientsState{}, err - } - - if len(pagedRecipients) == 0 { - break - } - - batch := ingestion.AccountBatch{} - for _, recipient := range pagedRecipients { - createdAt, err := time.Parse("2006-01-02T15:04:05.999999999", recipient.Attributes.CreatedAt) - if err != nil { - return fetchRecipientsState{}, errors.Wrap(task.ErrNonRetryable, fmt.Sprintf("failed to parse transaction date: %v", err)) - } - - switch createdAt.Compare(state.LastCreatedAt) { - case -1, 0: - continue - default: - } - - raw, err := json.Marshal(recipient) - if err != nil { - return fetchRecipientsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: recipient.ID, - ConnectorID: connectorID, - }, - // Moneycorp does not send the opening date of the account - CreatedAt: createdAt, - Reference: recipient.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, recipient.Attributes.BankAccountCurrency), - AccountName: recipient.Attributes.BankAccountName, - Type: models.AccountTypeExternal, - RawData: raw, - }) - - newState.LastCreatedAt = createdAt - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return fetchRecipientsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedRecipients) < pageSize { - break - } - } - - return newState, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_transactions.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_transactions.go deleted file mode 100644 index 536952923f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_fetch_transactions.go +++ /dev/null @@ -1,214 +0,0 @@ -package moneycorp - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchTransactionsState struct { - LastCreatedAt time.Time `json:"last_created_at"` -} - -func taskFetchTransactions(client *client.Client, accountID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskFetchTransactions", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("accountID", accountID), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchTransactionsState{}) - - newState, err := fetchTransactions(ctx, client, accountID, connectorID, ingester, state) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := ingester.UpdateTaskState(ctx, newState); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func fetchTransactions( - ctx context.Context, - client *client.Client, - accountID string, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - state fetchTransactionsState, -) (fetchTransactionsState, error) { - newState := fetchTransactionsState{ - LastCreatedAt: state.LastCreatedAt, - } - - for page := 0; ; page++ { - pagedTransactions, err := client.GetTransactions(ctx, accountID, page, pageSize, state.LastCreatedAt) - if err != nil { - // retryable error already handled by the client - return fetchTransactionsState{}, err - } - - if len(pagedTransactions) == 0 { - break - } - - batch := ingestion.PaymentBatch{} - for _, transaction := range pagedTransactions { - createdAt, err := time.Parse("2006-01-02T15:04:05.999999999", transaction.Attributes.CreatedAt) - if err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrNonRetryable, fmt.Sprintf("failed to parse transaction date: %v", err)) - } - - switch createdAt.Compare(state.LastCreatedAt) { - case -1, 0: - continue - default: - } - - newState.LastCreatedAt = createdAt - - batchElement, err := toPaymentBatch(connectorID, transaction) - if err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if batchElement == nil { - continue - } - - batch = append(batch, *batchElement) - } - - if err := ingester.IngestPayments(ctx, batch); err != nil { - return fetchTransactionsState{}, errors.Wrap(task.ErrRetryable, err.Error()) - } - - if len(pagedTransactions) < pageSize { - break - } - } - - return fetchTransactionsState{}, nil -} - -func toPaymentBatch( - connectorID models.ConnectorID, - transaction *client.Transaction, -) (*ingestion.PaymentBatchElement, error) { - rawData, err := json.Marshal(transaction) - if err != nil { - return nil, fmt.Errorf("failed to marshal transaction: %w", err) - } - - paymentType, shouldBeRecorded := matchPaymentType(transaction.Attributes.Type, transaction.Attributes.Direction) - if !shouldBeRecorded { - return nil, nil - } - - createdAt, err := time.Parse("2006-01-02T15:04:05.999999999", transaction.Attributes.CreatedAt) - if err != nil { - return nil, fmt.Errorf("failed to parse transaction date: %w", err) - } - - c, err := currency.GetPrecision(supportedCurrenciesWithDecimal, transaction.Attributes.Currency) - if err != nil { - return nil, err - } - - amount, err := currency.GetAmountWithPrecisionFromString(transaction.Attributes.Amount.String(), c) - if err != nil { - return nil, err - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: transaction.ID, - Type: paymentType, - }, - ConnectorID: connectorID, - }, - CreatedAt: createdAt, - Reference: transaction.ID, - ConnectorID: connectorID, - Amount: amount, - InitialAmount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transaction.Attributes.Currency), - Type: paymentType, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeOther, - RawData: rawData, - }, - } - - switch paymentType { - case models.PaymentTypePayIn: - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: strconv.Itoa(int(transaction.Attributes.AccountID)), - ConnectorID: connectorID, - } - case models.PaymentTypePayOut: - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: strconv.Itoa(int(transaction.Attributes.AccountID)), - ConnectorID: connectorID, - } - default: - if transaction.Attributes.Direction == "Debit" { - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: strconv.Itoa(int(transaction.Attributes.AccountID)), - ConnectorID: connectorID, - } - } else { - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: strconv.Itoa(int(transaction.Attributes.AccountID)), - ConnectorID: connectorID, - } - } - } - - return &batchElement, nil -} - -func matchPaymentType(transactionType string, transactionDirection string) (models.PaymentType, bool) { - switch transactionType { - case "Transfer": - return models.PaymentTypeTransfer, true - case "Payment", "Exchange", "Charge", "Refund": - switch transactionDirection { - case "Debit": - return models.PaymentTypePayOut, true - case "Credit": - return models.PaymentTypePayIn, true - } - } - - return models.PaymentTypeOther, false -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_main.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_main.go deleted file mode 100644 index 4391b0da7a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_main.go +++ /dev/null @@ -1,50 +0,0 @@ -package moneycorp - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_payments.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_payments.go deleted file mode 100644 index e2ecc98149..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_payments.go +++ /dev/null @@ -1,326 +0,0 @@ -package moneycorp - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskInitiatePayment(moneycorpClient *client.Client, transferID string) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, moneycorpClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - moneycorpClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("no source account provided") - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - var curr string - var precision int - curr, precision, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - amount, err := currency.GetStringAmountFromBigIntWithPrecision(transfer.Amount, precision) - if err != nil { - return err - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *client.TransferResponse - resp, err = moneycorpClient.InitiateTransfer(ctx, &client.TransferRequest{ - SourceAccountID: transfer.SourceAccountID.Reference, - IdempotencyKey: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - ReceivingAccountID: transfer.DestinationAccountID.Reference, - TransferAmount: json.Number(amount), - TransferCurrency: curr, - TransferReference: transfer.Description, - ClientReference: transfer.Description, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - var resp *client.PayoutResponse - resp, err = moneycorpClient.InitiatePayout(ctx, &client.PayoutRequest{ - SourceAccountID: transfer.SourceAccountID.Reference, - IdempotencyKey: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - RecipientID: transfer.DestinationAccountID.Reference, - PaymentAmount: json.Number(amount), - PaymentCurrency: curr, - PaymentMethgod: "Standard", - PaymentReference: transfer.Description, - ClientReference: transfer.Description, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - moneycorpClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "moneycorp.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", pID), - attribute.Int("attempt", attempt), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, moneycorpClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - moneycorpClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status string - var resultMessage string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - var resp *client.TransferResponse - resp, err = moneycorpClient.GetTransfer(ctx, transfer.SourceAccount.Reference, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Attributes.TransferStatus - case models.TransferInitiationTypePayout: - var resp *client.PayoutResponse - resp, err = moneycorpClient.GetPayout(ctx, transfer.SourceAccount.Reference, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Attributes.PaymentStatus - } - - switch status { - case "Awaiting Dispatch": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "Cleared", "Sent": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - case "Unauthorised", "Failed", "Cancelled": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, resultMessage, time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/moneycorp/task_resolve.go deleted file mode 100644 index 4be23abd3c..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/moneycorp/task_resolve.go +++ /dev/null @@ -1,72 +0,0 @@ -package moneycorp - -import ( - "fmt" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/moneycorp/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskNameMain = "main" - taskNameFetchAccounts = "fetch-accounts" - taskNameFetchRecipients = "fetch-recipients" - taskNameFetchTransactions = "fetch-transactions" - taskNameFetchBalances = "fetch-balances" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - AccountID string `json:"accountID" yaml:"accountID" bson:"accountID"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` -} - -// clientID, apiKey, endpoint string, logger logging -func resolveTasks(logger logging.Logger, config Config) func(taskDefinition TaskDescriptor) task.Task { - moneycorpClient, err := client.NewClient( - config.ClientID, - config.APIKey, - config.Endpoint, - logger, - ) - if err != nil { - logger.Error(err) - - return func(taskDescriptor TaskDescriptor) task.Task { - return func() error { - return fmt.Errorf("cannot build moneycorp client: %w", err) - } - } - } - - return func(taskDescriptor TaskDescriptor) task.Task { - switch taskDescriptor.Key { - case taskNameMain: - return taskMain() - case taskNameFetchAccounts: - return taskFetchAccounts(moneycorpClient, &config) - case taskNameFetchRecipients: - return taskFetchRecipients(moneycorpClient, taskDescriptor.AccountID) - case taskNameFetchTransactions: - return taskFetchTransactions(moneycorpClient, taskDescriptor.AccountID) - case taskNameFetchBalances: - return taskFetchBalances(moneycorpClient, taskDescriptor.AccountID) - case taskNameInitiatePayment: - return taskInitiatePayment(moneycorpClient, taskDescriptor.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(moneycorpClient, taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDescriptor.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/accounts.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/accounts.go deleted file mode 100644 index f35bfa8a86..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/accounts.go +++ /dev/null @@ -1,84 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" -) - -const ( - accountsEndpoint = "https://api.stripe.com/v1/accounts" -) - -//nolint:tagliatelle // allow different styled tags in client -type AccountsListResponse struct { - HasMore bool `json:"has_more"` - Data []*stripe.Account `json:"data"` -} - -func (d *DefaultClient) Accounts(ctx context.Context, - options ...ClientOption, -) ([]*stripe.Account, bool, error) { - f := connectors.ClientMetrics(ctx, "stripe", "list_accounts") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, accountsEndpoint, nil) - if err != nil { - return nil, false, errors.Wrap(err, "creating http request") - } - - for _, opt := range options { - opt.Apply(req) - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(d.apiKey, "") // gfyrag: really weird authentication right? - - var httpResponse *http.Response - - httpResponse, err = d.httpClient.Do(req) - if err != nil { - return nil, false, errors.Wrap(err, "doing request") - } - defer httpResponse.Body.Close() - - if httpResponse.StatusCode != http.StatusOK { - return nil, false, fmt.Errorf("unexpected status code: %d", httpResponse.StatusCode) - } - - type listResponse struct { - AccountsListResponse - Data []json.RawMessage `json:"data"` - } - - rsp := &listResponse{} - - err = json.NewDecoder(httpResponse.Body).Decode(rsp) - if err != nil { - return nil, false, errors.Wrap(err, "decoding response") - } - - accounts := make([]*stripe.Account, 0) - - if len(rsp.Data) > 0 { - for _, data := range rsp.Data { - account := &stripe.Account{} - - err = json.Unmarshal(data, &account) - if err != nil { - return nil, false, err - } - - accounts = append(accounts, account) - } - } - - return accounts, rsp.HasMore, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/balance_transactions.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/balance_transactions.go deleted file mode 100644 index 9c53d77399..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/balance_transactions.go +++ /dev/null @@ -1,88 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" -) - -const ( - balanceTransactionsEndpoint = "https://api.stripe.com/v1/balance_transactions" -) - -//nolint:tagliatelle // allow different styled tags in client -type TransactionsListResponse struct { - HasMore bool `json:"has_more"` - Data []*stripe.BalanceTransaction `json:"data"` -} - -func (d *DefaultClient) BalanceTransactions(ctx context.Context, - options ...ClientOption, -) ([]*stripe.BalanceTransaction, bool, error) { - f := connectors.ClientMetrics(ctx, "stripe", "list_balance_transactions") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, balanceTransactionsEndpoint, nil) - if err != nil { - return nil, false, errors.Wrap(err, "creating http request") - } - - for _, opt := range options { - opt.Apply(req) - } - - if d.stripeAccount != "" { - req.Header.Set("Stripe-Account", d.stripeAccount) - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(d.apiKey, "") // gfyrag: really weird authentication right? - - var httpResponse *http.Response - - httpResponse, err = d.httpClient.Do(req) - if err != nil { - return nil, false, errors.Wrap(err, "doing request") - } - defer httpResponse.Body.Close() - - if httpResponse.StatusCode != http.StatusOK { - return nil, false, fmt.Errorf("unexpected status code: %d", httpResponse.StatusCode) - } - - type listResponse struct { - TransactionsListResponse - Data []json.RawMessage `json:"data"` - } - - rsp := &listResponse{} - - err = json.NewDecoder(httpResponse.Body).Decode(rsp) - if err != nil { - return nil, false, errors.Wrap(err, "decoding response") - } - - asBalanceTransactions := make([]*stripe.BalanceTransaction, 0) - - if len(rsp.Data) > 0 { - for _, data := range rsp.Data { - asBalanceTransaction := &stripe.BalanceTransaction{} - - err = json.Unmarshal(data, &asBalanceTransaction) - if err != nil { - return nil, false, err - } - - asBalanceTransactions = append(asBalanceTransactions, asBalanceTransaction) - } - } - - return asBalanceTransactions, rsp.HasMore, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/balances.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/balances.go deleted file mode 100644 index 651768f630..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/balances.go +++ /dev/null @@ -1,67 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" -) - -const ( - balanceEndpoint = "https://api.stripe.com/v1/balance" -) - -type BalanceResponse struct { - *stripe.Balance -} - -func (d *DefaultClient) Balance(ctx context.Context, options ...ClientOption) (*stripe.Balance, error) { - f := connectors.ClientMetrics(ctx, "stripe", "get_balance") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, balanceEndpoint, nil) - if err != nil { - return nil, errors.Wrap(err, "creating http request") - } - - for _, opt := range options { - opt.Apply(req) - } - - if d.stripeAccount != "" { - req.Header.Set("Stripe-Account", d.stripeAccount) - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(d.apiKey, "") // gfyrag: really weird authentication right? - - var httpResponse *http.Response - httpResponse, err = d.httpClient.Do(req) - if err != nil { - return nil, errors.Wrap(err, "doing request") - } - defer httpResponse.Body.Close() - - if httpResponse.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", httpResponse.StatusCode) - } - - type balanceResponse struct { - BalanceResponse - } - - rsp := &balanceResponse{} - - err = json.NewDecoder(httpResponse.Body).Decode(rsp) - if err != nil { - return nil, errors.Wrap(err, "decoding response") - } - - return rsp.Balance, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/client.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/client.go deleted file mode 100644 index cd7dcfd7bf..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/client.go +++ /dev/null @@ -1,72 +0,0 @@ -package client - -import ( - "context" - "net/http" - "time" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - - "github.com/stripe/stripe-go/v72" -) - -type ClientOption interface { - Apply(req *http.Request) -} -type ClientOptionFn func(req *http.Request) - -func (fn ClientOptionFn) Apply(req *http.Request) { - fn(req) -} - -func QueryParam(key, value string) ClientOptionFn { - return func(req *http.Request) { - q := req.URL.Query() - q.Set(key, value) - req.URL.RawQuery = q.Encode() - } -} - -type Client interface { - Accounts(ctx context.Context, options ...ClientOption) ([]*stripe.Account, bool, error) - ExternalAccounts(ctx context.Context, options ...ClientOption) ([]*stripe.ExternalAccount, bool, error) - BalanceTransactions(ctx context.Context, options ...ClientOption) ([]*stripe.BalanceTransaction, bool, error) - Balance(ctx context.Context, options ...ClientOption) (*stripe.Balance, error) - CreateTransfer(ctx context.Context, CreateTransferRequest *CreateTransferRequest, options ...ClientOption) (*stripe.Transfer, error) - ReverseTransfer(ctx context.Context, createTransferReversalRequest *CreateTransferReversalRequest, options ...ClientOption) (*stripe.Reversal, error) - CreatePayout(ctx context.Context, createPayoutRequest *CreatePayoutRequest, options ...ClientOption) (*stripe.Payout, error) - GetPayout(ctx context.Context, payoutID string, options ...ClientOption) (*stripe.Payout, error) - ForAccount(account string) Client -} - -type DefaultClient struct { - httpClient *http.Client - apiKey string - stripeAccount string -} - -func NewDefaultClient(apiKey string) *DefaultClient { - return &DefaultClient{ - httpClient: newHTTPClient(), - apiKey: apiKey, - } -} - -func (d *DefaultClient) ForAccount(account string) Client { - cp := *d - cp.stripeAccount = account - - return &cp -} - -func newHTTPClient() *http.Client { - return &http.Client{ - Transport: otelhttp.NewTransport(http.DefaultTransport), - } -} - -var _ Client = &DefaultClient{} - -func DatePtr(t time.Time) *time.Time { - return &t -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/client_test.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/client_test.go deleted file mode 100644 index e0c74412b8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/client_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package client - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "reflect" - "sync" - "testing" - "time" - - "github.com/stripe/stripe-go/v72" -) - -type httpMockExpectation interface { - handle(t *testing.T, r *http.Request) (*http.Response, error) -} - -type httpMock struct { - t *testing.T - expectations []httpMockExpectation - mu sync.Mutex -} - -func (mock *httpMock) RoundTrip(request *http.Request) (*http.Response, error) { - mock.mu.Lock() - defer mock.mu.Unlock() - - if len(mock.expectations) == 0 { - return nil, fmt.Errorf("no more expectations") - } - - expectations := mock.expectations[0] - if len(mock.expectations) == 1 { - mock.expectations = make([]httpMockExpectation, 0) - } else { - mock.expectations = mock.expectations[1:] - } - - return expectations.handle(mock.t, request) -} - -var _ http.RoundTripper = &httpMock{} - -type HTTPExpect[REQUEST any, RESPONSE any] struct { - statusCode int - path string - method string - requestBody *REQUEST - responseBody *RESPONSE - queryParams map[string]any -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) handle(t *testing.T, request *http.Request) (*http.Response, error) { - t.Helper() - - if e.path != request.URL.Path { - return nil, fmt.Errorf("expected url was '%s', got, '%s'", e.path, request.URL.Path) - } - - if e.method != request.Method { - return nil, fmt.Errorf("expected method was '%s', got, '%s'", e.method, request.Method) - } - - if e.requestBody != nil { - body := new(REQUEST) - - err := json.NewDecoder(request.Body).Decode(body) - if err != nil { - panic(err) - } - - if !reflect.DeepEqual(*e.responseBody, *body) { - return nil, fmt.Errorf("mismatch body") - } - } - - for key, value := range e.queryParams { - qpvalue := "" - - switch value.(type) { - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - qpvalue = fmt.Sprintf("%d", value) - default: - qpvalue = fmt.Sprintf("%s", value) - } - - if rvalue := request.URL.Query().Get(key); rvalue != qpvalue { - return nil, fmt.Errorf("expected query param '%s' with value '%s', got '%s'", key, qpvalue, rvalue) - } - } - - data := make([]byte, 0) - - if e.responseBody != nil { - var err error - - data, err = json.Marshal(e.responseBody) - if err != nil { - panic(err) - } - } - - return &http.Response{ - StatusCode: e.statusCode, - Body: io.NopCloser(bytes.NewReader(data)), - ContentLength: int64(len(data)), - Request: request, - }, nil -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) Path(p string) *HTTPExpect[REQUEST, RESPONSE] { - e.path = p - - return e -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) Method(p string) *HTTPExpect[REQUEST, RESPONSE] { - e.method = p - - return e -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) Body(body *REQUEST) *HTTPExpect[REQUEST, RESPONSE] { - e.requestBody = body - - return e -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) QueryParam(key string, value any) *HTTPExpect[REQUEST, RESPONSE] { - e.queryParams[key] = value - - return e -} - -func (e *HTTPExpect[REQUEST, RESPONSE]) RespondsWith(statusCode int, - body *RESPONSE, -) *HTTPExpect[REQUEST, RESPONSE] { - e.statusCode = statusCode - e.responseBody = body - - return e -} - -func Expect[REQUEST, RESPONSE any](mock *httpMock) *HTTPExpect[REQUEST, RESPONSE] { - expectations := &HTTPExpect[REQUEST, RESPONSE]{ - queryParams: map[string]any{}, - } - - mock.mu.Lock() - defer mock.mu.Unlock() - - mock.expectations = append(mock.expectations, expectations) - - return expectations -} - -type StripeBalanceTransactionListExpect struct { - *HTTPExpect[struct{}, MockedListResponse] -} - -func (e *StripeBalanceTransactionListExpect) Path(p string) *StripeBalanceTransactionListExpect { - e.HTTPExpect.Path(p) - - return e -} - -func (e *StripeBalanceTransactionListExpect) Method(p string) *StripeBalanceTransactionListExpect { - e.HTTPExpect.Method(p) - - return e -} - -func (e *StripeBalanceTransactionListExpect) QueryParam(key string, - value any, -) *StripeBalanceTransactionListExpect { - e.HTTPExpect.QueryParam(key, value) - - return e -} - -func (e *StripeBalanceTransactionListExpect) RespondsWith(statusCode int, hasMore bool, - body ...*stripe.BalanceTransaction, -) *StripeBalanceTransactionListExpect { - e.HTTPExpect.RespondsWith(statusCode, &MockedListResponse{ - HasMore: hasMore, - Data: body, - }) - - return e -} - -func (e *StripeBalanceTransactionListExpect) StartingAfter(v string) *StripeBalanceTransactionListExpect { - e.QueryParam("starting_after", v) - - return e -} - -func (e *StripeBalanceTransactionListExpect) CreatedLte(v time.Time) *StripeBalanceTransactionListExpect { - e.QueryParam("created[lte]", v.Unix()) - - return e -} - -func (e *StripeBalanceTransactionListExpect) Limit(v int) *StripeBalanceTransactionListExpect { - e.QueryParam("limit", v) - - return e -} - -func ExpectBalanceTransactionList(mock *httpMock) *StripeBalanceTransactionListExpect { - e := Expect[struct{}, MockedListResponse](mock) - e.Path("/v1/balance_transactions").Method(http.MethodGet) - - return &StripeBalanceTransactionListExpect{ - HTTPExpect: e, - } -} - -type BalanceTransactionSource stripe.BalanceTransactionSource - -func (t *BalanceTransactionSource) MarshalJSON() ([]byte, error) { - type Aux BalanceTransactionSource - - return json.Marshal(struct { - Aux - Charge *stripe.Charge `json:"charge"` - Payout *stripe.Payout `json:"payout"` - Refund *stripe.Refund `json:"refund"` - Transfer *stripe.Transfer `json:"transfer"` - }{ - Aux: Aux(*t), - Charge: t.Charge, - Payout: t.Payout, - Refund: t.Refund, - Transfer: t.Transfer, - }) -} - -type BalanceTransaction stripe.BalanceTransaction - -func (t *BalanceTransaction) MarshalJSON() ([]byte, error) { - type Aux BalanceTransaction - - return json.Marshal(struct { - Aux - Source *BalanceTransactionSource `json:"source"` - }{ - Aux: Aux(*t), - Source: (*BalanceTransactionSource)(t.Source), - }) -} - -//nolint:tagliatelle // allow snake_case in client -type MockedListResponse struct { - HasMore bool `json:"has_more"` - Data []*stripe.BalanceTransaction `json:"data"` -} - -func (t *MockedListResponse) MarshalJSON() ([]byte, error) { - type Aux MockedListResponse - - txs := make([]*BalanceTransaction, 0) - for _, tx := range t.Data { - txs = append(txs, (*BalanceTransaction)(tx)) - } - - return json.Marshal(struct { - Aux - Data []*BalanceTransaction `json:"data"` - }{ - Aux: Aux(*t), - Data: txs, - }) -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/external_accounts.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/external_accounts.go deleted file mode 100644 index cd5ff3bb57..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/external_accounts.go +++ /dev/null @@ -1,81 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" -) - -const ( - externalAccountsEndpoint = "https://api.stripe.com/v1/accounts/%s/external_accounts" -) - -func (d *DefaultClient) ExternalAccounts(ctx context.Context, options ...ClientOption) ([]*stripe.ExternalAccount, bool, error) { - f := connectors.ClientMetrics(ctx, "stripe", "list_external_accounts") - now := time.Now() - defer f(ctx, now) - - if d.stripeAccount == "" { - return nil, false, errors.New("stripe account is required") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(externalAccountsEndpoint, d.stripeAccount), nil) - if err != nil { - return nil, false, errors.Wrap(err, "creating http request") - } - - for _, opt := range options { - opt.Apply(req) - } - - req.Header.Set("Stripe-Account", d.stripeAccount) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth(d.apiKey, "") // gfyrag: really weird authentication right? - - var httpResponse *http.Response - - httpResponse, err = d.httpClient.Do(req) - if err != nil { - return nil, false, errors.Wrap(err, "doing request") - } - defer httpResponse.Body.Close() - - if httpResponse.StatusCode != http.StatusOK { - return nil, false, fmt.Errorf("unexpected status code: %d", httpResponse.StatusCode) - } - - type listResponse struct { - TransactionsListResponse - Data []json.RawMessage `json:"data"` - } - - rsp := &listResponse{} - - err = json.NewDecoder(httpResponse.Body).Decode(rsp) - if err != nil { - return nil, false, errors.Wrap(err, "decoding response") - } - - externalAccounts := make([]*stripe.ExternalAccount, 0) - - if len(rsp.Data) > 0 { - for _, data := range rsp.Data { - account := &stripe.ExternalAccount{} - - err = json.Unmarshal(data, &account) - if err != nil { - return nil, false, err - } - - externalAccounts = append(externalAccounts, account) - } - } - - return externalAccounts, rsp.HasMore, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/payouts.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/payouts.go deleted file mode 100644 index 611f6634ee..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/payouts.go +++ /dev/null @@ -1,71 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/payout" -) - -type CreatePayoutRequest struct { - IdempotencyKey string - Amount int64 - Currency string - Destination string - Description string -} - -func (d *DefaultClient) CreatePayout(ctx context.Context, createPayoutRequest *CreatePayoutRequest, options ...ClientOption) (*stripe.Payout, error) { - f := connectors.ClientMetrics(ctx, "stripe", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - stripe.Key = d.apiKey - - params := &stripe.PayoutParams{ - Params: stripe.Params{ - Context: ctx, - }, - Amount: stripe.Int64(createPayoutRequest.Amount), - Currency: stripe.String(createPayoutRequest.Currency), - Destination: stripe.String(createPayoutRequest.Destination), - Method: stripe.String("standard"), - } - - if d.stripeAccount != "" { - params.SetStripeAccount(d.stripeAccount) - } - - if createPayoutRequest.IdempotencyKey != "" { - params.IdempotencyKey = stripe.String(createPayoutRequest.IdempotencyKey) - } - - if createPayoutRequest.Description != "" { - params.Description = stripe.String(createPayoutRequest.Description) - } - - payoutResponse, err := payout.New(params) - if err != nil { - return nil, errors.Wrap(err, "creating transfer") - } - - return payoutResponse, nil -} - -func (d *DefaultClient) GetPayout(ctx context.Context, payoutID string, options ...ClientOption) (*stripe.Payout, error) { - f := connectors.ClientMetrics(ctx, "stripe", "get_payout") - now := time.Now() - defer f(ctx, now) - - stripe.Key = d.apiKey - - payoutResponse, err := payout.Get(payoutID, nil) - if err != nil { - return nil, errors.Wrap(err, "getting payout") - } - - return payoutResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/transfer_reversal.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/transfer_reversal.go deleted file mode 100644 index 192281f3d2..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/transfer_reversal.go +++ /dev/null @@ -1,46 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/reversal" -) - -type CreateTransferReversalRequest struct { - TransferID string - Amount int64 - Description string - Metadata map[string]string -} - -func (d *DefaultClient) ReverseTransfer(ctx context.Context, createTransferReversalRequest *CreateTransferReversalRequest, options ...ClientOption) (*stripe.Reversal, error) { - f := connectors.ClientMetrics(ctx, "stripe", "reverse_transfer") - now := time.Now() - defer f(ctx, now) - - stripe.Key = d.apiKey - - params := &stripe.ReversalParams{ - Params: stripe.Params{ - Context: ctx, - Metadata: createTransferReversalRequest.Metadata, - }, - Transfer: stripe.String(createTransferReversalRequest.TransferID), - Amount: stripe.Int64(createTransferReversalRequest.Amount), - Description: stripe.String(createTransferReversalRequest.Description), - } - - if d.stripeAccount != "" { - params.SetStripeAccount(d.stripeAccount) - } - - reversalResponse, err := reversal.New(params) - if err != nil { - return nil, err - } - - return reversalResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/client/transfers.go b/components/payments/cmd/connectors/internal/connectors/stripe/client/transfers.go deleted file mode 100644 index 3813e99c78..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/client/transfers.go +++ /dev/null @@ -1,51 +0,0 @@ -package client - -import ( - "context" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/transfer" -) - -type CreateTransferRequest struct { - IdempotencyKey string - Amount int64 - Currency string - Destination string - Description string -} - -func (d *DefaultClient) CreateTransfer(ctx context.Context, createTransferRequest *CreateTransferRequest, options ...ClientOption) (*stripe.Transfer, error) { - f := connectors.ClientMetrics(ctx, "stripe", "initiate_transfer") - now := time.Now() - defer f(ctx, now) - - stripe.Key = d.apiKey - - params := &stripe.TransferParams{ - Params: stripe.Params{ - Context: ctx, - }, - Amount: stripe.Int64(createTransferRequest.Amount), - Currency: stripe.String(createTransferRequest.Currency), - Destination: stripe.String(createTransferRequest.Destination), - } - - if d.stripeAccount != "" { - params.SetStripeAccount(d.stripeAccount) - } - - if createTransferRequest.IdempotencyKey != "" { - params.IdempotencyKey = stripe.String(createTransferRequest.IdempotencyKey) - } - - transferResponse, err := transfer.New(params) - if err != nil { - return nil, errors.Wrap(err, "creating transfer") - } - - return transferResponse, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/config.go b/components/payments/cmd/connectors/internal/connectors/stripe/config.go deleted file mode 100644 index fdcf01e17f..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/config.go +++ /dev/null @@ -1,66 +0,0 @@ -package stripe - -import ( - "encoding/json" - "errors" - "fmt" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -const ( - defaultPageSize = 10 - defaultPollingPeriod = 2 * time.Minute -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - TimelineConfig `bson:",inline"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return fmt.Sprintf("pollingPeriod=%s, pageSize=%d, apiKey=****", c.PollingPeriod, c.PageSize) -} - -func (c Config) Validate() error { - if c.APIKey == "" { - return errors.New("missing api key") - } - - if c.Name == "" { - return errors.New("missing name") - } - - return nil -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -type TimelineConfig struct { - PageSize uint64 `json:"pageSize" yaml:"pageSize" bson:"pageSize"` -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - cfg.AddParameter("pageSize", configtemplate.TypeDurationUnsignedInteger, strconv.Itoa(defaultPageSize), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/connector.go b/components/payments/cmd/connectors/internal/connectors/stripe/connector.go deleted file mode 100644 index 39c2666bba..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/connector.go +++ /dev/null @@ -1,153 +0,0 @@ -package stripe - -import ( - "context" - "errors" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" -) - -const name = models.ConnectorProviderStripe - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Main task to periodically fetch transactions", - Main: true, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return c.resolveTasks()(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - // Detach the context since we're launching an async task and we're mostly - // coming from a HTTP request. - detachedCtx, _ := contextutil.Detached(ctx.Context()) - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Reverse payment", - Key: taskNameReversePayment, - TransferReversalID: transferReversal.ID.String(), - }) - if err != nil { - return err - } - - err = ctx.Scheduler().Schedule(detachedCtx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW_SYNC, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/currencies.go b/components/payments/cmd/connectors/internal/connectors/stripe/currencies.go deleted file mode 100644 index 4e6518ec52..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/currencies.go +++ /dev/null @@ -1,162 +0,0 @@ -package stripe - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // c.f. https://stripe.com/docs/currencies#zero-decimal - supportedCurrenciesWithDecimal = map[string]int{ - "USD": currency.ISO4217Currencies["USD"], // United States dollar - "AED": currency.ISO4217Currencies["AED"], // United Arab Emirates dirham - "AFN": currency.ISO4217Currencies["AFN"], // Afghan afghani - "ALL": currency.ISO4217Currencies["ALL"], // Albanian lek - "AMD": currency.ISO4217Currencies["AMD"], // Armenian dram - "ANG": currency.ISO4217Currencies["ANG"], // Netherlands Antillean guilder - "AOA": currency.ISO4217Currencies["AOA"], // Angolan kwanza - "ARS": currency.ISO4217Currencies["ARS"], // Argentine peso - "AUD": currency.ISO4217Currencies["AUD"], // Australian dollar - "AWG": currency.ISO4217Currencies["AWG"], // Aruban florin - "AZN": currency.ISO4217Currencies["AZN"], // Azerbaijani manat - "BAM": currency.ISO4217Currencies["BAM"], // Bosnia and Herzegovina convertible mark - "BBD": currency.ISO4217Currencies["BBD"], // Barbados dollar - "BDT": currency.ISO4217Currencies["BDT"], // Bangladeshi taka - "BGN": currency.ISO4217Currencies["BGN"], // Bulgarian lev - "BIF": currency.ISO4217Currencies["BIF"], // Burundian franc - "BMD": currency.ISO4217Currencies["BMD"], // Bermudian dollar - "BND": currency.ISO4217Currencies["BND"], // Brunei dollar - "BOB": currency.ISO4217Currencies["BOB"], // Bolivian boliviano - "BRL": currency.ISO4217Currencies["BRL"], // Brazilian real - "BSD": currency.ISO4217Currencies["BSD"], // Bahamian dollar - "BWP": currency.ISO4217Currencies["BWP"], // Botswana pula - "BYN": currency.ISO4217Currencies["BYN"], // Belarusian ruble - "BZD": currency.ISO4217Currencies["BZD"], // Belize dollar - "CAD": currency.ISO4217Currencies["CAD"], // Canadian dollar - "CDF": currency.ISO4217Currencies["CDF"], // Congolese franc - "CHF": currency.ISO4217Currencies["CHF"], // Swiss franc - "CLP": currency.ISO4217Currencies["CLP"], // Chilean peso - "CNY": currency.ISO4217Currencies["CNY"], // Chinese yuan - "COP": currency.ISO4217Currencies["COP"], // Colombian peso - "CRC": currency.ISO4217Currencies["CRC"], // Costa Rican colon - "CVE": currency.ISO4217Currencies["CVE"], // Cape Verdean escudo - "CZK": currency.ISO4217Currencies["CZK"], // Czech koruna - "DJF": currency.ISO4217Currencies["DJF"], // Djiboutian franc - "DKK": currency.ISO4217Currencies["DKK"], // Danish krone - "DOP": currency.ISO4217Currencies["DOP"], // Dominican peso - "DZD": currency.ISO4217Currencies["DZD"], // Algerian dinar - "EGP": currency.ISO4217Currencies["EGP"], // Egyptian pound - "ETB": currency.ISO4217Currencies["ETB"], // Ethiopian birr - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "FJD": currency.ISO4217Currencies["FJD"], // Fiji dollar - "FKP": currency.ISO4217Currencies["FKP"], // Falkland Islands pound - "GBP": currency.ISO4217Currencies["GBP"], // Pound sterling - "GEL": currency.ISO4217Currencies["GEL"], // Georgian lari - "GIP": currency.ISO4217Currencies["GIP"], // Gibraltar pound - "GMD": currency.ISO4217Currencies["GMD"], // Gambian dalasi - "GNF": currency.ISO4217Currencies["GNF"], // Guinean franc - "GTQ": currency.ISO4217Currencies["GTQ"], // Guatemalan quetzal - "GYD": currency.ISO4217Currencies["GYD"], // Guyanese dollar - "HKD": currency.ISO4217Currencies["HKD"], // Hong Kong dollar - "HNL": currency.ISO4217Currencies["HNL"], // Honduran lempira - "HTG": currency.ISO4217Currencies["HTG"], // Haitian gourde - "IDR": currency.ISO4217Currencies["IDR"], // Indonesian rupiah - "ILS": currency.ISO4217Currencies["ILS"], // Israeli new shekel - "INR": currency.ISO4217Currencies["INR"], // Indian rupee - "JMD": currency.ISO4217Currencies["JMD"], // Jamaican dollar - "JPY": currency.ISO4217Currencies["JPY"], // Japanese yen - "KES": currency.ISO4217Currencies["KES"], // Kenyan shilling - "KGS": currency.ISO4217Currencies["KGS"], // Kyrgyzstani som - "KHR": currency.ISO4217Currencies["KHR"], // Cambodian riel - "KMF": currency.ISO4217Currencies["KMF"], // Comoro franc - "KRW": currency.ISO4217Currencies["KRW"], // South Korean won - "KYD": currency.ISO4217Currencies["KYD"], // Cayman Islands dollar - "KZT": currency.ISO4217Currencies["KZT"], // Kazakhstani tenge - "LAK": currency.ISO4217Currencies["LAK"], // Lao kip - "LBP": currency.ISO4217Currencies["LBP"], // Lebanese pound - "LKR": currency.ISO4217Currencies["LKR"], // Sri Lankan rupee - "LRD": currency.ISO4217Currencies["LRD"], // Liberian dollar - "LSL": currency.ISO4217Currencies["LSL"], // Lesotho loti - "MAD": currency.ISO4217Currencies["MAD"], // Moroccan dirham - "MDL": currency.ISO4217Currencies["MDL"], // Moldovan leu - "MKD": currency.ISO4217Currencies["MKD"], // Macedonian denar - "MMK": currency.ISO4217Currencies["MMK"], // Burmese kyat - "MNT": currency.ISO4217Currencies["MNT"], // Mongolian tögrög - "MOP": currency.ISO4217Currencies["MOP"], // Macanese pataca - "MUR": currency.ISO4217Currencies["MUR"], // Mauritian rupee - "MVR": currency.ISO4217Currencies["MVR"], // Maldivian rufiyaa - "MWK": currency.ISO4217Currencies["MWK"], // Malawian kwacha - "MXN": currency.ISO4217Currencies["MXN"], // Mexican peso - "MYR": currency.ISO4217Currencies["MYR"], // Malaysian ringgit - "MZN": currency.ISO4217Currencies["MZN"], // Mozambican metical - "NAD": currency.ISO4217Currencies["NAD"], // Namibian dollar - "NGN": currency.ISO4217Currencies["NGN"], // Nigerian naira - "NIO": currency.ISO4217Currencies["NIO"], // Nicaraguan córdoba - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian krone - "NPR": currency.ISO4217Currencies["NPR"], // Nepalese rupee - "NZD": currency.ISO4217Currencies["NZD"], // New Zealand dollar - "PAB": currency.ISO4217Currencies["PAB"], // Panamanian balboa - "PEN": currency.ISO4217Currencies["PEN"], // Peruvian sol - "PGK": currency.ISO4217Currencies["PGK"], // Papua New Guinean kina - "PHP": currency.ISO4217Currencies["PHP"], // Philippine peso - "PKR": currency.ISO4217Currencies["PKR"], // Pakistani rupee - "PLN": currency.ISO4217Currencies["PLN"], // Polish złoty - "PYG": currency.ISO4217Currencies["PYG"], // Paraguayan guaraní - "QAR": currency.ISO4217Currencies["QAR"], // Qatari riyal - "RON": currency.ISO4217Currencies["RON"], // Romanian leu - "RSD": currency.ISO4217Currencies["RSD"], // Serbian dinar - "RUB": currency.ISO4217Currencies["RUB"], // Russian ruble - "RWF": currency.ISO4217Currencies["RWF"], // Rwandan franc - "SAR": currency.ISO4217Currencies["SAR"], // Saudi riyal - "SBD": currency.ISO4217Currencies["SBD"], // Solomon Islands dollar - "SCR": currency.ISO4217Currencies["SCR"], // Seychelles rupee - "SEK": currency.ISO4217Currencies["SEK"], // Swedish krona/kronor - "SGD": currency.ISO4217Currencies["SGD"], // Singapore dollar - "SHP": currency.ISO4217Currencies["SHP"], // Saint Helena pound - "SOS": currency.ISO4217Currencies["SOS"], // Somali shilling - "SRD": currency.ISO4217Currencies["SRD"], // Surinamese dollar - "SZL": currency.ISO4217Currencies["SZL"], // Swazi lilangeni - "THB": currency.ISO4217Currencies["THB"], // Thai baht - "TJS": currency.ISO4217Currencies["TJS"], // Tajikistani somoni - "TOP": currency.ISO4217Currencies["TOP"], // Tongan paʻanga - "TRY": currency.ISO4217Currencies["TRY"], // Turkish lira - "TTD": currency.ISO4217Currencies["TTD"], // Trinidad and Tobago dollar - "TZS": currency.ISO4217Currencies["TZS"], // Tanzanian shilling - "UAH": currency.ISO4217Currencies["UAH"], // Ukrainian hryvnia - "UYU": currency.ISO4217Currencies["UYU"], // Uruguayan peso - "UZS": currency.ISO4217Currencies["UZS"], // Uzbekistan som - "VND": currency.ISO4217Currencies["VND"], // Vietnamese đồng - "VUV": currency.ISO4217Currencies["VUV"], // Vanuatu vatu - "WST": currency.ISO4217Currencies["WST"], // Samoan tala - "XAF": currency.ISO4217Currencies["XAF"], // Central African CFA franc - "XCD": currency.ISO4217Currencies["XCD"], // East Caribbean dollar - "XOF": currency.ISO4217Currencies["XOF"], // West African CFA franc - "XPF": currency.ISO4217Currencies["XPF"], // CFP franc - "YER": currency.ISO4217Currencies["YER"], // Yemeni rial - "ZAR": currency.ISO4217Currencies["ZAR"], // South African rand - "ZMW": currency.ISO4217Currencies["ZMW"], // Zambian kwacha - - // Unsupported currencies - // The following currencies are not in the ISO 4217 standard, - //so let's not handle them for now. - // "SLE": 2 // Sierra Leonean leone - // "STD": 2 // São Tomé and Príncipe dobra - - // The following currencies have not the same decimals in Stripe compared - // to ISO 4217 standard, so let's not handle them for now. - // "MGA": 2, // Malagasy ariary - - // The following currencies are 3 decimals currencies, but in order - // to use them with Stripe, it requires the last digit to be 0. - // Let's not handle them for now. - // "BHD": 3, // Bahraini dinar - // "JOD": 3, // Jordanian dinar - // "KWD": 3, // Kuwaiti dinar - // "OMR": 3, // Omani rial - // "TND": 3, // Tunisian dinar - - // The following currencies are apecial cases in stripe API (cf link above) - // let's not handle them for now. - // "ISK": 0, // Icelandic króna - // "HUF": 2, // Hungarian forint - // "UGX": 0, // Ugandan shilling - // "TWD": 2 // New Taiwan dollar - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/ingester.go b/components/payments/cmd/connectors/internal/connectors/stripe/ingester.go deleted file mode 100644 index ebbead8734..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/ingester.go +++ /dev/null @@ -1,43 +0,0 @@ -package stripe - -import ( - "context" - - "github.com/stripe/stripe-go/v72" -) - -type ingestTransaction func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error -type ingestAccounts func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error -type ingestExternalAccounts func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error - -type Ingester interface { - IngestTransactions(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error - IngestAccounts(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error - IngestExternalAccounts(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error -} - -type ingester struct { - it ingestTransaction - ia ingestAccounts - iea ingestExternalAccounts -} - -func NewIngester(it ingestTransaction, ia ingestAccounts, iea ingestExternalAccounts) Ingester { - return &ingester{ - it: it, - ia: ia, - iea: iea, - } -} - -func (i *ingester) IngestTransactions(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - return i.it(ctx, batch, commitState, tail) -} - -func (i *ingester) IngestAccounts(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return i.ia(ctx, batch, commitState, tail) -} - -func (i *ingester) IngestExternalAccounts(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return i.iea(ctx, batch, commitState, tail) -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/loader.go b/components/payments/cmd/connectors/internal/connectors/stripe/loader.go deleted file mode 100644 index 6cb25a59d2..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/loader.go +++ /dev/null @@ -1,51 +0,0 @@ -package stripe - -import ( - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PageSize == 0 { - cfg.PageSize = defaultPageSize - } - - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod = connectors.Duration{Duration: defaultPollingPeriod} - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/state.go b/components/payments/cmd/connectors/internal/connectors/stripe/state.go deleted file mode 100644 index 9ae3edbc62..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/state.go +++ /dev/null @@ -1,11 +0,0 @@ -package stripe - -import "time" - -type TimelineState struct { - OldestID string `bson:"oldestID,omitempty" json:"oldestID"` - OldestDate *time.Time `bson:"oldestDate,omitempty" json:"oldestDate"` - MoreRecentID string `bson:"moreRecentID,omitempty" json:"moreRecentID"` - MoreRecentDate *time.Time `bson:"moreRecentDate,omitempty" json:"moreRecentDate"` - NoMoreHistory bool `bson:"noMoreHistory" json:"noMoreHistory"` -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_accounts.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_accounts.go deleted file mode 100644 index 80fa20b90b..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_accounts.go +++ /dev/null @@ -1,212 +0,0 @@ -package stripe - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" - "go.opentelemetry.io/otel/attribute" -) - -const ( - rootAccountReference = "root" -) - -func fetchAccountsTask(config TimelineConfig, client *client.DefaultClient) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - resolver task.StateResolver, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.fetchAccountsTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - // Register root account. - if err := registerRootAccount(ctx, connectorID, ingester, scheduler); err != nil { - otel.RecordError(span, err) - return err - } - - tt := NewTimelineTrigger( - logger, - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - return nil - - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - if err := ingestAccountsBatch(ctx, connectorID, ingester, batch); err != nil { - return err - } - - for _, account := range batch { - transactionsTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transactions for a specific connected account", - Key: taskNameFetchPaymentsForAccounts, - Account: account.ID, - }) - if err != nil { - return errors.Wrap(err, "failed to transform task descriptor") - } - - err = scheduler.Schedule(ctx, transactionsTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(err, "scheduling connected account") - } - - balanceTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch balance for a specific connected account", - Key: taskNameFetchBalances, - Account: account.ID, - }) - if err != nil { - return errors.Wrap(err, "failed to transform task descriptor") - } - - err = scheduler.Schedule(ctx, balanceTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(err, "scheduling connected account") - } - - externalAccountsTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch external account for a specific connected account", - Key: taskNameFetchExternalAccounts, - Account: account.ID, - }) - if err != nil { - return errors.Wrap(err, "failed to transform task descriptor") - } - - err = scheduler.Schedule(ctx, externalAccountsTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(err, "scheduling connected account") - } - } - - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return nil - - }, - ), - NewTimeline(client, - config, task.MustResolveTo(ctx, resolver, TimelineState{})), - TimelineTriggerTypeAccounts, - ) - - if err := tt.Fetch(ctx); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func registerRootAccount( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, -) error { - if err := ingester.IngestAccounts(ctx, ingestion.AccountBatch{ - { - ID: models.AccountID{ - Reference: rootAccountReference, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Now().UTC(), - Reference: rootAccountReference, - Type: models.AccountTypeInternal, - }, - }); err != nil { - return err - } - balanceTask, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch balance for the root account", - Key: taskNameFetchBalances, - Account: rootAccountReference, - }) - if err != nil { - return errors.Wrap(err, "failed to transform task descriptor") - } - err = scheduler.Schedule(ctx, balanceTask, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return errors.Wrap(err, "scheduling connected account") - } - - return nil -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []*stripe.Account, -) error { - batch := ingestion.AccountBatch{} - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - metadata := make(map[string]string) - for k, v := range account.Metadata { - metadata[k] = v - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(account.Created, 0).UTC(), - Reference: account.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(account.DefaultCurrency)), - Type: models.AccountTypeInternal, - RawData: raw, - Metadata: metadata, - }) - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_balances.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_balances.go deleted file mode 100644 index c54230af82..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_balances.go +++ /dev/null @@ -1,72 +0,0 @@ -package stripe - -import ( - "context" - "math/big" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func balanceTask(account string, client *client.DefaultClient) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.balanceTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("account", account), - ) - defer span.End() - - stripeAccount := account - if account == rootAccountReference { - // special case for root account - stripeAccount = "" - } - - balances, err := client.ForAccount(stripeAccount).Balance(ctx) - if err != nil { - otel.RecordError(span, err) - return err - } - - batch := ingestion.BalanceBatch{} - for _, balance := range balances.Available { - timestamp := time.Now() - batch = append(batch, &models.Balance{ - AccountID: models.AccountID{ - Reference: account, - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balance.Currency)), - Balance: big.NewInt(balance.Value), - CreatedAt: timestamp, - LastUpdatedAt: timestamp, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestBalances(ctx, batch, false); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_external_accounts.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_external_accounts.go deleted file mode 100644 index 2810cd3e33..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_external_accounts.go +++ /dev/null @@ -1,102 +0,0 @@ -package stripe - -import ( - "context" - "encoding/json" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/stripe/stripe-go/v72" - "go.opentelemetry.io/otel/attribute" -) - -func fetchExternalAccountsTask(config TimelineConfig, account string, client *client.DefaultClient) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - resolver task.StateResolver, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.fetchExternalAccountsTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("account", account), - ) - defer span.End() - - tt := NewTimelineTrigger( - logger, - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - return nil - - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - if err := ingestExternalAccountsBatch(ctx, connectorID, ingester, batch); err != nil { - return err - } - return nil - }, - ), - NewTimeline(client.ForAccount(account), - config, task.MustResolveTo(ctx, resolver, TimelineState{})), - TimelineTriggerTypeExternalAccounts, - ) - - if err := tt.Fetch(ctx); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func ingestExternalAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []*stripe.ExternalAccount, -) error { - batch := ingestion.AccountBatch{} - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return err - } - - batch = append(batch, &models.Account{ - ID: models.AccountID{ - Reference: account.ID, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(account.BankAccount.Account.Created, 0).UTC(), - Reference: account.ID, - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(account.BankAccount.Account.DefaultCurrency)), - Type: models.AccountTypeExternal, - RawData: raw, - }) - } - - if err := ingester.IngestAccounts(ctx, batch); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments.go deleted file mode 100644 index c144dc734d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments.go +++ /dev/null @@ -1,65 +0,0 @@ -package stripe - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/stripe/stripe-go/v72" - "go.opentelemetry.io/otel/attribute" -) - -func fetchPaymentsTask(config TimelineConfig, client *client.DefaultClient) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - resolver task.StateResolver, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.fetchPaymentsTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("account", rootAccountReference), - ) - defer span.End() - - tt := NewTimelineTrigger( - logger, - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - if err := ingestBatch(ctx, connectorID, rootAccountReference, logger, ingester, batch, commitState, tail); err != nil { - return err - } - - return nil - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return nil - }, - ), - NewTimeline(client, - config, task.MustResolveTo(ctx, resolver, TimelineState{})), - TimelineTriggerTypeTransactions, - ) - - if err := tt.Fetch(ctx); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments_for_connected_account.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments_for_connected_account.go deleted file mode 100644 index fe55592619..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_fetch_payments_for_connected_account.go +++ /dev/null @@ -1,109 +0,0 @@ -package stripe - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/stripe/stripe-go/v72" - "go.opentelemetry.io/otel/attribute" -) - -func ingestBatch( - ctx context.Context, - connectorID models.ConnectorID, - account string, - logger logging.Logger, - ingester ingestion.Ingester, - bts []*stripe.BalanceTransaction, - commitState TimelineState, - tail bool, -) error { - batch := ingestion.PaymentBatch{} - - for i := range bts { - batchElement, handled := createBatchElement(connectorID, bts[i], account, !tail) - - if !handled { - logger.Debugf("Balance transaction type not handled: %s", bts[i].Type) - - continue - } - - if batchElement.Adjustment == nil && batchElement.Payment == nil { - continue - } - - batch = append(batch, batchElement) - } - - logger.WithFields(map[string]interface{}{ - "state": commitState, - }).Debugf("updating state") - - err := ingester.IngestPayments(ctx, batch) - if err != nil { - return err - } - - err = ingester.UpdateTaskState(ctx, commitState) - if err != nil { - return err - } - - return nil -} - -func connectedAccountTask(config TimelineConfig, account string, client *client.DefaultClient) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.connectedAccountTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("account", account), - ) - defer span.End() - - trigger := NewTimelineTrigger( - logger, - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - if err := ingestBatch(ctx, connectorID, account, logger, ingester, batch, commitState, tail); err != nil { - return err - } - - return nil - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return nil - }, - ), - NewTimeline(client. - ForAccount(account), config, task.MustResolveTo(ctx, resolver, TimelineState{})), - TimelineTriggerTypeTransactions, - ) - - if err := trigger.Fetch(ctx); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_main.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_main.go deleted file mode 100644 index 5cf7de4632..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_main.go +++ /dev/null @@ -1,68 +0,0 @@ -package stripe - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// Launch accounts and payments tasks -func (c *Connector) mainTask() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "stripe.mainTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskAccounts, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch accounts from client", - Key: taskNameFetchAccounts, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskAccounts, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - taskPayments, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch payments from client", - Key: taskNameFetchPayments, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskPayments, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_payments.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_payments.go deleted file mode 100644 index d0e445de09..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_payments.go +++ /dev/null @@ -1,309 +0,0 @@ -package stripe - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/stripe/stripe-go/v72" - "go.opentelemetry.io/otel/attribute" -) - -const ( - transferIDKey string = "transfer_id" -) - -func initiatePaymentTask(transferID string, stripeClient *client.DefaultClient) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "stripe.initiatePaymentTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, stripeClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - stripeClient *client.DefaultClient, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount != nil { - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - } - - var curr string - curr, _, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - c := client.Client(stripeClient) - // If source account is nil, or equal to root (which is a special - // account we create for stripe for the balance platform), we don't need - // to set the stripe account. - if transfer.SourceAccount != nil && transfer.SourceAccount.Reference != rootAccountReference { - c = c.ForAccount(transfer.SourceAccountID.Reference) - } - - var connectorPaymentID string - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - var resp *stripe.Transfer - resp, err = c.CreateTransfer(ctx, &client.CreateTransferRequest{ - IdempotencyKey: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - Amount: transfer.Amount.Int64(), - Currency: curr, - Destination: transfer.DestinationAccountID.Reference, - Description: transfer.Description, - }) - if err != nil { - return err - } - - if transfer.Metadata == nil { - transfer.Metadata = make(map[string]string) - } - transfer.Metadata[transferIDKey] = resp.ID - connectorPaymentID = resp.BalanceTransaction.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - var resp *stripe.Payout - resp, err = c.CreatePayout(ctx, &client.CreatePayoutRequest{ - IdempotencyKey: fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments)), - Amount: transfer.Amount.Int64(), - Currency: curr, - Destination: transfer.DestinationAccountID.Reference, - Description: transfer.Description, - }) - if err != nil { - return err - } - - connectorPaymentID = resp.BalanceTransaction.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: connectorPaymentID, - Type: paymentType, - }, - ConnectorID: connectorID, - } - - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func updatePaymentStatusTask( - transferID string, - pID string, - attempt int, - stripeClient *client.DefaultClient, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "stripe.updatePaymentStatusTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", pID), - attribute.Int("attempt", attempt), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, stripeClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - stripeClient *client.DefaultClient, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status stripe.PayoutFailureCode - var resultMessage string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - // Nothing to do - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - - case models.TransferInitiationTypePayout: - var resp *stripe.Payout - resp, err = stripeClient.GetPayout(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.FailureCode - resultMessage = resp.FailureMessage - } - - if status == "" { - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - } - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, resultMessage, time.Now()) - if err != nil { - return err - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_resolve.go deleted file mode 100644 index dbdc8814e4..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_resolve.go +++ /dev/null @@ -1,62 +0,0 @@ -package stripe - -import ( - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskNameFetchAccounts = "fetch_accounts" - taskNameFetchPaymentsForAccounts = "fetch_transactions" - taskNameFetchPayments = "fetch_payments" - taskNameFetchBalances = "fetch_balance" - taskNameFetchExternalAccounts = "fetch_external_accounts" - taskNameInitiatePayment = "initiate-payment" - taskNameReversePayment = "reverse-payment" - taskNameUpdatePaymentStatus = "update-payment-status" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - Main bool `json:"main,omitempty" yaml:"main" bson:"main"` - Account string `json:"account,omitempty" yaml:"account" bson:"account"` - TransferID string `json:"transferID,omitempty" yaml:"transferID" bson:"transferID"` - TransferReversalID string `json:"transferReversalID,omitempty" yaml:"transferReversalID" bson:"transferReversalID"` - PaymentID string `json:"paymentID,omitempty" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt,omitempty" yaml:"attempt" bson:"attempt"` -} - -// clientID, apiKey, endpoint string, logger logging -func (c *Connector) resolveTasks() func(taskDefinition TaskDescriptor) task.Task { - client := client.NewDefaultClient(c.cfg.APIKey) - - return func(taskDescriptor TaskDescriptor) task.Task { - if taskDescriptor.Main { - return c.mainTask() - } - - switch taskDescriptor.Key { - case taskNameFetchPayments: - return fetchPaymentsTask(c.cfg.TimelineConfig, client) - case taskNameFetchAccounts: - return fetchAccountsTask(c.cfg.TimelineConfig, client) - case taskNameFetchExternalAccounts: - return fetchExternalAccountsTask(c.cfg.TimelineConfig, taskDescriptor.Account, client) - case taskNameFetchPaymentsForAccounts: - return connectedAccountTask(c.cfg.TimelineConfig, taskDescriptor.Account, client) - case taskNameFetchBalances: - return balanceTask(taskDescriptor.Account, client) - case taskNameInitiatePayment: - return initiatePaymentTask(taskDescriptor.TransferID, client) - case taskNameReversePayment: - return reversePaymentTask(taskDescriptor.TransferReversalID, client) - case taskNameUpdatePaymentStatus: - return updatePaymentStatusTask(taskDescriptor.TransferID, taskDescriptor.PaymentID, taskDescriptor.Attempt, client) - default: - // For compatibility with old tasks - return connectedAccountTask(c.cfg.TimelineConfig, taskDescriptor.Account, client) - } - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/task_reverse_payment.go b/components/payments/cmd/connectors/internal/connectors/stripe/task_reverse_payment.go deleted file mode 100644 index ad927b51d8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/task_reverse_payment.go +++ /dev/null @@ -1,143 +0,0 @@ -package stripe - -import ( - "context" - "errors" - "time" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func reversePaymentTask(transferReversalID string, stripeClient *client.DefaultClient) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - reversalID := models.MustTransferReversalIDFromString(transferReversalID) - - ctx, span := connectors.StartSpan( - ctx, - "stripe.reversePaymentTask", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferReversalID", transferReversalID), - attribute.String("reference", reversalID.Reference), - ) - defer span.End() - - transferReversal, err := getTransferReversal(ctx, storageReader, reversalID) - if err != nil { - otel.RecordError(span, err) - return err - } - - transfer, err := getTransfer(ctx, storageReader, transferReversal.TransferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := reversePayment(ctx, stripeClient, transfer, transferReversal, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func reversePayment( - ctx context.Context, - stripeClient *client.DefaultClient, - transfer *models.TransferInitiation, - transferReversal *models.TransferReversal, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - - transferReversal.Status = models.TransferReversalStatusFailed - transferReversal.Error = err.Error() - transferReversal.UpdatedAt = time.Now().UTC() - - _ = ingester.UpdateTransferReversalStatus(ctx, transfer, transferReversal) - } - }() - - c := client.Client(stripeClient) - // If source account is nil, or equal to root (which is a special - // account we create for stripe for the balance platform), we don't need - // to set the stripe account. - if transfer.SourceAccount != nil && transfer.SourceAccount.Reference != rootAccountReference { - c = c.ForAccount(transfer.SourceAccountID.Reference) - } - - transferID, err := getTransferIDFromMetadata(transfer) - if err != nil { - return err - } - - _, err = c.ReverseTransfer(ctx, &client.CreateTransferReversalRequest{ - TransferID: transferID, - Amount: transferReversal.Amount.Int64(), - Description: transferReversal.Description, - Metadata: transferReversal.Metadata, - }) - if err != nil { - return err - } - - transferReversal.Status = models.TransferReversalStatusProcessed - transferReversal.UpdatedAt = time.Now().UTC() - if err = ingester.UpdateTransferReversalStatus(ctx, transfer, transferReversal); err != nil { - return err - } - - return nil -} - -func getTransferReversal( - ctx context.Context, - reader storage.Reader, - transferReversalID models.TransferReversalID, -) (*models.TransferReversal, error) { - transferReversal, err := reader.GetTransferReversal(ctx, transferReversalID) - if err != nil { - return nil, err - } - - return transferReversal, nil -} - -func getTransferIDFromMetadata( - transfer *models.TransferInitiation, -) (string, error) { - if transfer.Metadata == nil { - return "", errors.New("metadata not found") - } - - transferID, ok := transfer.Metadata[transferIDKey] - if !ok { - return "", errors.New("transfer id not found in metadata") - } - - return transferID, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline.go deleted file mode 100644 index b27a76cca0..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline.go +++ /dev/null @@ -1,54 +0,0 @@ -package stripe - -import ( - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" -) - -type Timeline struct { - state TimelineState - firstIDAfterStartingAt string - startingAt time.Time - config TimelineConfig - client client.Client -} - -func NewTimeline(client client.Client, cfg TimelineConfig, state TimelineState, options ...TimelineOption) *Timeline { - defaultOptions := make([]TimelineOption, 0) - - c := &Timeline{ - config: cfg, - state: state, - client: client, - } - - options = append(defaultOptions, append([]TimelineOption{ - WithStartingAt(time.Now()), - }, options...)...) - - for _, opt := range options { - opt.apply(c) - } - - return c -} - -type TimelineOption interface { - apply(c *Timeline) -} -type TimelineOptionFn func(c *Timeline) - -func (fn TimelineOptionFn) apply(c *Timeline) { - fn(c) -} - -func WithStartingAt(v time.Time) TimelineOptionFn { - return func(c *Timeline) { - c.startingAt = v - } -} - -func (tl *Timeline) State() TimelineState { - return tl.state -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_connected_account.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_connected_account.go deleted file mode 100644 index 3d53f23599..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_connected_account.go +++ /dev/null @@ -1,142 +0,0 @@ -package stripe - -import ( - "context" - "fmt" - "net/url" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/stripe/stripe-go/v72" -) - -func (tl *Timeline) doAccountsRequest(ctx context.Context, queryParams url.Values, - to *[]*stripe.Account, -) (bool, error) { - options := make([]client.ClientOption, 0) - options = append(options, client.QueryParam("limit", fmt.Sprintf("%d", tl.config.PageSize))) - - for k, v := range queryParams { - options = append(options, client.QueryParam(k, v[0])) - } - - txs, hasMore, err := tl.client.Accounts(ctx, options...) - if err != nil { - return false, err - } - - *to = txs - - return hasMore, nil -} - -func (tl *Timeline) initAccounts(ctx context.Context) error { - ret := make([]*stripe.Account, 0) - params := url.Values{} - params.Set("limit", "1") - params.Set("created[lt]", fmt.Sprintf("%d", tl.startingAt.Unix())) - - _, err := tl.doAccountsRequest(ctx, params, &ret) - if err != nil { - return err - } - - if len(ret) > 0 { - tl.firstIDAfterStartingAt = ret[0].ID - } - - return nil -} - -func (tl *Timeline) AccountsTail(ctx context.Context, to *[]*stripe.Account) (bool, TimelineState, func(), error) { - queryParams := url.Values{} - - switch { - case tl.state.OldestID != "": - queryParams.Set("starting_after", tl.state.OldestID) - default: - queryParams.Set("created[lte]", fmt.Sprintf("%d", tl.startingAt.Unix())) - } - - hasMore, err := tl.doAccountsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.Created, 0) - futureState.OldestDate = &oldestDate - - if futureState.MoreRecentID == "" { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - } - } - - futureState.NoMoreHistory = !hasMore - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} - -func (tl *Timeline) AccountsHead(ctx context.Context, to *[]*stripe.Account) (bool, TimelineState, func(), error) { - if tl.firstIDAfterStartingAt == "" && tl.state.MoreRecentID == "" { - err := tl.initAccounts(ctx) - if err != nil { - return false, TimelineState{}, nil, err - } - - if tl.firstIDAfterStartingAt == "" { - return false, TimelineState{ - NoMoreHistory: true, - }, func() {}, nil - } - } - - queryParams := url.Values{} - - switch { - case tl.state.MoreRecentID != "": - queryParams.Set("ending_before", tl.state.MoreRecentID) - case tl.firstIDAfterStartingAt != "": - queryParams.Set("ending_before", tl.firstIDAfterStartingAt) - } - - hasMore, err := tl.doAccountsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - - if futureState.OldestID == "" { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.Created, 0) - futureState.OldestDate = &oldestDate - } - } - - futureState.NoMoreHistory = !hasMore - - for i, j := 0, len(*to)-1; i < j; i, j = i+1, j-1 { - (*to)[i], (*to)[j] = (*to)[j], (*to)[i] - } - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_external_accounts.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_external_accounts.go deleted file mode 100644 index 251ad9e7c3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_external_accounts.go +++ /dev/null @@ -1,144 +0,0 @@ -package stripe - -import ( - "context" - "fmt" - "net/url" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/stripe/stripe-go/v72" -) - -//nolint:tagliatelle // allow different styled tags in client -type ExternalAccountsListResponse struct { - HasMore bool `json:"has_more"` - Data []*stripe.ExternalAccount `json:"data"` -} - -func (tl *Timeline) doExternalAccountsRequest(ctx context.Context, queryParams url.Values, - to *[]*stripe.ExternalAccount, -) (bool, error) { - options := make([]client.ClientOption, 0) - options = append(options, client.QueryParam("limit", fmt.Sprintf("%d", tl.config.PageSize))) - - for k, v := range queryParams { - options = append(options, client.QueryParam(k, v[0])) - } - - txs, hasMore, err := tl.client.ExternalAccounts(ctx, options...) - if err != nil { - return false, err - } - - *to = txs - - return hasMore, nil -} - -func (tl *Timeline) initExternalAccounts(ctx context.Context) error { - ret := make([]*stripe.ExternalAccount, 0) - - _, err := tl.doExternalAccountsRequest(ctx, url.Values{}, &ret) - if err != nil { - return err - } - - if len(ret) > 0 { - tl.firstIDAfterStartingAt = ret[0].ID - } - - return nil -} - -func (tl *Timeline) ExternalAccountsTail(ctx context.Context, to *[]*stripe.ExternalAccount) (bool, TimelineState, func(), error) { - queryParams := url.Values{} - - switch { - case tl.state.OldestID != "": - queryParams.Set("starting_after", tl.state.OldestID) - default: - } - - hasMore, err := tl.doExternalAccountsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.BankAccount.Account.Created, 0) - futureState.OldestDate = &oldestDate - - if futureState.MoreRecentID == "" { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.BankAccount.Account.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - } - } - - futureState.NoMoreHistory = !hasMore - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} - -func (tl *Timeline) ExternalAccountsHead(ctx context.Context, to *[]*stripe.ExternalAccount) (bool, TimelineState, func(), error) { - if tl.firstIDAfterStartingAt == "" && tl.state.MoreRecentID == "" { - err := tl.initExternalAccounts(ctx) - if err != nil { - return false, TimelineState{}, nil, err - } - - if tl.firstIDAfterStartingAt == "" { - return false, TimelineState{ - NoMoreHistory: true, - }, func() {}, nil - } - } - - queryParams := url.Values{} - - switch { - case tl.state.MoreRecentID != "": - queryParams.Set("ending_before", tl.state.MoreRecentID) - case tl.firstIDAfterStartingAt != "": - queryParams.Set("ending_before", tl.firstIDAfterStartingAt) - } - - hasMore, err := tl.doExternalAccountsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.BankAccount.Account.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - - if futureState.OldestID == "" { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.BankAccount.Account.Created, 0) - futureState.OldestDate = &oldestDate - } - } - - futureState.NoMoreHistory = !hasMore - - for i, j := 0, len(*to)-1; i < j; i, j = i+1, j-1 { - (*to)[i], (*to)[j] = (*to)[j], (*to)[i] - } - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_test.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_test.go deleted file mode 100644 index 59503f4234..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package stripe - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/stretchr/testify/require" - "github.com/stripe/stripe-go/v72" -) - -func TestTimeline(t *testing.T) { - t.Parallel() - - mock := NewClientMock(t, true) - ref := time.Now() - timeline := NewTimeline(mock, TimelineConfig{ - PageSize: 2, - }, TimelineState{}, WithStartingAt(ref)) - - tx1 := &stripe.BalanceTransaction{ - ID: "tx1", - Created: ref.Add(-time.Minute).Unix(), - } - - tx2 := &stripe.BalanceTransaction{ - ID: "tx2", - Created: ref.Add(-2 * time.Minute).Unix(), - } - - mock.Expect(). - Limit(2). - CreatedLte(ref). - RespondsWith(true, tx1, tx2) - - ret := make([]*stripe.BalanceTransaction, 0) - hasMore, state, commit, err := timeline.TransactionsTail(context.TODO(), &ret) - require.NoError(t, err) - require.True(t, hasMore) - require.Equal(t, TimelineState{ - OldestID: "tx2", - OldestDate: client.DatePtr(time.Unix(tx2.Created, 0)), - MoreRecentID: "tx1", - MoreRecentDate: client.DatePtr(time.Unix(tx1.Created, 0)), - NoMoreHistory: false, - }, state) - - commit() - - tx3 := &stripe.BalanceTransaction{ - ID: "tx3", - Created: ref.Add(-3 * time.Minute).Unix(), - } - - mock.Expect().Limit(2).StartingAfter(tx2.ID).RespondsWith(false, tx3) - - hasMore, state, _, err = timeline.TransactionsTail(context.TODO(), &ret) - require.NoError(t, err) - require.False(t, hasMore) - require.Equal(t, TimelineState{ - OldestID: "tx3", - OldestDate: client.DatePtr(time.Unix(tx3.Created, 0)), - MoreRecentID: "tx1", - MoreRecentDate: client.DatePtr(time.Unix(tx1.Created, 0)), - NoMoreHistory: true, - }, state) -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_transactions.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_transactions.go deleted file mode 100644 index 5df01e8178..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_transactions.go +++ /dev/null @@ -1,145 +0,0 @@ -package stripe - -import ( - "context" - "fmt" - "net/url" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/stripe/stripe-go/v72" -) - -func (tl *Timeline) doTransactionsRequest(ctx context.Context, queryParams url.Values, - to *[]*stripe.BalanceTransaction, -) (bool, error) { - options := make([]client.ClientOption, 0) - options = append(options, client.QueryParam("limit", fmt.Sprintf("%d", tl.config.PageSize))) - options = append(options, client.QueryParam("expand[]", "data.source")) - options = append(options, client.QueryParam("expand[]", "data.source.charge")) - options = append(options, client.QueryParam("expand[]", "data.source.payment_intent")) - - for k, v := range queryParams { - options = append(options, client.QueryParam(k, v[0])) - } - - txs, hasMore, err := tl.client.BalanceTransactions(ctx, options...) - if err != nil { - return false, err - } - - *to = txs - - return hasMore, nil -} - -func (tl *Timeline) initTransactions(ctx context.Context) error { - ret := make([]*stripe.BalanceTransaction, 0) - params := url.Values{} - params.Set("limit", "1") - params.Set("created[lt]", fmt.Sprintf("%d", tl.startingAt.Unix())) - - _, err := tl.doTransactionsRequest(ctx, params, &ret) - if err != nil { - return err - } - - if len(ret) > 0 { - tl.firstIDAfterStartingAt = ret[0].ID - } - - return nil -} - -func (tl *Timeline) TransactionsTail(ctx context.Context, to *[]*stripe.BalanceTransaction) (bool, TimelineState, func(), error) { - queryParams := url.Values{} - - switch { - case tl.state.OldestID != "": - queryParams.Set("starting_after", tl.state.OldestID) - default: - queryParams.Set("created[lte]", fmt.Sprintf("%d", tl.startingAt.Unix())) - } - - hasMore, err := tl.doTransactionsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.Created, 0) - futureState.OldestDate = &oldestDate - - if futureState.MoreRecentID == "" { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - } - } - - futureState.NoMoreHistory = !hasMore - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} - -func (tl *Timeline) TransactionsHead(ctx context.Context, to *[]*stripe.BalanceTransaction) (bool, TimelineState, func(), error) { - if tl.firstIDAfterStartingAt == "" && tl.state.MoreRecentID == "" { - err := tl.initTransactions(ctx) - if err != nil { - return false, TimelineState{}, nil, err - } - - if tl.firstIDAfterStartingAt == "" { - return false, TimelineState{ - NoMoreHistory: true, - }, func() {}, nil - } - } - - queryParams := url.Values{} - - switch { - case tl.state.MoreRecentID != "": - queryParams.Set("ending_before", tl.state.MoreRecentID) - case tl.firstIDAfterStartingAt != "": - queryParams.Set("ending_before", tl.firstIDAfterStartingAt) - } - - hasMore, err := tl.doTransactionsRequest(ctx, queryParams, to) - if err != nil { - return false, TimelineState{}, nil, err - } - - futureState := tl.state - - if len(*to) > 0 { - firstItem := (*to)[0] - futureState.MoreRecentID = firstItem.ID - moreRecentDate := time.Unix(firstItem.Created, 0) - futureState.MoreRecentDate = &moreRecentDate - - if futureState.OldestID == "" { - lastItem := (*to)[len(*to)-1] - futureState.OldestID = lastItem.ID - oldestDate := time.Unix(lastItem.Created, 0) - futureState.OldestDate = &oldestDate - } - } - - futureState.NoMoreHistory = !hasMore - - for i, j := 0, len(*to)-1; i < j; i, j = i+1, j-1 { - (*to)[i], (*to)[j] = (*to)[j], (*to)[i] - } - - return hasMore, futureState, func() { - tl.state = futureState - }, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger.go deleted file mode 100644 index ab5e072d94..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger.go +++ /dev/null @@ -1,184 +0,0 @@ -package stripe - -import ( - "context" - - "github.com/formancehq/go-libs/logging" - "github.com/pkg/errors" - "github.com/stripe/stripe-go/v72" - "golang.org/x/sync/semaphore" -) - -type TimelineTriggerType string - -const ( - TimelineTriggerTypeTransactions TimelineTriggerType = "transactions" - TimelineTriggerTypeAccounts TimelineTriggerType = "accounts" - TimelineTriggerTypeExternalAccounts TimelineTriggerType = "external_accounts" -) - -func NewTimelineTrigger( - logger logging.Logger, - ingester Ingester, - timeline *Timeline, - timelineType TimelineTriggerType, -) *TimelineTrigger { - return &TimelineTrigger{ - logger: logger.WithFields(map[string]interface{}{ - "component": "timeline-trigger", - }), - ingester: ingester, - timeline: timeline, - timelineType: timelineType, - sem: semaphore.NewWeighted(1), - } -} - -type TimelineTrigger struct { - logger logging.Logger - ingester Ingester - timeline *Timeline - timelineType TimelineTriggerType - sem *semaphore.Weighted - cancel func() -} - -func (t *TimelineTrigger) Fetch(ctx context.Context) error { - if t.sem.TryAcquire(1) { - defer t.sem.Release(1) - - ctx, t.cancel = context.WithCancel(ctx) - if !t.timeline.State().NoMoreHistory { - if err := t.fetch(ctx, true); err != nil { - return err - } - } - - select { - case <-ctx.Done(): - return ctx.Err() - default: - if err := t.fetch(ctx, false); err != nil { - return err - } - } - } - - return nil -} - -func (t *TimelineTrigger) Cancel(ctx context.Context) { - if t.cancel != nil { - t.cancel() - - err := t.sem.Acquire(ctx, 1) - if err != nil { - panic(err) - } - - t.sem.Release(1) - } -} - -func (t *TimelineTrigger) fetch(ctx context.Context, tail bool) error { - for { - select { - case <-ctx.Done(): - return ctx.Err() - default: - hasMore, err := t.triggerPage(ctx, tail) - if err != nil { - return errors.Wrap(err, "error triggering tail page") - } - - if !hasMore { - return nil - } - } - } -} - -func (t *TimelineTrigger) triggerPage(ctx context.Context, tail bool) (bool, error) { - logger := t.logger.WithFields(map[string]interface{}{ - "tail": tail, - }) - - logger.Debugf("Trigger page") - - var hasMore bool - switch t.timelineType { - case TimelineTriggerTypeTransactions: - ret := make([]*stripe.BalanceTransaction, 0) - method := t.timeline.TransactionsHead - if tail { - method = t.timeline.TransactionsTail - } - - more, futureState, commitFn, err := method(ctx, &ret) - if err != nil { - return false, errors.Wrap(err, "fetching timeline") - } - hasMore = more - - logger.Debug("Ingest transactions batch") - - if len(ret) > 0 { - err = t.ingester.IngestTransactions(ctx, ret, futureState, tail) - if err != nil { - return false, errors.Wrap(err, "ingesting batch") - } - } - - commitFn() - - case TimelineTriggerTypeAccounts: - ret := make([]*stripe.Account, 0) - method := t.timeline.AccountsHead - if tail { - method = t.timeline.AccountsTail - } - - more, futureState, commitFn, err := method(ctx, &ret) - if err != nil { - return false, errors.Wrap(err, "fetching timeline") - } - hasMore = more - - logger.Debug("Ingest accounts batch") - - if len(ret) > 0 { - err = t.ingester.IngestAccounts(ctx, ret, futureState, tail) - if err != nil { - return false, errors.Wrap(err, "ingesting batch") - } - } - - commitFn() - - case TimelineTriggerTypeExternalAccounts: - ret := make([]*stripe.ExternalAccount, 0) - method := t.timeline.ExternalAccountsHead - if tail { - method = t.timeline.ExternalAccountsTail - } - - more, futureState, commitFn, err := method(ctx, &ret) - if err != nil { - return false, errors.Wrap(err, "fetching timeline") - } - hasMore = more - - logger.Debug("Ingest transactions batch") - - if len(ret) > 0 { - err = t.ingester.IngestExternalAccounts(ctx, ret, futureState, tail) - if err != nil { - return false, errors.Wrap(err, "ingesting batch") - } - } - - commitFn() - } - - return hasMore, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger_test.go b/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger_test.go deleted file mode 100644 index 21fa54ac2d..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/timeline_trigger_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package stripe - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/stretchr/testify/require" - "github.com/stripe/stripe-go/v72" -) - -func TestTimelineTrigger(t *testing.T) { - t.Parallel() - - const txCount = 12 - - mock := NewClientMock(t, true) - ref := time.Now().Add(-time.Minute * time.Duration(txCount) / 2) - timeline := NewTimeline(mock, TimelineConfig{ - PageSize: 2, - }, TimelineState{}, WithStartingAt(ref)) - - ingestedTx := make([]*stripe.BalanceTransaction, 0) - trigger := NewTimelineTrigger( - logging.FromContext(context.TODO()), - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - ingestedTx = append(ingestedTx, batch...) - return nil - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return nil - }, - ), - timeline, - TimelineTriggerTypeTransactions, - ) - - allTxs := make([]*stripe.BalanceTransaction, txCount) - for i := 0; i < txCount/2; i++ { - allTxs[txCount/2+i] = &stripe.BalanceTransaction{ - ID: fmt.Sprintf("%d", txCount/2+i), - Created: ref.Add(-time.Duration(i) * time.Minute).Unix(), - } - allTxs[txCount/2-i-1] = &stripe.BalanceTransaction{ - ID: fmt.Sprintf("%d", txCount/2-i-1), - Created: ref.Add(time.Duration(i) * time.Minute).Unix(), - } - } - - for i := 0; i < txCount/2; i += 2 { - mock.Expect().Limit(2).RespondsWith(i < txCount/2-2, allTxs[txCount/2+i], allTxs[txCount/2+i+1]) - } - - for i := 0; i < txCount/2; i += 2 { - mock.Expect().Limit(2).RespondsWith(i < txCount/2-2, allTxs[txCount/2-i-2], allTxs[txCount/2-i-1]) - } - - ctx, cancel := context.WithDeadline(context.TODO(), time.Now().Add(time.Second)) - defer cancel() - - require.NoError(t, trigger.Fetch(ctx)) - require.Len(t, ingestedTx, txCount) -} - -func TestCancelTimelineTrigger(t *testing.T) { - t.Parallel() - - const txCount = 12 - - mock := NewClientMock(t, false) - ref := time.Now().Add(-time.Minute * time.Duration(txCount) / 2) - timeline := NewTimeline(mock, TimelineConfig{ - PageSize: 1, - }, TimelineState{}, WithStartingAt(ref)) - - waiting := make(chan struct{}) - trigger := NewTimelineTrigger( - logging.FromContext(context.TODO()), - NewIngester( - func(ctx context.Context, batch []*stripe.BalanceTransaction, commitState TimelineState, tail bool) error { - close(waiting) // Instruct the test the trigger is in fetching state - <-ctx.Done() - - return nil - }, - func(ctx context.Context, batch []*stripe.Account, commitState TimelineState, tail bool) error { - return nil - }, - func(ctx context.Context, batch []*stripe.ExternalAccount, commitState TimelineState, tail bool) error { - return nil - }, - ), - timeline, - TimelineTriggerTypeTransactions, - ) - - allTxs := make([]*stripe.BalanceTransaction, txCount) - for i := 0; i < txCount; i++ { - allTxs[i] = &stripe.BalanceTransaction{ - ID: fmt.Sprintf("%d", i), - } - mock.Expect().Limit(1).RespondsWith(i < txCount-1, allTxs[i]) - } - - go func() { - // TODO: Handle error - _ = trigger.Fetch(context.TODO()) - }() - select { - case <-time.After(time.Second): - t.Fatalf("timeout") - case <-waiting: - trigger.Cancel(context.TODO()) - require.NotEmpty(t, mock.expectations) - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/translate.go b/components/payments/cmd/connectors/internal/connectors/stripe/translate.go deleted file mode 100644 index 057035bab8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/translate.go +++ /dev/null @@ -1,901 +0,0 @@ -package stripe - -import ( - "encoding/json" - "log" - "math/big" - "runtime/debug" - "strings" - "time" - - "github.com/davecgh/go-spew/spew" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/internal/models" - "github.com/stripe/stripe-go/v72" -) - -func createBatchElement( - connectorID models.ConnectorID, - balanceTransaction *stripe.BalanceTransaction, - account string, - forward bool, -) (ingestion.PaymentBatchElement, bool) { - var payment *models.Payment - var adjustment *models.PaymentAdjustment - - defer func() { - // DEBUG - if e := recover(); e != nil { - log.Println("Error translating transaction") - debug.PrintStack() - spew.Dump(balanceTransaction) - panic(e) - } - }() - - if balanceTransaction.Source == nil { - return ingestion.PaymentBatchElement{}, false - } - - rawData, err := json.Marshal(balanceTransaction) - if err != nil { - return ingestion.PaymentBatchElement{}, false - } - - switch balanceTransaction.Type { - case stripe.BalanceTransactionTypeCharge: - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Created, 0) - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Charge.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Charge.Metadata, balanceTransaction.Source.Charge.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Charge.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Charge.Amount - balanceTransaction.Source.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - RawData: rawData, - Scheme: models.PaymentScheme(balanceTransaction.Source.Charge.PaymentMethodDetails.Card.Brand), - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - case stripe.BalanceTransactionTypeRefund: - // Refund a charge - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Refund.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Refund.Charge.Created, 0) - // Created when a credit card charge refund is initiated. - // If you authorize and capture separately and the capture amount is - // less than the initial authorization, you see a balance transaction - // of type charge for the full authorization amount and another balance - // transaction of type refund for the uncaptured portion. - // cf https://stripe.com/docs/reports/balance-transaction-types - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Refund.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Metadata, balanceTransaction.Source.Refund.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount - balanceTransaction.Source.Refund.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - RawData: rawData, - Scheme: models.PaymentScheme(balanceTransaction.Source.Refund.Charge.PaymentMethodDetails.Card.Brand), - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Source.Refund.Amount), - Status: models.PaymentStatusRefunded, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeRefundFailure: - // Refund a charge - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Refund.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Refund.Charge.Created, 0) - // Created when a credit card charge refund is initiated. - // If you authorize and capture separately and the capture amount is - // less than the initial authorization, you see a balance transaction - // of type charge for the full authorization amount and another balance - // transaction of type refund for the uncaptured portion. - // cf https://stripe.com/docs/reports/balance-transaction-types - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Refund.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Metadata, balanceTransaction.Source.Refund.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount - balanceTransaction.Source.Refund.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - RawData: rawData, - Scheme: models.PaymentScheme(balanceTransaction.Source.Refund.Charge.PaymentMethodDetails.Card.Brand), - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Source.Refund.Amount), - Status: models.PaymentStatusRefundedFailure, - RawData: rawData, - } - - case stripe.BalanceTransactionTypePayment: - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Created, 0) - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Charge.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Charge.Metadata, balanceTransaction.Source.Charge.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Charge.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Charge.Amount - balanceTransaction.Source.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Charge.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - Scheme: models.PaymentSchemeOther, - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - case stripe.BalanceTransactionTypePaymentRefund: - // Refund a charge - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Refund.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Refund.Charge.Created, 0) - // Created when a credit card charge refund is initiated. - // If you authorize and capture separately and the capture amount is - // less than the initial authorization, you see a balance transaction - // of type charge for the full authorization amount and another balance - // transaction of type refund for the uncaptured portion. - // cf https://stripe.com/docs/reports/balance-transaction-types - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Refund.Charge.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Charge.Metadata, balanceTransaction.Source.Refund.Charge.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Charge.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount - balanceTransaction.Source.Refund.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - Scheme: models.PaymentSchemeOther, - RawData: rawData, - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Source.Refund.Amount), - Status: models.PaymentStatusRefunded, - RawData: rawData, - } - - case stripe.BalanceTransactionTypePaymentFailureRefund: - // Refund a charge - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Refund.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Refund.Charge.Created, 0) - // Created when a credit card charge refund is initiated. - // If you authorize and capture separately and the capture amount is - // less than the initial authorization, you see a balance transaction - // of type charge for the full authorization amount and another balance - // transaction of type refund for the uncaptured portion. - // cf https://stripe.com/docs/reports/balance-transaction-types - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Refund.Charge.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Charge.Metadata, balanceTransaction.Source.Refund.Charge.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Refund.Charge.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount - balanceTransaction.Source.Refund.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Refund.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - Scheme: models.PaymentSchemeOther, - RawData: rawData, - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Refund.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Source.Refund.Amount), - Status: models.PaymentStatusRefundedFailure, - RawData: rawData, - } - - case stripe.BalanceTransactionTypePayout: - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Payout.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Created, 0) - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayOut, - Status: convertPayoutStatus(balanceTransaction.Source.Payout.Status), - Amount: big.NewInt(balanceTransaction.Source.Payout.Amount), - InitialAmount: big.NewInt(balanceTransaction.Source.Payout.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Payout.Currency)), - Scheme: func() models.PaymentScheme { - switch balanceTransaction.Source.Payout.Type { - case stripe.PayoutTypeBank: - return models.PaymentSchemeSepaCredit - case stripe.PayoutTypeCard: - return models.PaymentScheme(balanceTransaction.Source.Payout.Card.Brand) - } - - return models.PaymentSchemeUnknown - }(), - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Payout.Metadata), - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - case stripe.BalanceTransactionTypePayoutFailure: - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Payout.Created, 0) - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusFailed, - Amount: big.NewInt(balanceTransaction.Source.Payout.Amount), - InitialAmount: big.NewInt(balanceTransaction.Source.Payout.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Payout.Currency)), - Scheme: func() models.PaymentScheme { - switch balanceTransaction.Source.Payout.Type { - case stripe.PayoutTypeBank: - return models.PaymentSchemeSepaCredit - case stripe.PayoutTypeCard: - return models.PaymentScheme(balanceTransaction.Source.Payout.Card.Brand) - } - - return models.PaymentSchemeUnknown - }(), - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Payout.Metadata), - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Status: models.PaymentStatusFailed, - RawData: rawData, - } - - case stripe.BalanceTransactionTypePayoutCancel: - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Payout.Created, 0) - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusCancelled, - Amount: big.NewInt(balanceTransaction.Source.Payout.Amount), - InitialAmount: big.NewInt(balanceTransaction.Source.Payout.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Payout.Currency)), - Scheme: func() models.PaymentScheme { - switch balanceTransaction.Source.Payout.Type { - case stripe.PayoutTypeBank: - return models.PaymentSchemeSepaCredit - case stripe.PayoutTypeCard: - return models.PaymentScheme(balanceTransaction.Source.Payout.Card.Brand) - } - - return models.PaymentSchemeUnknown - }(), - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Payout.Metadata), - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Payout.BalanceTransaction.ID, - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Status: models.PaymentStatusCancelled, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeTransfer: - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Transfer.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Created, 0) - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Transfer.Amount - balanceTransaction.Source.Transfer.AmountReversed), - InitialAmount: big.NewInt(balanceTransaction.Source.Transfer.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Transfer.Currency)), - Scheme: models.PaymentSchemeOther, - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Transfer.Metadata), - } - - if balanceTransaction.Source.Transfer.Destination != nil { - payment.DestinationAccountID = &models.AccountID{ - Reference: balanceTransaction.Source.Transfer.Destination.ID, - ConnectorID: connectorID, - } - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - case stripe.BalanceTransactionTypeTransferRefund: - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Transfer.Created, 0) - // Two things to insert here: the balance transaction at the origin - // of the refund and the balance transaction of the refund, which is an - // adjustment of the origin. - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusSucceeded, - Amount: big.NewInt(balanceTransaction.Source.Transfer.Amount - balanceTransaction.Source.Transfer.AmountReversed), - InitialAmount: big.NewInt(balanceTransaction.Source.Transfer.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Transfer.Currency)), - Scheme: models.PaymentSchemeOther, - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Transfer.Metadata), - } - - if balanceTransaction.Source.Transfer.Destination != nil { - payment.DestinationAccountID = &models.AccountID{ - Reference: balanceTransaction.Source.Transfer.Destination.ID, - ConnectorID: connectorID, - } - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Amount), - Status: models.PaymentStatusRefunded, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeTransferCancel: - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Transfer.Created, 0) - - // Two things to insert here: the balance transaction at the origin - // of the refund and the balance transaction of the refund, which is an - // adjustment of the origin. - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusCancelled, - Amount: big.NewInt(balanceTransaction.Source.Transfer.Amount - balanceTransaction.Source.Transfer.AmountReversed), - InitialAmount: big.NewInt(balanceTransaction.Source.Transfer.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Transfer.Currency)), - Scheme: models.PaymentSchemeOther, - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Transfer.Metadata), - } - - if balanceTransaction.Source.Transfer.Destination != nil { - payment.DestinationAccountID = &models.AccountID{ - Reference: balanceTransaction.Source.Transfer.Destination.ID, - ConnectorID: connectorID, - } - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Amount), - Status: models.PaymentStatusCancelled, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeTransferFailure: - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Transfer.Created, 0) - // Two things to insert here: the balance transaction at the origin - // of the refund and the balance transaction of the refund, which is an - // adjustment of the origin. - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusFailed, - Amount: big.NewInt(balanceTransaction.Source.Transfer.Amount - balanceTransaction.Source.Transfer.AmountReversed), - InitialAmount: big.NewInt(balanceTransaction.Source.Transfer.Amount), - RawData: rawData, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, string(balanceTransaction.Source.Transfer.Currency)), - Scheme: models.PaymentSchemeOther, - CreatedAt: createdAt, - Metadata: computeMetadata(paymentID, createdAt, balanceTransaction.Source.Transfer.Metadata), - } - - if balanceTransaction.Source.Transfer.Destination != nil { - payment.DestinationAccountID = &models.AccountID{ - Reference: balanceTransaction.Source.Transfer.Destination.ID, - ConnectorID: connectorID, - } - } - - if account != "" { - payment.SourceAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Transfer.BalanceTransaction.ID, - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Amount: big.NewInt(balanceTransaction.Amount), - Status: models.PaymentStatusFailed, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeAdjustment: - if balanceTransaction.Source.Dispute == nil { - // We are only handle dispute adjustments - return ingestion.PaymentBatchElement{}, false - } - - transactionCurrency := strings.ToUpper(string(balanceTransaction.Source.Dispute.Charge.Currency)) - _, ok := supportedCurrenciesWithDecimal[transactionCurrency] - if !ok { - return ingestion.PaymentBatchElement{}, false - } - - disputeStatus := convertDisputeStatus(balanceTransaction.Source.Dispute.Status) - paymentStatus := models.PaymentStatusPending - switch disputeStatus { - case models.PaymentStatusDisputeWon: - paymentStatus = models.PaymentStatusSucceeded - case models.PaymentStatusDisputeLost: - paymentStatus = models.PaymentStatusFailed - default: - paymentStatus = models.PaymentStatusPending - } - - paymentID := models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Dispute.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - } - createdAt := time.Unix(balanceTransaction.Source.Dispute.Charge.Created, 0) - - var metadata []*models.PaymentMetadata - if balanceTransaction.Source.Dispute.Charge.PaymentIntent != nil { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Dispute.Charge.Metadata, balanceTransaction.Source.Dispute.Charge.PaymentIntent.Metadata) - } else { - metadata = computeMetadata(paymentID, createdAt, balanceTransaction.Source.Dispute.Charge.Metadata) - } - - payment = &models.Payment{ - ID: paymentID, - Reference: balanceTransaction.Source.Dispute.Charge.BalanceTransaction.ID, - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: paymentStatus, // Dispute is occuring, we don't know the outcome yet - Amount: big.NewInt(balanceTransaction.Source.Dispute.Charge.Amount - balanceTransaction.Source.Dispute.Charge.AmountRefunded), - InitialAmount: big.NewInt(balanceTransaction.Source.Dispute.Charge.Amount), - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transactionCurrency), - RawData: rawData, - Scheme: models.PaymentScheme(balanceTransaction.Source.Dispute.Charge.PaymentMethodDetails.Card.Brand), - CreatedAt: createdAt, - Metadata: metadata, - } - - if account != "" { - payment.DestinationAccountID = &models.AccountID{ - Reference: account, - ConnectorID: connectorID, - } - } - - adjustment = &models.PaymentAdjustment{ - PaymentID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: balanceTransaction.Source.Dispute.Charge.BalanceTransaction.ID, - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - CreatedAt: time.Unix(balanceTransaction.Created, 0), - Reference: balanceTransaction.ID, - Status: disputeStatus, - RawData: rawData, - } - - case stripe.BalanceTransactionTypeStripeFee: - return ingestion.PaymentBatchElement{}, false - default: - return ingestion.PaymentBatchElement{}, false - } - - return ingestion.PaymentBatchElement{ - Payment: payment, - Adjustment: adjustment, - }, true -} - -func convertDisputeStatus(status stripe.DisputeStatus) models.PaymentStatus { - switch status { - case stripe.DisputeStatusNeedsResponse, stripe.DisputeStatusUnderReview: - return models.PaymentStatusDispute - case stripe.DisputeStatusLost: - return models.PaymentStatusDisputeLost - case stripe.DisputeStatusWon: - return models.PaymentStatusDisputeWon - default: - return models.PaymentStatusDispute - } -} - -func convertPayoutStatus(status stripe.PayoutStatus) models.PaymentStatus { - switch status { - case stripe.PayoutStatusCanceled: - return models.PaymentStatusCancelled - case stripe.PayoutStatusFailed: - return models.PaymentStatusFailed - case stripe.PayoutStatusInTransit, stripe.PayoutStatusPending: - return models.PaymentStatusPending - case stripe.PayoutStatusPaid: - return models.PaymentStatusSucceeded - } - - return models.PaymentStatusOther -} - -func computeMetadata(paymentID models.PaymentID, createdAt time.Time, metadatas ...map[string]string) []*models.PaymentMetadata { - res := make([]*models.PaymentMetadata, 0) - for _, metadata := range metadatas { - for k, v := range metadata { - res = append(res, &models.PaymentMetadata{ - PaymentID: paymentID, - CreatedAt: createdAt, - Key: k, - Value: v, - }) - } - } - - return res -} diff --git a/components/payments/cmd/connectors/internal/connectors/stripe/utils_test.go b/components/payments/cmd/connectors/internal/connectors/stripe/utils_test.go deleted file mode 100644 index 117bf33c68..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/stripe/utils_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package stripe - -import ( - "context" - "flag" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "os" - "sync" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/stripe/client" - "github.com/stripe/stripe-go/v72" -) - -func TestMain(m *testing.M) { - flag.Parse() - - os.Exit(m.Run()) -} - -type ClientMockExpectation struct { - query url.Values - hasMore bool - items []*stripe.BalanceTransaction -} - -func (e *ClientMockExpectation) QueryParam(key string, value any) *ClientMockExpectation { - var qpvalue string - switch value.(type) { - case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: - qpvalue = fmt.Sprintf("%d", value) - default: - qpvalue = fmt.Sprintf("%s", value) - } - e.query.Set(key, qpvalue) - - return e -} - -func (e *ClientMockExpectation) StartingAfter(v string) *ClientMockExpectation { - e.QueryParam("starting_after", v) - - return e -} - -func (e *ClientMockExpectation) CreatedLte(v time.Time) *ClientMockExpectation { - e.QueryParam("created[lte]", v.Unix()) - - return e -} - -func (e *ClientMockExpectation) Limit(v int) *ClientMockExpectation { - e.QueryParam("limit", v) - - return e -} - -func (e *ClientMockExpectation) RespondsWith(hasMore bool, - txs ...*stripe.BalanceTransaction, -) *ClientMockExpectation { - e.hasMore = hasMore - e.items = txs - - return e -} - -func (e *ClientMockExpectation) handle(options ...client.ClientOption) ([]*stripe.BalanceTransaction, bool, error) { - req := httptest.NewRequest(http.MethodGet, "/", nil) - - for _, option := range options { - option.Apply(req) - } - - for key := range e.query { - if req.URL.Query().Get(key) != e.query.Get(key) { - return nil, false, fmt.Errorf("mismatch query params, expected query param '%s' "+ - "with value '%s', got '%s'", key, e.query.Get(key), req.URL.Query().Get(key)) - } - } - - return e.items, e.hasMore, nil -} - -type ClientMock struct { - expectations *FIFO[*ClientMockExpectation] -} - -func (m *ClientMock) ForAccount(account string) client.Client { - return m -} - -func (m *ClientMock) Accounts(ctx context.Context, - options ...client.ClientOption, -) ([]*stripe.Account, bool, error) { - return nil, false, nil -} - -func (m *ClientMock) Balance(ctx context.Context, - options ...client.ClientOption, -) (*stripe.Balance, error) { - return nil, nil -} - -func (m *ClientMock) ExternalAccounts(ctx context.Context, - options ...client.ClientOption, -) ([]*stripe.ExternalAccount, bool, error) { - return nil, false, nil -} - -func (m *ClientMock) CreateTransfer(ctx context.Context, - createTransferRequest *client.CreateTransferRequest, - options ...client.ClientOption, -) (*stripe.Transfer, error) { - return nil, nil -} - -func (m *ClientMock) ReverseTransfer(ctx context.Context, - createTransferReversalRequest *client.CreateTransferReversalRequest, - options ...client.ClientOption, -) (*stripe.Reversal, error) { - return nil, nil -} - -func (m *ClientMock) CreatePayout(ctx context.Context, - createPayoutRequest *client.CreatePayoutRequest, - options ...client.ClientOption, -) (*stripe.Payout, error) { - return nil, nil -} - -func (m *ClientMock) GetPayout(ctx context.Context, - payoutID string, - options ...client.ClientOption, -) (*stripe.Payout, error) { - return nil, nil -} - -func (m *ClientMock) BalanceTransactions(ctx context.Context, - options ...client.ClientOption, -) ([]*stripe.BalanceTransaction, bool, error) { - e, ok := m.expectations.Pop() - if !ok { - return nil, false, fmt.Errorf("no more expectation") - } - - return e.handle(options...) -} - -func (m *ClientMock) Expect() *ClientMockExpectation { - e := &ClientMockExpectation{ - query: url.Values{}, - } - m.expectations.Push(e) - - return e -} - -func NewClientMock(t *testing.T, expectationsShouldBeConsumed bool) *ClientMock { - t.Helper() - - m := &ClientMock{ - expectations: &FIFO[*ClientMockExpectation]{}, - } - - if expectationsShouldBeConsumed { - t.Cleanup(func() { - if !m.expectations.Empty() && !t.Failed() { - t.Errorf("all expectations not consumed") - } - }) - } - - return m -} - -var _ client.Client = &ClientMock{} - -type FIFO[ITEM any] struct { - mu sync.Mutex - items []ITEM -} - -func (s *FIFO[ITEM]) Pop() (ITEM, bool) { - s.mu.Lock() - defer s.mu.Unlock() - - if len(s.items) == 0 { - var i ITEM - - return i, false - } - - ret := s.items[0] - - if len(s.items) == 1 { - s.items = make([]ITEM, 0) - - return ret, true - } - - s.items = s.items[1:] - - return ret, true -} - -func (s *FIFO[ITEM]) Peek() (ITEM, bool) { - s.mu.Lock() - defer s.mu.Unlock() - - if len(s.items) == 0 { - var i ITEM - - return i, false - } - - return s.items[0], true -} - -func (s *FIFO[ITEM]) Push(i ITEM) *FIFO[ITEM] { - s.mu.Lock() - defer s.mu.Unlock() - - s.items = append(s.items, i) - - return s -} - -func (s *FIFO[ITEM]) Empty() bool { - s.mu.Lock() - defer s.mu.Unlock() - - return len(s.items) == 0 -} diff --git a/components/payments/cmd/connectors/internal/connectors/utils.go b/components/payments/cmd/connectors/internal/connectors/utils.go deleted file mode 100644 index eea7591cc1..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/utils.go +++ /dev/null @@ -1,52 +0,0 @@ -package connectors - -import ( - "context" - "os" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/trace" -) - -type DeferrableFunc func(ctx context.Context, timeSince time.Time) - -func ClientMetrics(ctx context.Context, connectorName, operation string) DeferrableFunc { - attributes := []attribute.KeyValue{ - attribute.String("connector", connectorName), - attribute.String("operation", operation), - } - - stack := os.Getenv("STACK") - if stack != "" { - attributes = append(attributes, attribute.String("stack", stack)) - } - - metrics.GetMetricsRegistry().ConnectorPSPCalls().Add(ctx, 1, metric.WithAttributes(attributes...)) - - return func(ctx context.Context, timeSince time.Time) { - metrics.GetMetricsRegistry().ConnectorPSPCallLatencies().Record(ctx, time.Since(timeSince).Milliseconds(), metric.WithAttributes(attributes...)) - } -} - -func StartSpan( - ctx context.Context, - spanName string, - attributes ...attribute.KeyValue, -) (context.Context, trace.Span) { - parentSpan := trace.SpanFromContext(ctx) - return otel.Tracer().Start( - ctx, - spanName, - trace.WithNewRoot(), - trace.WithLinks(trace.Link{ - SpanContext: parentSpan.SpanContext(), - }), - trace.WithAttributes( - attributes..., - ), - ) -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/balances.go b/components/payments/cmd/connectors/internal/connectors/wise/client/balances.go deleted file mode 100644 index eb9b5e7105..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/balances.go +++ /dev/null @@ -1,67 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Balance struct { - ID uint64 `json:"id"` - Currency string `json:"currency"` - Type string `json:"type"` - Name string `json:"name"` - Amount struct { - Value json.Number `json:"value"` - Currency string `json:"currency"` - } `json:"amount"` - ReservedAmount struct { - Value json.Number `json:"value"` - Currency string `json:"currency"` - } `json:"reservedAmount"` - CashAmount struct { - Value json.Number `json:"value"` - Currency string `json:"currency"` - } `json:"cashAmount"` - TotalWorth struct { - Value json.Number `json:"value"` - Currency string `json:"currency"` - } `json:"totalWorth"` - CreationTime time.Time `json:"creationTime"` - ModificationTime time.Time `json:"modificationTime"` - Visible bool `json:"visible"` -} - -func (w *Client) GetBalances(ctx context.Context, profileID uint64) ([]*Balance, error) { - f := connectors.ClientMetrics(ctx, "wise", "list_balances") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint(fmt.Sprintf("v4/profiles/%d/balances?types=STANDARD", profileID)), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - var balances []*Balance - err = json.NewDecoder(res.Body).Decode(&balances) - if err != nil { - return nil, fmt.Errorf("failed to decode account: %w", err) - } - - return balances, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/client.go b/components/payments/cmd/connectors/internal/connectors/wise/client/client.go deleted file mode 100644 index bfe308c044..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/client.go +++ /dev/null @@ -1,47 +0,0 @@ -package client - -import ( - "fmt" - "net/http" - - lru "github.com/hashicorp/golang-lru/v2" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" -) - -const apiEndpoint = "https://api.wise.com" - -type apiTransport struct { - APIKey string - underlying http.RoundTripper -} - -func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.APIKey)) - - return t.underlying.RoundTrip(req) -} - -type Client struct { - httpClient *http.Client - - recipientAccountsCache *lru.Cache[uint64, *RecipientAccount] -} - -func (w *Client) endpoint(path string) string { - return fmt.Sprintf("%s/%s", apiEndpoint, path) -} - -func NewClient(apiKey string) *Client { - recipientsCache, _ := lru.New[uint64, *RecipientAccount](2048) - httpClient := &http.Client{ - Transport: &apiTransport{ - APIKey: apiKey, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - } - - return &Client{ - httpClient: httpClient, - recipientAccountsCache: recipientsCache, - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/error.go b/components/payments/cmd/connectors/internal/connectors/wise/client/error.go deleted file mode 100644 index 13f3813d28..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/error.go +++ /dev/null @@ -1,42 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - "io" -) - -type wiseErrors struct { - Errors []*wiseError `json:"errors"` -} - -type wiseError struct { - StatusCode int `json:"-"` - Code string `json:"code"` - Message string `json:"message"` -} - -func (me *wiseError) Error() error { - if me.Message == "" { - return fmt.Errorf("unexpected status code: %d", me.StatusCode) - } - - return fmt.Errorf("%s: %s", me.Code, me.Message) -} - -func unmarshalError(statusCode int, body io.ReadCloser) *wiseError { - var ces wiseErrors - _ = json.NewDecoder(body).Decode(&ces) - - if len(ces.Errors) == 0 { - return &wiseError{ - StatusCode: statusCode, - } - } - - return &wiseError{ - StatusCode: statusCode, - Code: ces.Errors[0].Code, - Message: ces.Errors[0].Message, - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/payouts.go b/components/payments/cmd/connectors/internal/connectors/wise/client/payouts.go deleted file mode 100644 index a87111e460..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/payouts.go +++ /dev/null @@ -1,131 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Payout struct { - ID uint64 `json:"id"` - Reference string `json:"reference"` - Status string `json:"status"` - SourceAccount uint64 `json:"sourceAccount"` - SourceCurrency string `json:"sourceCurrency"` - SourceValue json.Number `json:"sourceValue"` - TargetAccount uint64 `json:"targetAccount"` - TargetCurrency string `json:"targetCurrency"` - TargetValue json.Number `json:"targetValue"` - Business uint64 `json:"business"` - Created string `json:"created"` - //nolint:tagliatelle // allow for clients - CustomerTransactionID string `json:"customerTransactionId"` - Details struct { - Reference string `json:"reference"` - } `json:"details"` - Rate float64 `json:"rate"` - User uint64 `json:"user"` - - SourceBalanceID uint64 `json:"-"` - DestinationBalanceID uint64 `json:"-"` - - CreatedAt time.Time `json:"-"` -} - -func (t *Payout) UnmarshalJSON(data []byte) error { - type Alias Transfer - - aux := &struct { - Created string `json:"created"` - *Alias - }{ - Alias: (*Alias)(t), - } - - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - var err error - - t.CreatedAt, err = time.Parse("2006-01-02 15:04:05", aux.Created) - if err != nil { - return fmt.Errorf("failed to parse created time: %w", err) - } - - return nil -} - -func (w *Client) GetPayout(ctx context.Context, payoutID string) (*Payout, error) { - f := connectors.ClientMetrics(ctx, "wise", "get_payout") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint("v1/transfers/"+payoutID), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - var payout Payout - err = json.Unmarshal(body, &payout) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal transfer: %w", err) - } - - return &payout, nil -} - -func (w *Client) CreatePayout(ctx context.Context, quote Quote, targetAccount uint64, transactionID string) (*Payout, error) { - f := connectors.ClientMetrics(ctx, "wise", "initiate_payout") - now := time.Now() - defer f(ctx, now) - - req, err := json.Marshal(map[string]interface{}{ - "targetAccount": targetAccount, - "quoteUuid": quote.ID.String(), - "customerTransactionId": transactionID, - }) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Post(w.endpoint("v1/transfers"), "application/json", bytes.NewBuffer(req)) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - var response Payout - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to get response from transfer: %w", err) - } - - return &response, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/profiles.go b/components/payments/cmd/connectors/internal/connectors/wise/client/profiles.go deleted file mode 100644 index 6cf4fe1907..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/profiles.go +++ /dev/null @@ -1,48 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type Profile struct { - ID uint64 `json:"id"` - Type string `json:"type"` -} - -func (w *Client) GetProfiles(ctx context.Context) ([]Profile, error) { - f := connectors.ClientMetrics(ctx, "wise", "list_profiles") - now := time.Now() - defer f(ctx, now) - - var profiles []Profile - - res, err := w.httpClient.Get(w.endpoint("v2/profiles")) - if err != nil { - return profiles, err - } - - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - err = json.Unmarshal(body, &profiles) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal profiles: %w", err) - } - - return profiles, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/quotes.go b/components/payments/cmd/connectors/internal/connectors/wise/client/quotes.go deleted file mode 100644 index cdbced8ea3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/quotes.go +++ /dev/null @@ -1,57 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/google/uuid" -) - -type Quote struct { - ID uuid.UUID `json:"id"` -} - -func (w *Client) CreateQuote(ctx context.Context, profileID, currency string, amount json.Number) (Quote, error) { - f := connectors.ClientMetrics(ctx, "wise", "create_quote") - now := time.Now() - defer f(ctx, now) - - var response Quote - - req, err := json.Marshal(map[string]interface{}{ - "sourceCurrency": currency, - "targetCurrency": currency, - "sourceAmount": amount, - }) - if err != nil { - return response, err - } - - res, err := w.httpClient.Post(w.endpoint("v3/profiles/"+profileID+"/quotes"), "application/json", bytes.NewBuffer(req)) - if err != nil { - return response, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return response, unmarshalError(res.StatusCode, res.Body).Error() - } - - body, err := io.ReadAll(res.Body) - if err != nil { - return response, fmt.Errorf("failed to read response body: %w", err) - } - - err = json.Unmarshal(body, &response) - if err != nil { - return response, fmt.Errorf("failed to get response from quote: %w", err) - } - - return response, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/recipient_accounts.go b/components/payments/cmd/connectors/internal/connectors/wise/client/recipient_accounts.go deleted file mode 100644 index 83930a71fd..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/recipient_accounts.go +++ /dev/null @@ -1,132 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" -) - -type RecipientAccountsResponse struct { - Content []*RecipientAccount `json:"content"` - SeekPositionForCurrent uint64 `json:"seekPositionForCurrent"` - SeekPositionForNext uint64 `json:"seekPositionForNext"` - Size int `json:"size"` -} - -type RecipientAccount struct { - ID uint64 `json:"id"` - Profile uint64 `json:"profileId"` - Currency string `json:"currency"` - Name struct { - FullName string `json:"fullName"` - } `json:"name"` -} - -func (w *Client) GetRecipientAccounts(ctx context.Context, profileID uint64, pageSize int, seekPositionForNext uint64) (*RecipientAccountsResponse, error) { - f := connectors.ClientMetrics(ctx, "wise", "list_recipient_accounts") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint("v2/accounts"), http.NoBody) - if err != nil { - return nil, err - } - - q := req.URL.Query() - q.Add("profile", fmt.Sprintf("%d", profileID)) - q.Add("size", fmt.Sprintf("%d", pageSize)) - q.Add("sort", "id,asc") - if seekPositionForNext > 0 { - q.Add("seekPosition", fmt.Sprintf("%d", seekPositionForNext)) - } - req.URL.RawQuery = q.Encode() - - res, err := w.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - var recipientAccounts *RecipientAccountsResponse - err = json.Unmarshal(body, &recipientAccounts) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal transfers: %w", err) - } - - return recipientAccounts, nil -} - -func (w *Client) GetRecipientAccount(ctx context.Context, accountID uint64) (*RecipientAccount, error) { - f := connectors.ClientMetrics(ctx, "wise", "get_recipient_account") - now := time.Now() - defer f(ctx, now) - - if rc, ok := w.recipientAccountsCache.Get(accountID); ok { - return rc, nil - } - - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint(fmt.Sprintf("v1/accounts/%d", accountID)), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - type errorResponse struct { - Errors []struct { - Code string `json:"code"` - Message string `json:"message"` - } - } - - var e errorResponse - err = json.NewDecoder(res.Body).Decode(&e) - if err != nil { - return nil, fmt.Errorf("failed to decode error response: %w", err) - } - - if len(e.Errors) == 0 { - return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) - } - - switch e.Errors[0].Code { - case "RECIPIENT_MISSING": - // This is a valid response, we just don't have the account amoungs - // our recipients. - return &RecipientAccount{}, nil - } - - return nil, fmt.Errorf("unexpected status code: %d with err: %v", res.StatusCode, e) - } - - var account RecipientAccount - err = json.NewDecoder(res.Body).Decode(&account) - if err != nil { - return nil, fmt.Errorf("failed to decode account: %w", err) - } - - w.recipientAccountsCache.Add(accountID, &account) - - return &account, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/client/transfers.go b/components/payments/cmd/connectors/internal/connectors/wise/client/transfers.go deleted file mode 100644 index 0de78341f5..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/client/transfers.go +++ /dev/null @@ -1,264 +0,0 @@ -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -type Transfer struct { - ID uint64 `json:"id"` - Reference string `json:"reference"` - Status string `json:"status"` - SourceAccount uint64 `json:"sourceAccount"` - SourceCurrency string `json:"sourceCurrency"` - SourceValue json.Number `json:"sourceValue"` - TargetAccount uint64 `json:"targetAccount"` - TargetCurrency string `json:"targetCurrency"` - TargetValue json.Number `json:"targetValue"` - Business uint64 `json:"business"` - Created string `json:"created"` - //nolint:tagliatelle // allow for clients - CustomerTransactionID string `json:"customerTransactionId"` - Details struct { - Reference string `json:"reference"` - } `json:"details"` - Rate float64 `json:"rate"` - User uint64 `json:"user"` - - SourceBalanceID uint64 `json:"-"` - DestinationBalanceID uint64 `json:"-"` - - CreatedAt time.Time `json:"-"` -} - -func (t *Transfer) UnmarshalJSON(data []byte) error { - type Alias Transfer - - aux := &struct { - Created string `json:"created"` - *Alias - }{ - Alias: (*Alias)(t), - } - - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - var err error - - t.CreatedAt, err = time.Parse("2006-01-02 15:04:05", aux.Created) - if err != nil { - return fmt.Errorf("failed to parse created time: %w", err) - } - - return nil -} - -func (w *Client) GetTransfers(ctx context.Context, profile *Profile) ([]Transfer, error) { - f := connectors.ClientMetrics(ctx, "wise", "list_transfers") - now := time.Now() - defer f(ctx, now) - - var transfers []Transfer - - limit := 10 - offset := 0 - - for { - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint("v1/transfers"), http.NoBody) - if err != nil { - return transfers, err - } - - q := req.URL.Query() - q.Add("limit", fmt.Sprintf("%d", limit)) - q.Add("profile", fmt.Sprintf("%d", profile.ID)) - q.Add("offset", fmt.Sprintf("%d", offset)) - req.URL.RawQuery = q.Encode() - - res, err := w.httpClient.Do(req) - if err != nil { - return transfers, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, unmarshalError(res.StatusCode, res.Body).Error() - } - - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - var transferList []Transfer - - err = json.Unmarshal(body, &transferList) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal transfers: %w", err) - } - - for i, transfer := range transferList { - var sourceProfileID, targetProfileID uint64 - if transfer.SourceAccount != 0 { - recipientAccount, err := w.GetRecipientAccount(ctx, transfer.SourceAccount) - if err != nil { - return nil, fmt.Errorf("failed to get source profile id: %w", err) - } - - sourceProfileID = recipientAccount.Profile - } - - if transfer.TargetAccount != 0 { - recipientAccount, err := w.GetRecipientAccount(ctx, transfer.TargetAccount) - if err != nil { - return nil, fmt.Errorf("failed to get target profile id: %w", err) - } - - targetProfileID = recipientAccount.Profile - } - - // TODO(polo): fetching balances for each transfer is not efficient - // and can be quite long. We should consider caching balances, but - // at the same time we will develop a feature soon to get balances - // for every accounts, so caching is not a solution. - switch { - case sourceProfileID == 0 && targetProfileID == 0: - // Do nothing - case sourceProfileID == targetProfileID && sourceProfileID != 0: - // Same profile id for target and source - balances, err := w.GetBalances(ctx, sourceProfileID) - if err != nil { - return nil, fmt.Errorf("failed to get balances: %w", err) - } - for _, balance := range balances { - if balance.Currency == transfer.SourceCurrency { - transferList[i].SourceBalanceID = balance.ID - } - - if balance.Currency == transfer.TargetCurrency { - transferList[i].DestinationBalanceID = balance.ID - } - } - default: - if sourceProfileID != 0 { - balances, err := w.GetBalances(ctx, sourceProfileID) - if err != nil { - return nil, fmt.Errorf("failed to get balances: %w", err) - } - for _, balance := range balances { - if balance.Currency == transfer.SourceCurrency { - transferList[i].SourceBalanceID = balance.ID - } - } - } - - if targetProfileID != 0 { - balances, err := w.GetBalances(ctx, targetProfileID) - if err != nil { - return nil, fmt.Errorf("failed to get balances: %w", err) - } - for _, balance := range balances { - if balance.Currency == transfer.TargetCurrency { - transferList[i].DestinationBalanceID = balance.ID - } - } - } - - } - } - - transfers = append(transfers, transferList...) - - if len(transferList) < limit { - break - } - - offset += limit - } - - return transfers, nil -} - -func (w *Client) GetTransfer(ctx context.Context, transferID string) (*Transfer, error) { - f := connectors.ClientMetrics(ctx, "wise", "get_transfer") - now := time.Now() - defer f(ctx, now) - - req, err := http.NewRequestWithContext(ctx, - http.MethodGet, w.endpoint("v1/transfers/"+transferID), http.NoBody) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Do(req) - if err != nil { - return nil, err - } - - body, err := io.ReadAll(res.Body) - if err != nil { - res.Body.Close() - - return nil, fmt.Errorf("failed to read response body: %w", err) - } - - if err = res.Body.Close(); err != nil { - return nil, fmt.Errorf("failed to close response body: %w", err) - } - - var transfer Transfer - err = json.Unmarshal(body, &transfer) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal transfer: %w", err) - } - - return &transfer, nil -} - -func (w *Client) CreateTransfer(ctx context.Context, quote Quote, targetAccount uint64, transactionID string) (*Transfer, error) { - metrics.GetMetricsRegistry().ConnectorPSPCalls().Add(ctx, 1, metric.WithAttributes([]attribute.KeyValue{ - attribute.String("connector", "wise"), - attribute.String("operation", "initiate_transfer"), - }...)) - - req, err := json.Marshal(map[string]interface{}{ - "targetAccount": targetAccount, - "quoteUuid": quote.ID.String(), - "customerTransactionId": transactionID, - }) - if err != nil { - return nil, err - } - - res, err := w.httpClient.Post(w.endpoint("v1/transfers"), "application/json", bytes.NewBuffer(req)) - if err != nil { - return nil, err - } - - defer res.Body.Close() - - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode) - } - - var response Transfer - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to get response from transfer: %w", err) - } - - return &response, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/config.go b/components/payments/cmd/connectors/internal/connectors/wise/config.go deleted file mode 100644 index aacab62524..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/config.go +++ /dev/null @@ -1,56 +0,0 @@ -package wise - -import ( - "encoding/json" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/configtemplate" -) - -const ( - defaultPollingPeriod = 2 * time.Minute - pageSize = 100 -) - -type Config struct { - Name string `json:"name" yaml:"name" bson:"name"` - APIKey string `json:"apiKey" yaml:"apiKey" bson:"apiKey"` - PollingPeriod connectors.Duration `json:"pollingPeriod" yaml:"pollingPeriod" bson:"pollingPeriod"` -} - -// String obfuscates sensitive fields and returns a string representation of the config. -// This is used for logging. -func (c Config) String() string { - return "apiKey=***" -} - -func (c Config) Validate() error { - if c.APIKey == "" { - return ErrMissingAPIKey - } - - if c.Name == "" { - return ErrMissingName - } - - return nil -} - -func (c Config) Marshal() ([]byte, error) { - return json.Marshal(c) -} - -func (c Config) ConnectorName() string { - return c.Name -} - -func (c Config) BuildTemplate() (string, configtemplate.Config) { - cfg := configtemplate.NewConfig() - - cfg.AddParameter("name", configtemplate.TypeString, name.String(), false) - cfg.AddParameter("apiKey", configtemplate.TypeString, "", true) - cfg.AddParameter("pollingPeriod", configtemplate.TypeDurationNs, defaultPollingPeriod.String(), false) - - return name.String(), cfg -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/connector.go b/components/payments/cmd/connectors/internal/connectors/wise/connector.go deleted file mode 100644 index db043da8ee..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/connector.go +++ /dev/null @@ -1,137 +0,0 @@ -package wise - -import ( - "context" - - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const name = models.ConnectorProviderWise - -var ( - mainTaskDescriptor = TaskDescriptor{ - Name: "Fetch profiles from client", - Key: taskNameFetchProfiles, - } -) - -type Connector struct { - logger logging.Logger - cfg Config -} - -func newConnector(logger logging.Logger, cfg Config) *Connector { - return &Connector{ - logger: logger.WithFields(map[string]any{ - "component": "connector", - }), - cfg: cfg, - } -} - -func (c *Connector) UpdateConfig(ctx task.ConnectorContext, config models.ConnectorConfigObject) error { - cfg, ok := config.(Config) - if !ok { - return connectors.ErrInvalidConfig - } - - restartTask := c.cfg.PollingPeriod.Duration != cfg.PollingPeriod.Duration - - c.cfg = cfg - - if restartTask { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_STOP_AND_RESTART, - }) - } - - return nil -} - -func (c *Connector) Install(ctx task.ConnectorContext) error { - descriptor, err := models.EncodeTaskDescriptor(mainTaskDescriptor) - if err != nil { - return err - } - - return ctx.Scheduler().Schedule(ctx.Context(), descriptor, models.TaskSchedulerOptions{ - // We want to polling every c.cfg.PollingPeriod.Duration seconds the users - // and their transactions. - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: c.cfg.PollingPeriod.Duration, - // No need to restart this task, since the connector is not existing or - // was uninstalled previously, the task does not exists in the database - RestartOption: models.OPTIONS_RESTART_NEVER, - }) -} - -func (c *Connector) Uninstall(ctx context.Context) error { - return nil -} - -func (c *Connector) Resolve(descriptor models.TaskDescriptor) task.Task { - taskDescriptor, err := models.DecodeTaskDescriptor[TaskDescriptor](descriptor) - if err != nil { - panic(err) - } - - return c.resolveTasks()(taskDescriptor) -} - -func (c *Connector) SupportedCurrenciesAndDecimals() map[string]int { - return supportedCurrenciesWithDecimal -} - -func (c *Connector) InitiatePayment(ctx task.ConnectorContext, transfer *models.TransferInitiation) error { - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Initiate payment", - Key: taskNameInitiatePayment, - TransferID: transfer.ID.String(), - }) - if err != nil { - return err - } - - scheduleOption := models.OPTIONS_RUN_NOW_SYNC - scheduledAt := transfer.ScheduledAt - if !scheduledAt.IsZero() { - scheduleOption = models.OPTIONS_RUN_SCHEDULED_AT - } - - err = ctx.Scheduler().Schedule(ctx.Context(), taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: scheduleOption, - ScheduleAt: scheduledAt, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func (c *Connector) ReversePayment(ctx task.ConnectorContext, transferReversal *models.TransferReversal) error { - return connectors.ErrNotImplemented -} - -func (c *Connector) CreateExternalBankAccount(ctx task.ConnectorContext, bankAccount *models.BankAccount) error { - return connectors.ErrNotImplemented -} - -var _ connectors.Connector = &Connector{} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/currencies.go b/components/payments/cmd/connectors/internal/connectors/wise/currencies.go deleted file mode 100644 index ad2f38fbc3..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/currencies.go +++ /dev/null @@ -1,33 +0,0 @@ -package wise - -import "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - -var ( - // c.f. https://wise.com/help/articles/2897238/which-currencies-can-i-add-keep-and-receive-in-my-wise-account - supportedCurrenciesWithDecimal = map[string]int{ - "AUD": currency.ISO4217Currencies["AUD"], // Australian dollar - "BGN": currency.ISO4217Currencies["BGN"], // Bulgarian lev - "BRL": currency.ISO4217Currencies["BRL"], // Brazilian real - "CAD": currency.ISO4217Currencies["CAD"], // Canadian dollar - "CNY": currency.ISO4217Currencies["CNY"], // Chinese yuan - "CHF": currency.ISO4217Currencies["CHF"], // Swiss franc - "CZK": currency.ISO4217Currencies["CZK"], // Czech koruna - "DKK": currency.ISO4217Currencies["DKK"], // Danish krone - "EUR": currency.ISO4217Currencies["EUR"], // Euro - "GBP": currency.ISO4217Currencies["GBP"], // Pound sterling - "IDR": currency.ISO4217Currencies["IDR"], // Indonesian rupiah - "JPY": currency.ISO4217Currencies["JPY"], // Japanese yen - "MYR": currency.ISO4217Currencies["MYR"], // Malaysian ringgit - "NOK": currency.ISO4217Currencies["NOK"], // Norwegian krone - "NZD": currency.ISO4217Currencies["NZD"], // New Zealand dollar - "PLN": currency.ISO4217Currencies["PLN"], // Polish złoty - "RON": currency.ISO4217Currencies["RON"], // Romanian leu - "SEK": currency.ISO4217Currencies["SEK"], // Swedish krona/kronor - "SGD": currency.ISO4217Currencies["SGD"], // Singapore dollar - "TRY": currency.ISO4217Currencies["TRY"], // Turkish lira - "USD": currency.ISO4217Currencies["USD"], // United States dollar - - // Unsupported currencies - // "HUF": currency.ISO4217Currencies["HUF"], // Hungarian forint - } -) diff --git a/components/payments/cmd/connectors/internal/connectors/wise/errors.go b/components/payments/cmd/connectors/internal/connectors/wise/errors.go deleted file mode 100644 index 894ef089fd..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package wise - -import "github.com/pkg/errors" - -var ( - // ErrMissingTask is returned when the task is missing. - ErrMissingTask = errors.New("task is not implemented") - - // ErrMissingAPIKey is returned when the api key is missing from config. - ErrMissingAPIKey = errors.New("missing apiKey from config") - - // ErrMissingName is returned when the name is missing from config. - ErrMissingName = errors.New("missing name from config") -) diff --git a/components/payments/cmd/connectors/internal/connectors/wise/loader.go b/components/payments/cmd/connectors/internal/connectors/wise/loader.go deleted file mode 100644 index 63cee78c3a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/loader.go +++ /dev/null @@ -1,47 +0,0 @@ -package wise - -import ( - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/gorilla/mux" -) - -type Loader struct{} - -const allowedTasks = 50 - -func (l *Loader) AllowTasks() int { - return allowedTasks -} - -func (l *Loader) Name() models.ConnectorProvider { - return name -} - -func (l *Loader) Load(logger logging.Logger, config Config) connectors.Connector { - return newConnector(logger, config) -} - -func (l *Loader) ApplyDefaults(cfg Config) Config { - if cfg.PollingPeriod.Duration == 0 { - cfg.PollingPeriod.Duration = defaultPollingPeriod - } - - if cfg.Name == "" { - cfg.Name = name.String() - } - - return cfg -} - -func (l *Loader) Router(_ *storage.Storage) *mux.Router { - // Webhooks are not implemented yet - return nil -} - -// NewLoader creates a new loader. -func NewLoader() *Loader { - return &Loader{} -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_profiles.go b/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_profiles.go deleted file mode 100644 index 72ba0a3392..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_profiles.go +++ /dev/null @@ -1,177 +0,0 @@ -package wise - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strconv" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -func taskFetchProfiles(wiseClient *client.Client) task.Task { - return func( - ctx context.Context, - logger logging.Logger, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - ) error { - sp := trace.SpanFromContext(ctx) - sp.SetName("wise.taskFetchProfiles") - sp.SetAttributes( - attribute.String("connectorID", connectorID.String()), - ) - - if err := fetchProfiles(ctx, wiseClient, connectorID, ingester, scheduler); err != nil { - otel.RecordError(sp, err) - return err - } - - return nil - } -} - -func fetchProfiles( - ctx context.Context, - wiseClient *client.Client, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, -) error { - profiles, err := wiseClient.GetProfiles(ctx) - if err != nil { - return err - } - - var descriptors []models.TaskDescriptor - for _, profile := range profiles { - balances, err := wiseClient.GetBalances(ctx, profile.ID) - if err != nil { - return err - } - - if err := ingestAccountsBatch( - ctx, - connectorID, - ingester, - profile.ID, - balances, - ); err != nil { - return err - } - - transferDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch transfers from client by profile", - Key: taskNameFetchTransfers, - ProfileID: profile.ID, - }) - if err != nil { - return err - } - descriptors = append(descriptors, transferDescriptor) - - recipientAccountsDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch recipient accounts from client by profile", - Key: taskNameFetchRecipientAccounts, - ProfileID: profile.ID, - }) - if err != nil { - return err - } - descriptors = append(descriptors, recipientAccountsDescriptor) - } - - for _, descriptor := range descriptors { - err = scheduler.Schedule(ctx, descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - } - - return nil -} - -func ingestAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - profileID uint64, - balances []*client.Balance, -) error { - if len(balances) == 0 { - return nil - } - - accountsBatch := ingestion.AccountBatch{} - balancesBatch := ingestion.BalanceBatch{} - for _, balance := range balances { - raw, err := json.Marshal(balance) - if err != nil { - return err - } - - precision, ok := supportedCurrenciesWithDecimal[balance.Amount.Currency] - if !ok { - continue - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: fmt.Sprintf("%d", balance.ID), - ConnectorID: connectorID, - }, - CreatedAt: balance.CreationTime, - Reference: fmt.Sprintf("%d", balance.ID), - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Amount.Currency), - AccountName: balance.Name, - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "profile_id": strconv.FormatUint(profileID, 10), - }, - RawData: raw, - }) - - amount, err := currency.GetAmountWithPrecisionFromString(balance.Amount.Value.String(), precision) - if err != nil { - return err - } - - now := time.Now() - balancesBatch = append(balancesBatch, &models.Balance{ - AccountID: models.AccountID{ - Reference: fmt.Sprintf("%d", balance.ID), - ConnectorID: connectorID, - }, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, balance.Amount.Currency), - Balance: amount, - CreatedAt: now, - LastUpdatedAt: now, - ConnectorID: connectorID, - }) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return err - } - - if err := ingester.IngestBalances(ctx, balancesBatch, false); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_recipient_accounts.go b/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_recipient_accounts.go deleted file mode 100644 index f005d31b5a..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_recipient_accounts.go +++ /dev/null @@ -1,108 +0,0 @@ -package wise - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -type fetchRecipientAccountsState struct { - LastSeekPosition uint64 `json:"last_seek_position"` -} - -func taskFetchRecipientAccounts(wiseClient *client.Client, profileID uint64) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - resolver task.StateResolver, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "wise.taskFetchRecipientAccounts", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("profileID", strconv.FormatUint(profileID, 10)), - ) - defer span.End() - - state := task.MustResolveTo(ctx, resolver, fetchRecipientAccountsState{}) - - for { - recipientAccounts, err := wiseClient.GetRecipientAccounts(ctx, profileID, pageSize, state.LastSeekPosition) - if err != nil { - // Retryable errors already handled by the function - otel.RecordError(span, err) - return err - } - - if err := ingestRecipientAccountsBatch(ctx, connectorID, ingester, recipientAccounts.Content); err != nil { - // Retryable errors already handled by the function - otel.RecordError(span, err) - return err - } - - if recipientAccounts.SeekPositionForNext == 0 { - // No more data to fetch - break - } - - state.LastSeekPosition = recipientAccounts.SeekPositionForNext - } - - if err := ingester.UpdateTaskState(ctx, state); err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} - -func ingestRecipientAccountsBatch( - ctx context.Context, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - accounts []*client.RecipientAccount, -) error { - accountsBatch := ingestion.AccountBatch{} - for _, account := range accounts { - raw, err := json.Marshal(account) - if err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - accountsBatch = append(accountsBatch, &models.Account{ - ID: models.AccountID{ - Reference: fmt.Sprintf("%d", account.ID), - ConnectorID: connectorID, - }, - CreatedAt: time.Now(), - Reference: fmt.Sprintf("%d", account.ID), - ConnectorID: connectorID, - DefaultAsset: currency.FormatAsset(supportedCurrenciesWithDecimal, account.Currency), - AccountName: account.Name.FullName, - Type: models.AccountTypeExternal, - RawData: raw, - }) - } - - if err := ingester.IngestAccounts(ctx, accountsBatch); err != nil { - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_transfers.go b/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_transfers.go deleted file mode 100644 index 2e454112a8..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_fetch_transfers.go +++ /dev/null @@ -1,146 +0,0 @@ -package wise - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "go.opentelemetry.io/otel/attribute" -) - -func taskFetchTransfers(wiseClient *client.Client, profileID uint64) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "wise.taskFetchTransfers", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("profileID", strconv.FormatUint(profileID, 10)), - ) - defer span.End() - - if err := fetchTransfers(ctx, wiseClient, profileID, connectorID, scheduler, ingester); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func fetchTransfers( - ctx context.Context, - wiseClient *client.Client, - profileID uint64, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ingester ingestion.Ingester, -) error { - transfers, err := wiseClient.GetTransfers(ctx, &client.Profile{ - ID: profileID, - }) - if err != nil { - return err - } - - if len(transfers) == 0 { - return nil - } - - var ( - // accountBatch ingestion.AccountBatch - paymentBatch ingestion.PaymentBatch - ) - - for _, transfer := range transfers { - - var rawData json.RawMessage - - rawData, err = json.Marshal(transfer) - if err != nil { - return fmt.Errorf("failed to marshal transfer: %w", err) - } - - precision, ok := supportedCurrenciesWithDecimal[transfer.TargetCurrency] - if !ok { - continue - } - - amount, err := currency.GetAmountWithPrecisionFromString(transfer.TargetValue.String(), precision) - if err != nil { - return err - } - - batchElement := ingestion.PaymentBatchElement{ - Payment: &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: fmt.Sprintf("%d", transfer.ID), - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - CreatedAt: transfer.CreatedAt, - Reference: fmt.Sprintf("%d", transfer.ID), - ConnectorID: connectorID, - Type: models.PaymentTypeTransfer, - Status: matchTransferStatus(transfer.Status), - Scheme: models.PaymentSchemeOther, - Amount: amount, - Asset: currency.FormatAsset(supportedCurrenciesWithDecimal, transfer.TargetCurrency), - RawData: rawData, - }, - } - - if transfer.SourceBalanceID != 0 { - batchElement.Payment.SourceAccountID = &models.AccountID{ - Reference: fmt.Sprintf("%d", transfer.SourceBalanceID), - ConnectorID: connectorID, - } - } - - if transfer.DestinationBalanceID != 0 { - batchElement.Payment.DestinationAccountID = &models.AccountID{ - Reference: fmt.Sprintf("%d", transfer.DestinationBalanceID), - ConnectorID: connectorID, - } - } - - paymentBatch = append(paymentBatch, batchElement) - } - - if err := ingester.IngestPayments(ctx, paymentBatch); err != nil { - return err - } - - return nil -} - -func matchTransferStatus(status string) models.PaymentStatus { - switch status { - case "incoming_payment_waiting", "incoming_payment_initiated", "processing": - return models.PaymentStatusPending - case "funds_converted", "outgoing_payment_sent": - return models.PaymentStatusSucceeded - case "bounced_back", "funds_refunded": - return models.PaymentStatusFailed - case "cancelled": - return models.PaymentStatusCancelled - } - - return models.PaymentStatusOther -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_main.go b/components/payments/cmd/connectors/internal/connectors/wise/task_main.go deleted file mode 100644 index ea7cc20f30..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_main.go +++ /dev/null @@ -1,50 +0,0 @@ -package wise - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" -) - -// taskMain is the main task of the connector. It launches the other tasks. -func taskMain() task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - scheduler task.Scheduler, - ) error { - ctx, span := connectors.StartSpan( - ctx, - "wise.taskMain", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - ) - defer span.End() - - taskUsers, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Fetch users from client", - Key: taskNameFetchProfiles, - }) - if err != nil { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - err = scheduler.Schedule(ctx, taskUsers, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - otel.RecordError(span, err) - return errors.Wrap(task.ErrRetryable, err.Error()) - } - - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_payments.go b/components/payments/cmd/connectors/internal/connectors/wise/task_payments.go deleted file mode 100644 index e46b33cf09..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_payments.go +++ /dev/null @@ -1,341 +0,0 @@ -package wise - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "time" - - "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - - "github.com/formancehq/go-libs/contextutil" - "github.com/formancehq/payments/cmd/connectors/internal/connectors" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/currency" - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise/client" - "github.com/formancehq/payments/cmd/connectors/internal/ingestion" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/cmd/connectors/internal/task" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/internal/otel" -) - -func taskInitiatePayment( - wiseClient *client.Client, - transferID string, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "wise.taskInitiatePayment", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, true) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := initiatePayment(ctx, wiseClient, transfer, connectorID, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func initiatePayment( - ctx context.Context, - wiseClient *client.Client, - transfer *models.TransferInitiation, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var paymentID *models.PaymentID - defer func() { - if err != nil { - ctx, cancel := contextutil.Detached(ctx) - defer cancel() - _ = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, err.Error(), time.Now()) - } - }() - - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessing, "", time.Now()) - if err != nil { - return err - } - - if transfer.SourceAccount == nil { - err = errors.New("missing source account") - return err - } - - if transfer.SourceAccount.Type == models.AccountTypeExternal { - err = errors.New("payin not implemented: source account must be an internal account") - return err - } - - profileID, ok := transfer.SourceAccount.Metadata["profile_id"] - if !ok || profileID == "" { - err = errors.New("missing user_id in source account metadata") - return err - } - - var curr string - var precision int - curr, precision, err = currency.GetCurrencyAndPrecisionFromAsset(supportedCurrenciesWithDecimal, transfer.Asset) - if err != nil { - return err - } - - amount, err := currency.GetStringAmountFromBigIntWithPrecision(transfer.Amount, precision) - if err != nil { - return err - } - - quote, err := wiseClient.CreateQuote(ctx, profileID, curr, json.Number(amount)) - if err != nil { - return err - } - - var connectorPaymentID uint64 - var paymentType models.PaymentType - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - switch transfer.DestinationAccount.Type { - case models.AccountTypeInternal: - // Transfer between internal accounts - destinationAccount, err := strconv.ParseUint(transfer.DestinationAccount.Metadata["profile_id"], 10, 64) - if err != nil { - return err - } - - var resp *client.Transfer - resp, err = wiseClient.CreateTransfer(ctx, quote, destinationAccount, fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments))) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypeTransfer - case models.AccountTypeExternal: - // Payout to an external account - - destinationAccount, err := strconv.ParseUint(transfer.DestinationAccount.Reference, 10, 64) - if err != nil { - return err - } - - var resp *client.Payout - resp, err = wiseClient.CreatePayout(ctx, quote, destinationAccount, fmt.Sprintf("%s_%d", transfer.ID.Reference, len(transfer.RelatedAdjustments))) - if err != nil { - return err - } - - connectorPaymentID = resp.ID - paymentType = models.PaymentTypePayOut - } - - paymentID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: strconv.FormatUint(connectorPaymentID, 10), - Type: paymentType, - }, - ConnectorID: connectorID, - } - err = ingester.AddTransferInitiationPaymentID(ctx, transfer, paymentID, time.Now()) - if err != nil { - return err - } - - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: 1, - }) - if err != nil { - return err - } - - ctx, _ = contextutil.DetachedWithTimeout(ctx, 10*time.Second) - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - - return nil -} - -func taskUpdatePaymentStatus( - wiseClient *client.Client, - transferID string, - pID string, - attempt int, -) task.Task { - return func( - ctx context.Context, - taskID models.TaskID, - connectorID models.ConnectorID, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, - ) error { - paymentID := models.MustPaymentIDFromString(pID) - transferInitiationID := models.MustTransferInitiationIDFromString(transferID) - - ctx, span := connectors.StartSpan( - ctx, - "wise.taskUpdatePaymentStatus", - attribute.String("connectorID", connectorID.String()), - attribute.String("taskID", taskID.String()), - attribute.String("transferID", transferID), - attribute.String("paymentID", pID), - attribute.Int("attempt", attempt), - attribute.String("reference", transferInitiationID.Reference), - ) - defer span.End() - - transfer, err := getTransfer(ctx, storageReader, transferInitiationID, false) - if err != nil { - otel.RecordError(span, err) - return err - } - - if err := updatePaymentStatus(ctx, wiseClient, transfer, paymentID, attempt, ingester, scheduler, storageReader); err != nil { - otel.RecordError(span, err) - return err - } - - return nil - } -} - -func updatePaymentStatus( - ctx context.Context, - wiseClient *client.Client, - transfer *models.TransferInitiation, - paymentID *models.PaymentID, - attempt int, - ingester ingestion.Ingester, - scheduler task.Scheduler, - storageReader storage.Reader, -) error { - var err error - var status string - switch transfer.Type { - case models.TransferInitiationTypeTransfer: - var resp *client.Transfer - resp, err = wiseClient.GetTransfer(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - case models.TransferInitiationTypePayout: - var resp *client.Payout - resp, err = wiseClient.GetPayout(ctx, paymentID.Reference) - if err != nil { - return err - } - - status = resp.Status - } - - switch status { - case "incoming_payment_waiting", - "incoming_payment_initiated", - "processing", - "funds_converted", - "bounced_back", - "unknown": - taskDescriptor, err := models.EncodeTaskDescriptor(TaskDescriptor{ - Name: "Update transfer initiation status", - Key: taskNameUpdatePaymentStatus, - TransferID: transfer.ID.String(), - PaymentID: paymentID.String(), - Attempt: attempt + 1, - }) - if err != nil { - return err - } - - err = scheduler.Schedule(ctx, taskDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_IN_DURATION, - Duration: 2 * time.Minute, - RestartOption: models.OPTIONS_RESTART_IF_NOT_ACTIVE, - }) - if err != nil && !errors.Is(err, task.ErrAlreadyScheduled) { - return err - } - case "outgoing_payment_sent", "funds_refunded": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusProcessed, "", time.Now()) - if err != nil { - return err - } - - return nil - case "charged_back", "cancelled": - err = ingester.UpdateTransferInitiationPaymentsStatus(ctx, transfer, paymentID, models.TransferInitiationStatusFailed, "", time.Now()) - if err != nil { - return err - } - - return nil - } - - return nil -} - -func getTransfer( - ctx context.Context, - reader storage.Reader, - transferID models.TransferInitiationID, - expand bool, -) (*models.TransferInitiation, error) { - transfer, err := reader.ReadTransferInitiation(ctx, transferID) - if err != nil { - return nil, err - } - - if expand { - if transfer.SourceAccountID != nil { - sourceAccount, err := reader.GetAccount(ctx, transfer.SourceAccountID.String()) - if err != nil { - return nil, err - } - transfer.SourceAccount = sourceAccount - } - - destinationAccount, err := reader.GetAccount(ctx, transfer.DestinationAccountID.String()) - if err != nil { - return nil, err - } - transfer.DestinationAccount = destinationAccount - } - - return transfer, nil -} diff --git a/components/payments/cmd/connectors/internal/connectors/wise/task_resolve.go b/components/payments/cmd/connectors/internal/connectors/wise/task_resolve.go deleted file mode 100644 index 6e07953a9e..0000000000 --- a/components/payments/cmd/connectors/internal/connectors/wise/task_resolve.go +++ /dev/null @@ -1,53 +0,0 @@ -package wise - -import ( - "fmt" - - "github.com/formancehq/payments/cmd/connectors/internal/connectors/wise/client" - "github.com/formancehq/payments/cmd/connectors/internal/task" -) - -const ( - taskNameMain = "main" - taskNameFetchTransfers = "fetch-transfers" - taskNameFetchProfiles = "fetch-profiles" - taskNameFetchRecipientAccounts = "fetch-recipient-accounts" - taskNameInitiatePayment = "initiate-payment" - taskNameUpdatePaymentStatus = "update-payment-status" -) - -// TaskDescriptor is the definition of a task. -type TaskDescriptor struct { - Name string `json:"name" yaml:"name" bson:"name"` - Key string `json:"key" yaml:"key" bson:"key"` - ProfileID uint64 `json:"profileID" yaml:"profileID" bson:"profileID"` - TransferID string `json:"transferID" yaml:"transferID" bson:"transferID"` - PaymentID string `json:"paymentID" yaml:"paymentID" bson:"paymentID"` - Attempt int `json:"attempt" yaml:"attempt" bson:"attempt"` -} - -func (c *Connector) resolveTasks() func(taskDefinition TaskDescriptor) task.Task { - client := client.NewClient(c.cfg.APIKey) - - return func(taskDefinition TaskDescriptor) task.Task { - switch taskDefinition.Key { - case taskNameMain: - return taskMain() - case taskNameFetchProfiles: - return taskFetchProfiles(client) - case taskNameFetchRecipientAccounts: - return taskFetchRecipientAccounts(client, taskDefinition.ProfileID) - case taskNameFetchTransfers: - return taskFetchTransfers(client, taskDefinition.ProfileID) - case taskNameInitiatePayment: - return taskInitiatePayment(client, taskDefinition.TransferID) - case taskNameUpdatePaymentStatus: - return taskUpdatePaymentStatus(client, taskDefinition.TransferID, taskDefinition.PaymentID, taskDefinition.Attempt) - } - - // This should never happen. - return func() error { - return fmt.Errorf("key '%s': %w", taskDefinition.Key, ErrMissingTask) - } - } -} diff --git a/components/payments/cmd/connectors/internal/ingestion/accounts.go b/components/payments/cmd/connectors/internal/ingestion/accounts.go deleted file mode 100644 index 05b7f37963..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/accounts.go +++ /dev/null @@ -1,67 +0,0 @@ -package ingestion - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type AccountBatch []*models.Account - -type AccountIngesterFn func(ctx context.Context, batch AccountBatch, commitState any) error - -func (fn AccountIngesterFn) IngestAccounts(ctx context.Context, batch AccountBatch, commitState any) error { - return fn(ctx, batch, commitState) -} - -func (i *DefaultIngester) IngestAccounts(ctx context.Context, batch AccountBatch) error { - startingAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "startingAt": startingAt, - }).Debugf("Ingest accounts batch") - - idsInserted, err := i.store.UpsertAccounts(ctx, batch) - if err != nil { - return fmt.Errorf("error upserting accounts: %w", err) - } - - idsInsertedMap := make(map[string]struct{}, len(idsInserted)) - for idx := range idsInserted { - idsInsertedMap[idsInserted[idx].String()] = struct{}{} - } - - for accountIdx := range batch { - _, ok := idsInsertedMap[batch[accountIdx].ID.String()] - if !ok { - // No need to publish an event for an already existing payment - continue - } - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedAccounts(i.provider, batch[accountIdx]), - ), - ); err != nil { - logging.FromContext(ctx).Errorf("Publishing message: %w", err) - } - } - - endedAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "endedAt": endedAt, - "latency": endedAt.Sub(startingAt).String(), - }).Debugf("Accounts batch ingested") - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/balances.go b/components/payments/cmd/connectors/internal/ingestion/balances.go deleted file mode 100644 index 334a99aebf..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/balances.go +++ /dev/null @@ -1,55 +0,0 @@ -package ingestion - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type BalanceBatch []*models.Balance - -type BalanceIngesterFn func(ctx context.Context, batch BalanceBatch) error - -func (fn BalanceIngesterFn) IngestBalances(ctx context.Context, batch BalanceBatch) error { - return fn(ctx, batch) -} - -func (i *DefaultIngester) IngestBalances(ctx context.Context, batch BalanceBatch, checkIfAccountExists bool) error { - startingAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "startingAt": startingAt, - }).Debugf("Ingest balances batch") - - if err := i.store.InsertBalances(ctx, batch, checkIfAccountExists); err != nil { - return fmt.Errorf("error inserting balances: %w", err) - } - - for _, balance := range batch { - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedBalances(balance), - ), - ); err != nil { - logging.FromContext(ctx).Errorf("Publishing message: %w", err) - } - } - - endedAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "endedAt": endedAt, - "latency": endedAt.Sub(startingAt).String(), - }).Debugf("Accounts batch ingested") - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/bank_account.go b/components/payments/cmd/connectors/internal/ingestion/bank_account.go deleted file mode 100644 index c9a9e7b252..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/bank_account.go +++ /dev/null @@ -1,39 +0,0 @@ -package ingestion - -import ( - "context" - "time" - - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" -) - -func (i *DefaultIngester) LinkBankAccountWithAccount(ctx context.Context, bankAccount *models.BankAccount, accountID *models.AccountID) error { - adjustment := &models.BankAccountRelatedAccount{ - ID: uuid.New(), - CreatedAt: time.Now().UTC(), - BankAccountID: bankAccount.ID, - ConnectorID: accountID.ConnectorID, - AccountID: *accountID, - } - - if err := i.store.AddBankAccountRelatedAccount(ctx, adjustment); err != nil { - return err - } - - bankAccount.RelatedAccounts = append(bankAccount.RelatedAccounts, adjustment) - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedBankAccounts(bankAccount), - ), - ); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/ingester.go b/components/payments/cmd/connectors/internal/ingestion/ingester.go deleted file mode 100644 index 0c54c7d5a3..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/ingester.go +++ /dev/null @@ -1,65 +0,0 @@ -package ingestion - -import ( - "context" - "encoding/json" - "time" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" -) - -type Ingester interface { - IngestAccounts(ctx context.Context, batch AccountBatch) error - IngestPayments(ctx context.Context, batch PaymentBatch) error - IngestBalances(ctx context.Context, batch BalanceBatch, checkIfAccountExists bool) error - UpdateTaskState(ctx context.Context, state any) error - UpdateTransferInitiationPayment(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, status models.TransferInitiationStatus, errorMessage string, updatedAt time.Time) error - UpdateTransferInitiationPaymentsStatus(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, status models.TransferInitiationStatus, errorMessage string, updatedAt time.Time) error - UpdateTransferReversalStatus(ctx context.Context, transfer *models.TransferInitiation, transferReversal *models.TransferReversal) error - AddTransferInitiationPaymentID(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, updatedAt time.Time) error - LinkBankAccountWithAccount(ctx context.Context, bankAccount *models.BankAccount, accountID *models.AccountID) error -} - -type DefaultIngester struct { - provider models.ConnectorProvider - connectorID models.ConnectorID - store Store - descriptor models.TaskDescriptor - publisher message.Publisher - messages *messages.Messages -} - -type Store interface { - UpsertAccounts(ctx context.Context, accounts []*models.Account) ([]models.AccountID, error) - UpsertPayments(ctx context.Context, payments []*models.Payment) ([]*models.PaymentID, error) - UpsertPaymentsAdjustments(ctx context.Context, paymentsAdjustment []*models.PaymentAdjustment) error - UpsertPaymentsMetadata(ctx context.Context, paymentsMetadata []*models.PaymentMetadata) error - InsertBalances(ctx context.Context, balances []*models.Balance, checkIfAccountExists bool) error - UpdateTaskState(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, state json.RawMessage) error - UpdateTransferInitiationPaymentsStatus(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, adjustment *models.TransferInitiationAdjustment) error - UpdateTransferReversalStatus(ctx context.Context, transfer *models.TransferInitiation, transferReversal *models.TransferReversal, adjustment *models.TransferInitiationAdjustment) error - AddTransferInitiationPaymentID(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, updatedAt time.Time, metadata map[string]string) error - AddBankAccountRelatedAccount(ctx context.Context, adjustment *models.BankAccountRelatedAccount) error -} - -func NewDefaultIngester( - provider models.ConnectorProvider, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, - repo Store, - publisher message.Publisher, - messages *messages.Messages, -) *DefaultIngester { - return &DefaultIngester{ - provider: provider, - connectorID: connectorID, - descriptor: descriptor, - store: repo, - publisher: publisher, - messages: messages, - } -} - -var _ Ingester = (*DefaultIngester)(nil) diff --git a/components/payments/cmd/connectors/internal/ingestion/ingester_test.go b/components/payments/cmd/connectors/internal/ingestion/ingester_test.go deleted file mode 100644 index 9c1f1895ac..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/ingester_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package ingestion - -import ( - "context" - "encoding/json" - "time" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/formancehq/payments/internal/models" -) - -type MockStore struct { - paymentIDsNotModified map[string]struct{} -} - -func NewMockStore() *MockStore { - return &MockStore{ - paymentIDsNotModified: make(map[string]struct{}), - } -} - -func (m *MockStore) WithPaymentIDsNotModified(paymentsIDs []models.PaymentID) *MockStore { - for _, id := range paymentsIDs { - m.paymentIDsNotModified[id.String()] = struct{}{} - } - return m -} - -func (m *MockStore) UpsertAccounts(ctx context.Context, accounts []*models.Account) ([]models.AccountID, error) { - return nil, nil -} - -func (m *MockStore) UpsertPayments(ctx context.Context, payments []*models.Payment) ([]*models.PaymentID, error) { - ids := make([]*models.PaymentID, 0, len(payments)) - for _, payment := range payments { - if _, ok := m.paymentIDsNotModified[payment.ID.String()]; !ok { - ids = append(ids, &payment.ID) - } - } - - return ids, nil -} - -func (m *MockStore) UpsertPaymentsAdjustments(ctx context.Context, adjustments []*models.PaymentAdjustment) error { - return nil -} - -func (m *MockStore) UpsertPaymentsMetadata(ctx context.Context, metadata []*models.PaymentMetadata) error { - return nil -} - -func (m *MockStore) InsertBalances(ctx context.Context, balances []*models.Balance, checkIfAccountExists bool) error { - return nil -} - -func (m *MockStore) UpdateTaskState(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, state json.RawMessage) error { - return nil -} - -func (m *MockStore) UpdateTransferInitiationPaymentsStatus(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, adjustment *models.TransferInitiationAdjustment) error { - return nil -} - -func (m *MockStore) UpdateTransferReversalStatus(ctx context.Context, transfer *models.TransferInitiation, transferReversal *models.TransferReversal, adjustment *models.TransferInitiationAdjustment) error { - return nil -} - -func (m *MockStore) AddTransferInitiationPaymentID(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, updatedAt time.Time, metadata map[string]string) error { - return nil -} - -func (m *MockStore) AddBankAccountRelatedAccount(ctx context.Context, adjustment *models.BankAccountRelatedAccount) error { - return nil -} - -type MockPublisher struct { - messages chan *message.Message -} - -func NewMockPublisher() *MockPublisher { - return &MockPublisher{ - messages: make(chan *message.Message, 100), - } -} - -func (m *MockPublisher) Publish(topic string, messages ...*message.Message) error { - for _, msg := range messages { - m.messages <- msg - } - - return nil -} - -func (m *MockPublisher) Close() error { - close(m.messages) - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/payments.go b/components/payments/cmd/connectors/internal/ingestion/payments.go deleted file mode 100644 index 856bafef5b..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/payments.go +++ /dev/null @@ -1,110 +0,0 @@ -package ingestion - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type PaymentBatchElement struct { - Payment *models.Payment - Adjustment *models.PaymentAdjustment -} - -type PaymentBatch []PaymentBatchElement - -type IngesterFn func(ctx context.Context, batch PaymentBatch, commitState any) error - -func (fn IngesterFn) IngestPayments(ctx context.Context, batch PaymentBatch, commitState any) error { - return fn(ctx, batch, commitState) -} - -func (i *DefaultIngester) IngestPayments( - ctx context.Context, - batch PaymentBatch, -) error { - startingAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "startingAt": startingAt, - }).Debugf("Ingest batch") - - var allPayments []*models.Payment //nolint:prealloc // length is unknown - var allMetadata []*models.PaymentMetadata - var allAdjustments []*models.PaymentAdjustment - - for batchIdx := range batch { - payment := batch[batchIdx].Payment - adjustment := batch[batchIdx].Adjustment - - if payment != nil { - allPayments = append(allPayments, payment) - - for _, data := range payment.Metadata { - data.Changelog = append(data.Changelog, - models.MetadataChangelog{ - CreatedAt: time.Now(), - Value: data.Value, - }) - - allMetadata = append(allMetadata, data) - } - } - - if adjustment != nil && adjustment.Reference != "" { - allAdjustments = append(allAdjustments, adjustment) - } - } - - // Insert first all payments - idsInserted, err := i.store.UpsertPayments(ctx, allPayments) - if err != nil { - return fmt.Errorf("error upserting payments: %w", err) - } - - // Then insert all metadata - if err := i.store.UpsertPaymentsMetadata(ctx, allMetadata); err != nil { - return fmt.Errorf("error upserting payments metadata: %w", err) - } - - // Then insert all adjustments - if err := i.store.UpsertPaymentsAdjustments(ctx, allAdjustments); err != nil { - return fmt.Errorf("error upserting payments adjustments: %w", err) - } - - idsInsertedMap := make(map[string]struct{}, len(idsInserted)) - for idx := range idsInserted { - idsInsertedMap[idsInserted[idx].String()] = struct{}{} - } - - for paymentIdx := range allPayments { - _, ok := idsInsertedMap[allPayments[paymentIdx].ID.String()] - if !ok { - // No need to publish an event for an already existing payment - continue - } - err = i.publisher.Publish(events.TopicPayments, - publish.NewMessage(ctx, i.messages.NewEventSavedPayments(i.provider, allPayments[paymentIdx]))) - if err != nil { - logging.FromContext(ctx).Errorf("Publishing message: %w", err) - - continue - } - } - - endedAt := time.Now() - - logging.FromContext(ctx).WithFields(map[string]interface{}{ - "size": len(batch), - "endedAt": endedAt, - "latency": endedAt.Sub(startingAt).String(), - }).Debugf("Batch ingested") - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/payments_test.go b/components/payments/cmd/connectors/internal/ingestion/payments_test.go deleted file mode 100644 index 73d47dd09f..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/payments_test.go +++ /dev/null @@ -1,223 +0,0 @@ -package ingestion - -import ( - "context" - "encoding/json" - "math/big" - "testing" - "time" - - "github.com/formancehq/payments/internal/messages" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - connectorID = models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - - acc1 = models.AccountID{ - Reference: "acc1", - ConnectorID: connectorID, - } - acc2 = models.AccountID{ - Reference: "acc2", - ConnectorID: connectorID, - } - - p1 = &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p1", - Type: models.PaymentTypePayIn, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 4, 55, 0, 0, time.UTC), - Reference: "p1", - Amount: big.NewInt(100), - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusCancelled, - Scheme: models.PaymentSchemeA2A, - Asset: models.Asset("USD/2"), - SourceAccountID: &acc1, - } - - p2 = &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p2", - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 4, 54, 0, 0, time.UTC), - Reference: "p2", - Amount: big.NewInt(150), - Type: models.PaymentTypeTransfer, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeApplePay, - Asset: models.Asset("EUR/2"), - DestinationAccountID: &acc2, - } - - p3 = &models.Payment{ - ID: models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "p3", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - }, - ConnectorID: connectorID, - CreatedAt: time.Date(2023, 11, 14, 4, 53, 0, 0, time.UTC), - Reference: "p3", - Amount: big.NewInt(200), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusPending, - Scheme: models.PaymentSchemeCardMasterCard, - Asset: models.Asset("USD/2"), - SourceAccountID: &acc1, - DestinationAccountID: &acc2, - } -) - -type linkPayload struct { - Name string `json:"name"` - URI string `json:"uri"` -} -type paymentMessagePayload struct { - Payload struct { - ID string `json:"id"` - Links []linkPayload `json:"links"` - } `json:"payload"` -} - -func TestIngestPayments(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - batch PaymentBatch - paymentIDsNotModified []models.PaymentID - requiredPublishedPaymentIDs []models.PaymentID - } - - testCases := []testCase{ - { - name: "nominal", - batch: PaymentBatch{ - { - Payment: p1, - }, - { - Payment: p2, - }, - { - Payment: p3, - }, - }, - paymentIDsNotModified: []models.PaymentID{}, - requiredPublishedPaymentIDs: []models.PaymentID{p1.ID, p2.ID, p3.ID}, - }, - { - name: "only one payment upserted, should publish only one message", - batch: PaymentBatch{ - { - Payment: p1, - }, - { - Payment: p2, - }, - { - Payment: p3, - }, - }, - paymentIDsNotModified: []models.PaymentID{p1.ID, p2.ID}, - requiredPublishedPaymentIDs: []models.PaymentID{p3.ID}, - }, - { - name: "all payments are not modified, should not publish any message", - batch: PaymentBatch{ - { - Payment: p1, - }, - { - Payment: p2, - }, - { - Payment: p3, - }, - }, - paymentIDsNotModified: []models.PaymentID{p1.ID, p2.ID, p3.ID}, - requiredPublishedPaymentIDs: []models.PaymentID{}, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - publisher := NewMockPublisher() - - ingester := NewDefaultIngester( - models.ConnectorProviderDummyPay, - connectorID, - nil, - NewMockStore().WithPaymentIDsNotModified(tc.paymentIDsNotModified), - publisher, - messages.NewMessages(""), - ) - - err := ingester.IngestPayments(context.Background(), tc.batch) - publisher.Close() - require.NoError(t, err) - - require.Len(t, publisher.messages, len(tc.requiredPublishedPaymentIDs)) - i := 0 - for msg := range publisher.messages { - var payload paymentMessagePayload - require.NoError(t, json.Unmarshal(msg.Payload, &payload)) - require.Equal(t, tc.requiredPublishedPaymentIDs[i].String(), payload.Payload.ID) - - var expectedLinks []linkPayload - p := getPayment(tc.requiredPublishedPaymentIDs[i]) - if p == nil { - continue - } - if p.SourceAccountID != nil { - expectedLinks = append(expectedLinks, linkPayload{ - Name: "source_account", - URI: "/api/payments/accounts/" + p.SourceAccountID.String(), - }) - } - if p.DestinationAccountID != nil { - expectedLinks = append(expectedLinks, linkPayload{ - Name: "destination_account", - URI: "/api/payments/accounts/" + p.DestinationAccountID.String(), - }) - } - require.Equal(t, expectedLinks, payload.Payload.Links) - - i++ - } - }) - } -} - -func getPayment(id models.PaymentID) *models.Payment { - switch id { - case p1.ID: - return p1 - case p2.ID: - return p2 - case p3.ID: - return p3 - default: - return nil - } -} diff --git a/components/payments/cmd/connectors/internal/ingestion/task.go b/components/payments/cmd/connectors/internal/ingestion/task.go deleted file mode 100644 index b8588071a1..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/task.go +++ /dev/null @@ -1,20 +0,0 @@ -package ingestion - -import ( - "context" - "encoding/json" - "fmt" -) - -func (i *DefaultIngester) UpdateTaskState(ctx context.Context, state any) error { - taskState, err := json.Marshal(state) - if err != nil { - return fmt.Errorf("error marshaling task state: %w", err) - } - - if err = i.store.UpdateTaskState(ctx, i.connectorID, i.descriptor, taskState); err != nil { - return fmt.Errorf("error updating task state: %w", err) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/test.json b/components/payments/cmd/connectors/internal/ingestion/test.json deleted file mode 100644 index 2c806bb71f..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/test.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"PAYMENT":[{"amount":300,"asset":"EUR/2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-10-31T15:01:10Z","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwNzYyMTg3OSIsIlR5cGUiOiJUUkFOU0ZFUiJ9","initialAmount":300,"provider":"MANGOPAY","reference":"207621879","scheme":"other","status":"SUCCEEDED","type":"TRANSFER"},{"amount":300,"asset":"EUR/2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-10-12T15:15:00Z","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwNjEzODczOSIsIlR5cGUiOiJUUkFOU0ZFUiJ9","initialAmount":300,"provider":"MANGOPAY","reference":"206138739","scheme":"other","status":"SUCCEEDED","type":"TRANSFER"},{"amount":300,"asset":"EUR/2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-10-11T15:45:41Z","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwNjAzMTEyOSIsIlR5cGUiOiJUUkFOU0ZFUiJ9","initialAmount":300,"provider":"MANGOPAY","reference":"206031129","scheme":"other","status":"SUCCEEDED","type":"TRANSFER"},{"amount":300,"asset":"EUR/2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-10-12T15:09:17Z","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwNjEzODIwOCIsIlR5cGUiOiJUUkFOU0ZFUiJ9","initialAmount":300,"provider":"MANGOPAY","reference":"206138208","scheme":"other","status":"SUCCEEDED","type":"TRANSFER"},{"amount":300,"asset":"EUR/2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-10-03T13:58:43Z","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwNTE4Nzg0MSIsIlR5cGUiOiJUUkFOU0ZFUiJ9","initialAmount":300,"provider":"MANGOPAY","reference":"205187841","scheme":"other","status":"SUCCEEDED","type":"TRANSFER"}],"PAYMENT_ACCOUNT":[{"accountName":"Joe Blogs","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-08-14T09:26:07Z","defaultAsset":"","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwMDUxODA5NSJ9","provider":"MANGOPAY","reference":"200518095","type":"EXTERNAL"},{"accountName":"My big project transfer","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-08-28T09:58:34Z","defaultAsset":"EUR/2","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwMTY2MTE2NiJ9","provider":"MANGOPAY","reference":"201661166","type":"INTERNAL"},{"accountName":"My big project 2","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-07-25T11:48:44Z","defaultAsset":"USD/2","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjE5ODUzNjY4NSJ9","provider":"MANGOPAY","reference":"198536685","type":"INTERNAL"},{"accountName":"My big project","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2021-03-25T15:11:17Z","defaultAsset":"EUR/2","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjEwNDg1ODc3OCJ9","provider":"MANGOPAY","reference":"104858778","type":"INTERNAL"},{"accountName":"Joe Blogs","connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNzg0NmFkYTAtN2UyNy00MDFiLTg3MDMtOGJhNDZiYzYwYTQ2In0=","createdAt":"2023-08-14T09:26:07Z","defaultAsset":"","id":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNzg0NmFkYTAtN2UyNy00MDFiLTg3MDMtOGJhNDZiYzYwYTQ2In0sIlJlZmVyZW5jZSI6IjIwMDUxODA5NSJ9","reference":"200518095","type":"EXTERNAL"}],"PAYMENT_BALANCE":[{"accountID":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjIwMTY2MTE2NiJ9","asset":"EUR/2","balance":30004700,"connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-11-09T17:22:47.330609498Z"},{"accountID":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0sIlJlZmVyZW5jZSI6IjE5ODUzNjY4NSJ9","asset":"USD/2","balance":10058699,"connectorId":"eyJQcm92aWRlciI6Ik1BTkdPUEFZIiwiUmVmZXJlbmNlIjoiNjYwNjc1ODktMDkxZC00YTQxLTg1ZTctYzEyYTRhMWY5ZGY2In0=","createdAt":"2023-11-09T17:22:47.330566616Z"},{"accountID":"eyJDb25uZWN0b3JJRCI6eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6ImYxMmEzMTU4LTRjNzMtNDIzNS04MjY0LWIwMjUxZmExOTgxZSJ9LCJSZWZlcmVuY2UiOiJBMTIxNkdSMSJ9","asset":"GBP/2","balance":28200,"connectorId":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6ImYxMmEzMTU4LTRjNzMtNDIzNS04MjY0LWIwMjUxZmExOTgxZSJ9","createdAt":"2023-11-09T15:40:33.861824482Z"},[{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjE2RUcyIn0=","asset":"GBP/2","balance":3802050,"createdAt":"2023-11-08T12:50:52.121763165Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjE2R1IxIn0=","asset":"GBP/2","balance":28200,"createdAt":"2023-11-08T12:50:52.1217816Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5OU4ifQ==","asset":"GBP/2","balance":69950,"createdAt":"2023-11-08T12:50:52.121792602Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5OVEifQ==","asset":"GBP/2","balance":1099400,"createdAt":"2023-11-08T12:50:52.121805054Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5QjgifQ==","asset":"GBP/2","balance":0,"createdAt":"2023-11-08T12:50:52.12181514Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5QzIifQ==","asset":"GBP/2","balance":0,"createdAt":"2023-11-08T12:50:52.12182285Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5QzUifQ==","asset":"GBP/2","balance":0,"createdAt":"2023-11-08T12:50:52.121833676Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5QzYifQ==","asset":"GBP/2","balance":0,"createdAt":"2023-11-08T12:50:52.121843449Z"},{"accountID":"eyJQcm92aWRlciI6Ik1PRFVMUiIsIlJlZmVyZW5jZSI6IkExMjAwQkQ5QzcifQ==","asset":"GBP/2","balance":0,"createdAt":"2023-11-08T12:50:52.121854829Z"}]]}} \ No newline at end of file diff --git a/components/payments/cmd/connectors/internal/ingestion/transfer_initiation.go b/components/payments/cmd/connectors/internal/ingestion/transfer_initiation.go deleted file mode 100644 index 33fdf4db2f..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/transfer_initiation.go +++ /dev/null @@ -1,145 +0,0 @@ -package ingestion - -import ( - "context" - "fmt" - "time" - - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" -) - -// In some cases, we want to do the two udpates to the transfer initiations -// (update the payment status and add a related payment id) and send only one -// events for both of them. -func (i *DefaultIngester) UpdateTransferInitiationPayment( - ctx context.Context, - tf *models.TransferInitiation, - paymentID *models.PaymentID, - status models.TransferInitiationStatus, - errorMessage string, - updatedAt time.Time, -) error { - if err := i.addTransferInitiationPaymentID(ctx, tf, paymentID, updatedAt); err != nil { - return err - } - - if err := i.updateTransferInitiationPaymentStatus( - ctx, - tf, - paymentID, - status, - errorMessage, - updatedAt, - ); err != nil { - return err - } - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedTransferInitiations(tf), - ), - ); err != nil { - return err - } - - return nil -} - -// Updates only the transfer initiation payment status -func (i *DefaultIngester) UpdateTransferInitiationPaymentsStatus(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, status models.TransferInitiationStatus, errorMessage string, updatedAt time.Time) error { - if err := i.updateTransferInitiationPaymentStatus( - ctx, - tf, - paymentID, - status, - errorMessage, - updatedAt, - ); err != nil { - return err - } - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedTransferInitiations(tf), - ), - ); err != nil { - return err - } - - return nil -} - -// Only adds a related payment id to the transfer initiation -func (i *DefaultIngester) AddTransferInitiationPaymentID(ctx context.Context, tf *models.TransferInitiation, paymentID *models.PaymentID, updatedAt time.Time) error { - if err := i.addTransferInitiationPaymentID(ctx, tf, paymentID, updatedAt); err != nil { - return err - } - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedTransferInitiations(tf), - ), - ); err != nil { - return err - } - - return nil -} - -func (i *DefaultIngester) updateTransferInitiationPaymentStatus( - ctx context.Context, - tf *models.TransferInitiation, - paymentID *models.PaymentID, - status models.TransferInitiationStatus, - errorMessage string, - updatedAt time.Time, -) error { - adjustment := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: tf.ID, - CreatedAt: updatedAt.UTC(), - Status: status, - Error: errorMessage, - } - - tf.RelatedAdjustments = append([]*models.TransferInitiationAdjustment{adjustment}, tf.RelatedAdjustments...) - - if err := i.store.UpdateTransferInitiationPaymentsStatus(ctx, tf.ID, paymentID, adjustment); err != nil { - return err - } - - return nil -} - -func (i *DefaultIngester) addTransferInitiationPaymentID( - ctx context.Context, - tf *models.TransferInitiation, - paymentID *models.PaymentID, - updatedAt time.Time, -) error { - if paymentID == nil { - return fmt.Errorf("payment id is nil") - } - - tf.RelatedPayments = append(tf.RelatedPayments, &models.TransferInitiationPayment{ - TransferInitiationID: tf.ID, - PaymentID: *paymentID, - CreatedAt: updatedAt.UTC(), - Status: models.TransferInitiationStatusProcessing, - }) - - if err := i.store.AddTransferInitiationPaymentID(ctx, tf.ID, paymentID, updatedAt, tf.Metadata); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/ingestion/transfer_reversal.go b/components/payments/cmd/connectors/internal/ingestion/transfer_reversal.go deleted file mode 100644 index c5db2cec6f..0000000000 --- a/components/payments/cmd/connectors/internal/ingestion/transfer_reversal.go +++ /dev/null @@ -1,44 +0,0 @@ -package ingestion - -import ( - "context" - "math/big" - - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" -) - -func (i *DefaultIngester) UpdateTransferReversalStatus(ctx context.Context, tf *models.TransferInitiation, transferReversal *models.TransferReversal) error { - finalAmount := new(big.Int) - isFullyReversed := transferReversal.Status == models.TransferReversalStatusProcessed && - finalAmount.Sub(tf.Amount, transferReversal.Amount).Cmp(big.NewInt(0)) == 0 - - adjustment := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: transferReversal.TransferInitiationID, - CreatedAt: transferReversal.UpdatedAt.UTC(), - Status: transferReversal.Status.ToTransferInitiationStatus(isFullyReversed), - Error: transferReversal.Error, - Metadata: transferReversal.Metadata, - } - - if err := i.store.UpdateTransferReversalStatus(ctx, tf, transferReversal, adjustment); err != nil { - return err - } - - tf.RelatedAdjustments = append([]*models.TransferInitiationAdjustment{adjustment}, tf.RelatedAdjustments...) - - if err := i.publisher.Publish( - events.TopicPayments, - publish.NewMessage( - ctx, - i.messages.NewEventSavedTransferInitiations(tf), - ), - ); err != nil { - return err - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/metrics/metrics.go b/components/payments/cmd/connectors/internal/metrics/metrics.go deleted file mode 100644 index 37364e3773..0000000000 --- a/components/payments/cmd/connectors/internal/metrics/metrics.go +++ /dev/null @@ -1,84 +0,0 @@ -package metrics - -import ( - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" -) - -var registry MetricsRegistry - -func GetMetricsRegistry() MetricsRegistry { - if registry == nil { - registry = NewNoOpMetricsRegistry() - } - - return registry -} - -type MetricsRegistry interface { - ConnectorPSPCalls() metric.Int64Counter - ConnectorPSPCallLatencies() metric.Int64Histogram -} - -type metricsRegistry struct { - connectorPSPCalls metric.Int64Counter - connectorPSPCallLatencies metric.Int64Histogram -} - -func RegisterMetricsRegistry(meterProvider metric.MeterProvider) (MetricsRegistry, error) { - meter := meterProvider.Meter("payments") - - connectorPSPCalls, err := meter.Int64Counter( - "payments_connectors_psp_calls", - metric.WithUnit("1"), - metric.WithDescription("payments connectors psp calls"), - ) - if err != nil { - return nil, err - } - - connectorPSPCallLatencies, err := meter.Int64Histogram( - "payments_connectors_psp_calls_latencies", - metric.WithUnit("ms"), - metric.WithDescription("payments connectors psp calls latencies"), - ) - if err != nil { - return nil, err - } - - registry = &metricsRegistry{ - connectorPSPCalls: connectorPSPCalls, - connectorPSPCallLatencies: connectorPSPCallLatencies, - } - - return registry, nil -} - -func (m *metricsRegistry) ConnectorPSPCalls() metric.Int64Counter { - return m.connectorPSPCalls -} - -func (m *metricsRegistry) ConnectorPSPCallLatencies() metric.Int64Histogram { - return m.connectorPSPCallLatencies -} - -type NoopMetricsRegistry struct{} - -func NewNoOpMetricsRegistry() *NoopMetricsRegistry { - return &NoopMetricsRegistry{} -} - -func (m *NoopMetricsRegistry) ConnectorPSPCalls() metric.Int64Counter { - counter, _ := noop.NewMeterProvider().Meter("payments").Int64Counter("payments_connectors_psp_calls") - return counter -} - -func (m *NoopMetricsRegistry) ConnectorPSPCallLatencies() metric.Int64Histogram { - histogram, _ := noop.NewMeterProvider().Meter("payments").Int64Histogram("payments_connectors_psp_calls_latencies") - return histogram -} - -var ( - _ MetricsRegistry = (*metricsRegistry)(nil) - _ MetricsRegistry = (*NoopMetricsRegistry)(nil) -) diff --git a/components/payments/cmd/connectors/internal/storage/accounts.go b/components/payments/cmd/connectors/internal/storage/accounts.go deleted file mode 100644 index 15aa8c9593..0000000000 --- a/components/payments/cmd/connectors/internal/storage/accounts.go +++ /dev/null @@ -1,85 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) UpsertAccounts(ctx context.Context, accounts []*models.Account) ([]models.AccountID, error) { - if len(accounts) == 0 { - return nil, nil - } - - var idsUpdated []string - err := s.db.NewUpdate(). - With("_data", - s.db.NewValues(&accounts). - Column( - "id", - "default_currency", - "account_name", - "metadata", - ), - ). - Model((*models.Account)(nil)). - TableExpr("_data"). - Set("default_currency = _data.default_currency"). - Set("account_name = _data.account_name"). - Set("metadata = _data.metadata"). - Where(`(account.id = _data.id) AND - (account.default_currency != _data.default_currency OR account.account_name != _data.account_name OR (account.metadata != _data.metadata))`). - Returning("account.id"). - Scan(ctx, &idsUpdated) - if err != nil { - return nil, e("failed to update accounts", err) - } - - idsUpdatedMap := make(map[string]struct{}) - for _, id := range idsUpdated { - idsUpdatedMap[id] = struct{}{} - } - - accountsToInsert := make([]*models.Account, 0, len(accounts)) - for _, account := range accounts { - if _, ok := idsUpdatedMap[account.ID.String()]; !ok { - accountsToInsert = append(accountsToInsert, account) - } - } - - var idsInserted []string - if len(accountsToInsert) > 0 { - err = s.db.NewInsert(). - Model(&accountsToInsert). - On("CONFLICT (id) DO NOTHING"). - Returning("account.id"). - Scan(ctx, &idsInserted) - if err != nil { - return nil, e("failed to create accounts", err) - } - } - - res := make([]models.AccountID, 0, len(idsUpdated)+len(idsInserted)) - for _, id := range idsUpdated { - res = append(res, models.MustAccountIDFromString(id)) - } - for _, id := range idsInserted { - res = append(res, models.MustAccountIDFromString(id)) - } - - return res, nil -} - -func (s *Storage) GetAccount(ctx context.Context, id string) (*models.Account, error) { - var account models.Account - - err := s.db.NewSelect(). - Model(&account). - Where("id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("failed to get account", err) - } - - return &account, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/accounts_test.go b/components/payments/cmd/connectors/internal/storage/accounts_test.go deleted file mode 100644 index b31f15afed..0000000000 --- a/components/payments/cmd/connectors/internal/storage/accounts_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package storage_test - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - acc1ID models.AccountID - acc1T = time.Date(2023, 11, 14, 4, 59, 0, 0, time.UTC) - - acc2ID models.AccountID - acc2T = time.Date(2023, 11, 14, 4, 58, 0, 0, time.UTC) - - acc3ID models.AccountID - acc3T = time.Date(2023, 11, 14, 4, 57, 0, 0, time.UTC) -) - -func TestAccounts(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testCreateAccounts(t, store) - testUpdateAccounts(t, store) - testUninstallConnectors(t, store) - testAccountsDeletedAfterConnectorUninstall(t, store) -} - -func testCreateAccounts(t *testing.T, store *storage.Storage) { - acc1ID = models.AccountID{ - Reference: "test1", - ConnectorID: connectorID, - } - acc2ID = models.AccountID{ - Reference: "test2", - ConnectorID: connectorID, - } - acc3ID = models.AccountID{ - Reference: "test3", - ConnectorID: connectorID, - } - - acc1 := &models.Account{ - ID: acc1ID, - CreatedAt: acc1T, - Reference: "test1", - ConnectorID: connectorID, - DefaultAsset: "USD", - AccountName: "test1", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo": "bar", - }, - } - - acc2 := &models.Account{ - ID: acc2ID, - CreatedAt: acc2T, - Reference: "test2", - ConnectorID: connectorID, - Type: models.AccountTypeExternal, - } - - acc3 := &models.Account{ - ID: acc3ID, - CreatedAt: acc3T, - Reference: "test3", - ConnectorID: connectorID, - Type: models.AccountTypeInternal, - } - - connectorIDFail := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - accFail := &models.Account{ - ID: models.AccountID{Reference: "test4", ConnectorID: connectorIDFail}, - CreatedAt: acc3T, - Reference: "test4", - ConnectorID: connectorIDFail, - Type: models.AccountTypeInternal, - } - - // Try to insert accounts from a not installed connector - _, err := store.UpsertAccounts( - context.Background(), - []*models.Account{accFail}, - ) - require.Error(t, err) - - idsInserted, err := store.UpsertAccounts( - context.Background(), - []*models.Account{acc1, acc2, acc3}, - ) - require.NoError(t, err) - require.Len(t, idsInserted, 3) - require.Equal(t, acc1ID, idsInserted[0]) - require.Equal(t, acc2ID, idsInserted[1]) - require.Equal(t, acc3ID, idsInserted[2]) - - testGetAccount(t, store, acc1.ID, acc1, false) - testGetAccount(t, store, acc2.ID, acc2, false) - testGetAccount(t, store, acc3.ID, acc3, false) - testGetAccount(t, store, models.AccountID{Reference: "test4", ConnectorID: connectorID}, nil, true) -} - -func testGetAccount( - t *testing.T, - store *storage.Storage, - id models.AccountID, - expectedAccount *models.Account, - expectedError bool, -) { - account, err := store.GetAccount(context.Background(), id.String()) - if expectedError { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } - - account.CreatedAt = account.CreatedAt.UTC() - require.Equal(t, expectedAccount, account) -} - -func testUpdateAccounts(t *testing.T, store *storage.Storage) { - acc1Updated := &models.Account{ - ID: acc1ID, - CreatedAt: time.Date(2023, 11, 14, 5, 59, 0, 0, time.UTC), // New timestamps, but should not be updated in the database - Reference: "test1", - ConnectorID: connectorID, - DefaultAsset: "EUR", - AccountName: "test1-update", - Type: models.AccountTypeInternal, - Metadata: map[string]string{ - "foo2": "bar2", - }, - } - - idsInserted, err := store.UpsertAccounts( - context.Background(), - []*models.Account{acc1Updated}, - ) - require.NoError(t, err) - require.Len(t, idsInserted, 1) - require.Equal(t, acc1ID, idsInserted[0]) - - // CreatedAt should not be updated - acc1Updated.CreatedAt = acc1T - testGetAccount(t, store, acc1Updated.ID, acc1Updated, false) - - // Upsert again with the same values - idsInserted, err = store.UpsertAccounts( - context.Background(), - []*models.Account{acc1Updated}, - ) - require.NoError(t, err) - require.Len(t, idsInserted, 0) // Should not be updated or inserted - - testGetAccount(t, store, acc1Updated.ID, acc1Updated, false) -} - -func testAccountsDeletedAfterConnectorUninstall(t *testing.T, store *storage.Storage) { - // Accounts should be deleted after uninstalling the connector - testGetAccount(t, store, acc1ID, nil, true) - testGetAccount(t, store, acc2ID, nil, true) - testGetAccount(t, store, acc3ID, nil, true) -} diff --git a/components/payments/cmd/connectors/internal/storage/balance_test.go b/components/payments/cmd/connectors/internal/storage/balance_test.go deleted file mode 100644 index cfb2b7a912..0000000000 --- a/components/payments/cmd/connectors/internal/storage/balance_test.go +++ /dev/null @@ -1,91 +0,0 @@ -package storage_test - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -var ( - b1T = time.Date(2023, 11, 14, 5, 1, 10, 0, time.UTC) - b2T = time.Date(2023, 11, 14, 5, 1, 20, 0, time.UTC) -) - -func TestBalances(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testCreateAccounts(t, store) - testCreateBalances(t, store) - testUninstallConnectors(t, store) - testBalancesDeletedAfterConnectorUninstall(t, store) -} - -func testCreateBalances(t *testing.T, store *storage.Storage) { - b1 := &models.Balance{ - AccountID: models.AccountID{ - Reference: "not_existing", - ConnectorID: connectorID, - }, - Asset: "USD", - Balance: big.NewInt(int64(100)), - CreatedAt: b1T, - LastUpdatedAt: b1T, - } - - // Cannot insert balance for non-existing account - err := store.InsertBalances(context.Background(), []*models.Balance{b1}, false) - require.Error(t, err) - - // When inserting with ignore, no error is returned - err = store.InsertBalances(context.Background(), []*models.Balance{b1}, true) - require.NoError(t, err) - - b1.AccountID = acc1ID - err = store.InsertBalances(context.Background(), []*models.Balance{b1}, true) - require.NoError(t, err) - - b2 := &models.Balance{ - AccountID: acc1ID, - Asset: "USD", - Balance: big.NewInt(int64(200)), - CreatedAt: b2T, - LastUpdatedAt: b2T, - } - err = store.InsertBalances(context.Background(), []*models.Balance{b2}, true) - require.NoError(t, err) - - testGetBalance(t, store, acc1ID, []*models.Balance{b2, b1}, nil) -} - -func testGetBalance( - t *testing.T, - store *storage.Storage, - accountID models.AccountID, - expectedBalances []*models.Balance, - expectedError error, -) { - balances, err := store.GetBalancesForAccountID(context.Background(), accountID) - require.NoError(t, err) - require.Len(t, balances, len(expectedBalances)) - for i := range balances { - if i < len(balances)-1 { - require.Equal(t, balances[i+1].LastUpdatedAt.UTC(), balances[i].CreatedAt.UTC()) - } - require.Equal(t, expectedBalances[i].CreatedAt.UTC(), balances[i].CreatedAt.UTC()) - require.Equal(t, expectedBalances[i].AccountID, balances[i].AccountID) - require.Equal(t, expectedBalances[i].Asset, balances[i].Asset) - require.Equal(t, expectedBalances[i].Balance, balances[i].Balance) - } -} - -func testBalancesDeletedAfterConnectorUninstall(t *testing.T, store *storage.Storage) { - balances, err := store.GetBalancesForAccountID(context.Background(), acc1ID) - require.NoError(t, err) - require.Len(t, balances, 0) -} diff --git a/components/payments/cmd/connectors/internal/storage/balances.go b/components/payments/cmd/connectors/internal/storage/balances.go deleted file mode 100644 index a58e75d49c..0000000000 --- a/components/payments/cmd/connectors/internal/storage/balances.go +++ /dev/null @@ -1,75 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) InsertBalances(ctx context.Context, balances []*models.Balance, checkIfAccountExists bool) error { - if len(balances) == 0 { - return nil - } - - query := s.db.NewInsert(). - Model((*models.Balance)(nil)). - With("cte1", s.db.NewValues(&balances)). - Column( - "created_at", - "account_id", - "balance", - "currency", - "last_updated_at", - ) - if checkIfAccountExists { - query = query.TableExpr(` - (SELECT * - FROM cte1 - WHERE EXISTS (SELECT 1 FROM accounts.account WHERE id = cte1.account_id) - AND cte1.balance != COALESCE((SELECT balance FROM accounts.balances WHERE account_id = cte1.account_id AND last_updated_at < cte1.last_updated_at AND currency = cte1.currency ORDER BY last_updated_at DESC LIMIT 1), cte1.balance+1) - ) data`) - } else { - query = query.TableExpr(` - (SELECT * - FROM cte1 - WHERE cte1.balance != COALESCE((SELECT balance FROM accounts.balances WHERE account_id = cte1.account_id AND last_updated_at < cte1.last_updated_at AND currency = cte1.currency ORDER BY last_updated_at DESC LIMIT 1), cte1.balance+1) - ) data`) - } - - query = query.On("CONFLICT (account_id, created_at, currency) DO NOTHING") - - _, err := query.Exec(ctx) - if err != nil { - return e("failed to create balances", err) - } - - // Always update the previous row in order to keep the balance history consistent. - _, err = s.db.NewUpdate(). - Model((*models.Balance)(nil)). - With("cte1", s.db.NewValues(&balances)). - TableExpr(` - (SELECT (SELECT created_at FROM accounts.balances WHERE last_updated_at < cte1.last_updated_at AND account_id = cte1.account_id AND currency = cte1.currency ORDER BY last_updated_at DESC LIMIT 1), cte1.account_id, cte1.currency, cte1.last_updated_at FROM cte1) data - `). - Set("last_updated_at = data.last_updated_at"). - Where("balance.account_id = data.account_id AND balance.currency = data.currency AND balance.created_at = data.created_at"). - Exec(ctx) - if err != nil { - return e("failed to update balances", err) - } - - return nil -} - -func (s *Storage) GetBalancesForAccountID(ctx context.Context, accountID models.AccountID) ([]*models.Balance, error) { - var balances []*models.Balance - - err := s.db.NewSelect(). - Model(&balances). - Where("account_id = ?", accountID). - Scan(ctx) - if err != nil { - return nil, e("failed to get balances", err) - } - - return balances, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/bank_accounts.go b/components/payments/cmd/connectors/internal/storage/bank_accounts.go deleted file mode 100644 index b751a94cfb..0000000000 --- a/components/payments/cmd/connectors/internal/storage/bank_accounts.go +++ /dev/null @@ -1,134 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -func (s *Storage) CreateBankAccount(ctx context.Context, bankAccount *models.BankAccount) error { - account := models.BankAccount{ - CreatedAt: bankAccount.CreatedAt, - Country: bankAccount.Country, - Name: bankAccount.Name, - Metadata: bankAccount.Metadata, - } - - var id uuid.UUID - err := s.db.NewInsert().Model(&account).Returning("id").Scan(ctx, &id) - if err != nil { - return e("install connector", err) - } - bankAccount.ID = id - - return s.updateBankAccountInformation(ctx, id, bankAccount.AccountNumber, bankAccount.IBAN, bankAccount.SwiftBicCode) -} - -func (s *Storage) AddBankAccountRelatedAccount(ctx context.Context, relatedAccount *models.BankAccountRelatedAccount) error { - _, err := s.db.NewInsert().Model(relatedAccount).Exec(ctx) - if err != nil { - return e("add bank account related account", err) - } - - return nil -} - -func (s *Storage) updateBankAccountInformation(ctx context.Context, id uuid.UUID, accountNumber, iban, swiftBicCode string) error { - _, err := s.db.NewUpdate(). - Model(&models.BankAccount{}). - Set("account_number = pgp_sym_encrypt(?::TEXT, ?, ?)", accountNumber, s.configEncryptionKey, encryptionOptions). - Set("iban = pgp_sym_encrypt(?::TEXT, ?, ?)", iban, s.configEncryptionKey, encryptionOptions). - Set("swift_bic_code = pgp_sym_encrypt(?::TEXT, ?, ?)", swiftBicCode, s.configEncryptionKey, encryptionOptions). - Where("id = ?", id). - Exec(ctx) - if err != nil { - return e("update bank account information", err) - } - - return nil -} - -func (s *Storage) UpdateBankAccountMetadata(ctx context.Context, id uuid.UUID, metadata map[string]string) error { - tx, err := s.db.BeginTx(ctx, nil) - if err != nil { - return e("update bank account metadata", err) - } - defer tx.Rollback() - - var account models.BankAccount - err = tx.NewSelect(). - Model(&account). - Column("id", "metadata"). - Where("id = ?", id). - Scan(ctx) - if err != nil { - return e("update bank account metadata", err) - } - - if account.Metadata == nil { - account.Metadata = make(map[string]string) - } - - for k, v := range metadata { - account.Metadata[k] = v - } - - _, err = s.db.NewUpdate(). - Model(&account). - Column("metadata"). - Where("id = ?", id). - Exec(ctx) - if err != nil { - return e("update bank account metadata", err) - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) LinkBankAccountWithAccount(ctx context.Context, id uuid.UUID, accountID *models.AccountID) error { - relatedAccount := &models.BankAccountRelatedAccount{ - ID: uuid.New(), - BankAccountID: id, - ConnectorID: accountID.ConnectorID, - AccountID: *accountID, - } - - return s.AddBankAccountRelatedAccount(ctx, relatedAccount) -} - -func (s *Storage) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) { - var account models.BankAccount - query := s.db.NewSelect(). - Model(&account). - Relation("RelatedAccounts"). - Column("id", "created_at", "name", "created_at", "country", "metadata") - - if expand { - query = query.ColumnExpr("pgp_sym_decrypt(account_number, ?, ?) AS decrypted_account_number", s.configEncryptionKey, encryptionOptions). - ColumnExpr("pgp_sym_decrypt(iban, ?, ?) AS decrypted_iban", s.configEncryptionKey, encryptionOptions). - ColumnExpr("pgp_sym_decrypt(swift_bic_code, ?, ?) AS decrypted_swift_bic_code", s.configEncryptionKey, encryptionOptions) - } - - err := query. - Where("id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("get bank account", err) - } - - return &account, nil -} - -func (s *Storage) GetBankAccountRelatedAccounts(ctx context.Context, id uuid.UUID) ([]*models.BankAccountRelatedAccount, error) { - var relatedAccounts []*models.BankAccountRelatedAccount - err := s.db.NewSelect(). - Model(&relatedAccounts). - Where("bank_account_id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("get bank account related accounts", err) - } - - return relatedAccounts, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/bank_accounts_test.go b/components/payments/cmd/connectors/internal/storage/bank_accounts_test.go deleted file mode 100644 index 35772a9f85..0000000000 --- a/components/payments/cmd/connectors/internal/storage/bank_accounts_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package storage_test - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/stretchr/testify/require" -) - -var ( - bankAccount1ID uuid.UUID - bankAccount2ID uuid.UUID - - bankAccount1T = time.Date(2023, 11, 14, 5, 2, 0, 0, time.UTC) - bankAccount2T = time.Date(2023, 11, 14, 5, 1, 0, 0, time.UTC) -) - -func TestBankAccounts(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testCreateAccounts(t, store) - testCreateBankAccounts(t, store) - testUpdateBankAccountMetadata(t, store) - testUninstallConnectors(t, store) - testBankAccountsDeletedAfterConnectorUninstall(t, store) -} - -func testCreateBankAccounts(t *testing.T, store *storage.Storage) { - bankAccount1 := &models.BankAccount{ - CreatedAt: bankAccount1T, - Name: "test1", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "BNPAFRPPXXX", - Country: "FR", - } - - err := store.CreateBankAccount(context.Background(), bankAccount1) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, bankAccount1.ID) - bankAccount1ID = bankAccount1.ID - - bankAccount2 := &models.BankAccount{ - CreatedAt: bankAccount2T, - Name: "test2", - AccountNumber: "123456789", - Country: "FR", - } - - err = store.CreateBankAccount(context.Background(), bankAccount2) - require.NoError(t, err) - require.NotEqual(t, uuid.Nil, bankAccount2.ID) - bankAccount2ID = bankAccount2.ID - - relatedAccount := &models.BankAccountRelatedAccount{ - ID: uuid.New(), - CreatedAt: bankAccount2T, - BankAccountID: bankAccount2ID, - ConnectorID: connectorID, - AccountID: acc1ID, - } - err = store.AddBankAccountRelatedAccount(context.Background(), relatedAccount) - require.NoError(t, err) - bankAccount2.RelatedAccounts = append(bankAccount2.RelatedAccounts, relatedAccount) - - err = store.AddBankAccountRelatedAccount(context.Background(), &models.BankAccountRelatedAccount{ - ID: uuid.New(), - CreatedAt: bankAccount2T, - BankAccountID: bankAccount2ID, - ConnectorID: connectorID, - AccountID: models.AccountID{ - Reference: "not_existing", - ConnectorID: connectorID, - }, - }) - require.Error(t, err) - - testGetBankAccount(t, store, bankAccount1ID, true, bankAccount1, nil) - testGetBankAccount(t, store, bankAccount2ID, true, bankAccount2, nil) -} - -func testGetBankAccount( - t *testing.T, - store *storage.Storage, - bankAccountID uuid.UUID, - expand bool, - expectedBankAccount *models.BankAccount, - expectedError error, -) { - bankAccount, err := store.GetBankAccount(context.Background(), bankAccountID, expand) - if expectedError != nil { - require.EqualError(t, err, expectedError.Error()) - return - } else { - require.NoError(t, err) - } - - require.Equal(t, bankAccount.Country, expectedBankAccount.Country) - require.Equal(t, bankAccount.CreatedAt.UTC(), expectedBankAccount.CreatedAt.UTC()) - require.Equal(t, bankAccount.Name, expectedBankAccount.Name) - - if expand { - require.Equal(t, bankAccount.SwiftBicCode, expectedBankAccount.SwiftBicCode) - require.Equal(t, bankAccount.IBAN, expectedBankAccount.IBAN) - require.Equal(t, bankAccount.AccountNumber, expectedBankAccount.AccountNumber) - } - - require.Len(t, bankAccount.RelatedAccounts, len(expectedBankAccount.RelatedAccounts)) - for i, adj := range bankAccount.RelatedAccounts { - require.Equal(t, adj.BankAccountID, expectedBankAccount.RelatedAccounts[i].BankAccountID) - require.Equal(t, adj.CreatedAt.UTC(), expectedBankAccount.RelatedAccounts[i].CreatedAt.UTC()) - require.Equal(t, adj.ConnectorID, expectedBankAccount.RelatedAccounts[i].ConnectorID) - require.Equal(t, adj.AccountID, expectedBankAccount.RelatedAccounts[i].AccountID) - } -} - -func testUpdateBankAccountMetadata(t *testing.T, store *storage.Storage) { - metadata := map[string]string{ - "key": "value", - } - - err := store.UpdateBankAccountMetadata(context.Background(), bankAccount1ID, metadata) - require.NoError(t, err) - - bankAccount, err := store.GetBankAccount(context.Background(), bankAccount1ID, false) - require.NoError(t, err) - require.Equal(t, metadata, bankAccount.Metadata) - - // Bank account not existing - err = store.UpdateBankAccountMetadata(context.Background(), uuid.New(), metadata) - require.True(t, errors.Is(err, storage.ErrNotFound)) -} - -func testBankAccountsDeletedAfterConnectorUninstall(t *testing.T, store *storage.Storage) { - // Connector has been uninstalled, related adjustments are deleted, but not the bank - // accounts themselves. - bankAccount1 := &models.BankAccount{ - CreatedAt: bankAccount1T, - Name: "test1", - IBAN: "FR7630006000011234567890189", - SwiftBicCode: "BNPAFRPPXXX", - Country: "FR", - } - - bankAccount2 := &models.BankAccount{ - CreatedAt: bankAccount2T, - Name: "test2", - AccountNumber: "123456789", - Country: "FR", - } - - testGetBankAccount(t, store, bankAccount1ID, true, bankAccount1, nil) - testGetBankAccount(t, store, bankAccount2ID, true, bankAccount2, nil) -} diff --git a/components/payments/cmd/connectors/internal/storage/connectors.go b/components/payments/cmd/connectors/internal/storage/connectors.go deleted file mode 100644 index a7f6a7f2b7..0000000000 --- a/components/payments/cmd/connectors/internal/storage/connectors.go +++ /dev/null @@ -1,131 +0,0 @@ -package storage - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) ListConnectors(ctx context.Context) ([]*models.Connector, error) { - var connectors []*models.Connector - - err := s.db.NewSelect(). - Model(&connectors). - ColumnExpr("*, pgp_sym_decrypt(config, ?, ?) AS decrypted_config", s.configEncryptionKey, encryptionOptions). - Scan(ctx) - if err != nil { - return nil, e("list connectors", err) - } - - return connectors, nil -} - -func (s *Storage) ListConnectorsByProvider(ctx context.Context, provider models.ConnectorProvider) ([]*models.Connector, error) { - var connectors []*models.Connector - - err := s.db.NewSelect(). - Model(&connectors). - ColumnExpr("*, pgp_sym_decrypt(config, ?, ?) AS decrypted_config", s.configEncryptionKey, encryptionOptions). - Where("provider = ?", provider). - Scan(ctx) - if err != nil { - return nil, e("list connectors", err) - } - - return connectors, nil -} - -func (s *Storage) GetConfig(ctx context.Context, connectorID models.ConnectorID, destination any) error { - var connector models.Connector - - err := s.db.NewSelect(). - Model(&connector). - ColumnExpr("pgp_sym_decrypt(config, ?, ?) AS decrypted_config", s.configEncryptionKey, encryptionOptions). - Where("id = ?", connectorID). - Scan(ctx) - if err != nil { - return e(fmt.Sprintf("failed to get config for connector %s", connectorID), err) - } - - err = json.Unmarshal(connector.Config, destination) - if err != nil { - return e(fmt.Sprintf("failed to unmarshal config for connector %s", connectorID), err) - } - - return nil -} - -func (s *Storage) IsInstalledByConnectorID(ctx context.Context, connectorID models.ConnectorID) (bool, error) { - exists, err := s.db.NewSelect(). - Model(&models.Connector{}). - Where("id = ?", connectorID). - Exists(ctx) - if err != nil { - return false, e("find connector", err) - } - - return exists, nil -} - -func (s *Storage) IsInstalledByConnectorName(ctx context.Context, name string) (bool, error) { - exists, err := s.db.NewSelect(). - Model(&models.Connector{}). - Where("name = ?", name). - Exists(ctx) - if err != nil { - return false, e("find connector", err) - } - - return exists, nil -} - -func (s *Storage) Install(ctx context.Context, connector *models.Connector, config json.RawMessage) error { - _, err := s.db.NewInsert().Model(connector).Exec(ctx) - if err != nil { - return e("install connector", err) - } - - return s.UpdateConfig(ctx, connector.ID, config) -} - -func (s *Storage) Uninstall(ctx context.Context, connectorID models.ConnectorID) error { - _, err := s.db.NewDelete(). - Model(&models.Connector{}). - Where("id = ?", connectorID). - Exec(ctx) - if err != nil { - return e("uninstall connector", err) - } - - return nil -} - -func (s *Storage) UpdateConfig(ctx context.Context, connectorID models.ConnectorID, config json.RawMessage) error { - _, err := s.db.NewUpdate(). - Model(&models.Connector{}). - Set("config = pgp_sym_encrypt(?::TEXT, ?, ?)", config, s.configEncryptionKey, encryptionOptions). - Where("id = ?", connectorID). // Connector name is unique - Exec(ctx) - if err != nil { - return e("update connector config", err) - } - - return nil -} - -func (s *Storage) GetConnector(ctx context.Context, connectorID models.ConnectorID) (*models.Connector, error) { - var connector models.Connector - - err := s.db.NewSelect(). - Model(&connector). - ColumnExpr("*, pgp_sym_decrypt(config, ?, ?) AS decrypted_config", s.configEncryptionKey, encryptionOptions). - Where("id = ?", connectorID). - Scan(ctx) - if err != nil { - return nil, e("find connector", err) - } - - return &connector, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/connectors_test.go b/components/payments/cmd/connectors/internal/storage/connectors_test.go deleted file mode 100644 index f941fc7a55..0000000000 --- a/components/payments/cmd/connectors/internal/storage/connectors_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package storage_test - -import ( - "context" - "encoding/json" - "testing" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - connectorID = models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } -) - -func TestConnectors(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testIsInstalledConnectors(t, store) - testUpdateConfig(t, store) - testUninstallConnectors(t, store) - testAfterInstallationConnectors(t, store) -} - -func testInstallConnectors(t *testing.T, store *storage.Storage) { - connector1 := models.Connector{ - ID: connectorID, - Name: "test1", - Provider: models.ConnectorProviderDummyPay, - } - err := store.Install( - context.Background(), - &connector1, - json.RawMessage([]byte(`{"foo": "bar"}`)), - ) - require.NoError(t, err) - - err = store.Install( - context.Background(), - &connector1, - json.RawMessage([]byte(`{"foo": "bar"}`)), - ) - require.Equal(t, storage.ErrDuplicateKeyValue, err) - - testGetConnector(t, store, connectorID, []byte(`{"foo": "bar"}`)) -} - -func testGetConnector(t *testing.T, store *storage.Storage, connectorID models.ConnectorID, expectedConfig []byte) { - var config json.RawMessage - err := store.GetConfig(context.Background(), connectorID, &config) - require.NoError(t, err) - require.Equal(t, json.RawMessage(expectedConfig), config) -} - -func testUpdateConfig(t *testing.T, store *storage.Storage) { - err := store.UpdateConfig(context.Background(), connectorID, json.RawMessage([]byte(`{"foo2": "bar2"}`))) - require.NoError(t, err) - - testGetConnector(t, store, connectorID, []byte(`{"foo2": "bar2"}`)) -} - -func testIsInstalledConnectors(t *testing.T, store *storage.Storage) { - isInstalled, err := store.IsInstalledByConnectorID( - context.Background(), - models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }) - require.NoError(t, err) - require.False(t, isInstalled) - - isInstalled, err = store.IsInstalledByConnectorID(context.Background(), connectorID) - require.NoError(t, err) - require.True(t, isInstalled) -} - -func testUninstallConnectors(t *testing.T, store *storage.Storage) { - // No error if deleting an unknown connector - err := store.Uninstall(context.Background(), models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - }) - require.NoError(t, err) - - err = store.Uninstall(context.Background(), connectorID) - require.NoError(t, err) -} - -func testAfterInstallationConnectors(t *testing.T, store *storage.Storage) { - isInstalled, err := store.IsInstalledByConnectorID(context.Background(), connectorID) - require.NoError(t, err) - require.False(t, isInstalled) -} diff --git a/components/payments/cmd/connectors/internal/storage/error.go b/components/payments/cmd/connectors/internal/storage/error.go deleted file mode 100644 index 3cd5148877..0000000000 --- a/components/payments/cmd/connectors/internal/storage/error.go +++ /dev/null @@ -1,29 +0,0 @@ -package storage - -import ( - "database/sql" - "fmt" - - "github.com/jackc/pgx/v5/pgconn" - "github.com/pkg/errors" -) - -var ErrNotFound = errors.New("not found") -var ErrDuplicateKeyValue = errors.New("duplicate key value") - -func e(msg string, err error) error { - if err == nil { - return nil - } - - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == "23505" { - return ErrDuplicateKeyValue - } - - if errors.Is(err, sql.ErrNoRows) { - return ErrNotFound - } - - return fmt.Errorf("%s: %w", msg, err) -} diff --git a/components/payments/cmd/connectors/internal/storage/main_test.go b/components/payments/cmd/connectors/internal/storage/main_test.go deleted file mode 100644 index a6ae2b2e3f..0000000000 --- a/components/payments/cmd/connectors/internal/storage/main_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package storage_test - -import ( - "context" - "crypto/rand" - "testing" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - migrationstorage "github.com/formancehq/payments/internal/storage" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/stdlib" - "github.com/stretchr/testify/require" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" -) - -var ( - srv *pgtesting.PostgresServer -) - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} - -func newStore(t *testing.T) *storage.Storage { - t.Helper() - - pgServer := srv.NewDatabase(t) - - config, err := pgx.ParseConfig(pgServer.ConnString()) - require.NoError(t, err) - - key := make([]byte, 64) - _, err = rand.Read(key) - require.NoError(t, err) - - db := bun.NewDB(stdlib.OpenDB(*config), pgdialect.New()) - t.Cleanup(func() { - _ = db.Close() - }) - - err = migrationstorage.Migrate(context.Background(), db) - require.NoError(t, err) - - store := storage.NewStorage( - db, - string(key), - ) - - return store -} diff --git a/components/payments/cmd/connectors/internal/storage/metadata.go b/components/payments/cmd/connectors/internal/storage/metadata.go deleted file mode 100644 index 723337bd6e..0000000000 --- a/components/payments/cmd/connectors/internal/storage/metadata.go +++ /dev/null @@ -1,39 +0,0 @@ -package storage - -import ( - "context" - "time" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) UpdatePaymentMetadata(ctx context.Context, paymentID models.PaymentID, metadata map[string]string) error { - var metadataToInsert []models.PaymentMetadata // nolint:prealloc // it's against a map - - for key, value := range metadata { - metadataToInsert = append(metadataToInsert, models.PaymentMetadata{ - PaymentID: paymentID, - Key: key, - Value: value, - Changelog: []models.MetadataChangelog{ - { - CreatedAt: time.Now(), - Value: value, - }, - }, - }) - } - - _, err := s.db.NewInsert(). - Model(&metadataToInsert). - On("CONFLICT (payment_id, key) DO UPDATE"). - Set("value = EXCLUDED.value"). - Set("changelog = metadata.changelog || EXCLUDED.changelog"). - Where("metadata.value != EXCLUDED.value"). - Exec(ctx) - if err != nil { - return e("failed to update payment metadata", err) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/storage/module.go b/components/payments/cmd/connectors/internal/storage/module.go deleted file mode 100644 index cbdf9517ec..0000000000 --- a/components/payments/cmd/connectors/internal/storage/module.go +++ /dev/null @@ -1,31 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/logging" - "github.com/uptrace/bun" - "go.uber.org/fx" -) - -func Module(connectionOptions bunconnect.ConnectionOptions, configEncryptionKey string, debug bool) fx.Option { - return fx.Options( - fx.Supply(&connectionOptions), - bunconnect.Module(connectionOptions, debug), - fx.Provide(func(db *bun.DB) *Storage { - return NewStorage(db, configEncryptionKey) - }), - fx.Invoke(func(lc fx.Lifecycle, repo *Storage) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - logging.FromContext(ctx).Debug("Ping database...") - - // TODO: Check migrations state and panic if migrations are not applied - - return nil - }, - }) - }), - ) -} diff --git a/components/payments/cmd/connectors/internal/storage/paginate.go b/components/payments/cmd/connectors/internal/storage/paginate.go deleted file mode 100644 index ac170979be..0000000000 --- a/components/payments/cmd/connectors/internal/storage/paginate.go +++ /dev/null @@ -1,47 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/go-libs/query" - "github.com/uptrace/bun" -) - -type PaginatedQueryOptions[T any] struct { - QueryBuilder query.Builder `json:"qb"` - Sorter Sorter - PageSize uint64 `json:"pageSize"` - Options T `json:"options"` -} - -func (opts PaginatedQueryOptions[T]) WithQueryBuilder(qb query.Builder) PaginatedQueryOptions[T] { - opts.QueryBuilder = qb - - return opts -} - -func (opts PaginatedQueryOptions[T]) WithSorter(sorter Sorter) PaginatedQueryOptions[T] { - opts.Sorter = sorter - - return opts -} - -func (opts PaginatedQueryOptions[T]) WithPageSize(pageSize uint64) PaginatedQueryOptions[T] { - opts.PageSize = pageSize - - return opts -} - -func NewPaginatedQueryOptions[T any](options T) PaginatedQueryOptions[T] { - return PaginatedQueryOptions[T]{ - Options: options, - PageSize: bunpaginate.QueryDefaultPageSize, - } -} - -func PaginateWithOffset[FILTERS any, RETURN any](s *Storage, ctx context.Context, - q *bunpaginate.OffsetPaginatedQuery[FILTERS], builders ...func(query *bun.SelectQuery) *bun.SelectQuery) (*bunpaginate.Cursor[RETURN], error) { - query := s.db.NewSelect() - return bunpaginate.UsingOffset[FILTERS, RETURN](ctx, query, *q, builders...) -} diff --git a/components/payments/cmd/connectors/internal/storage/payments.go b/components/payments/cmd/connectors/internal/storage/payments.go deleted file mode 100644 index bcdf0b69cf..0000000000 --- a/components/payments/cmd/connectors/internal/storage/payments.go +++ /dev/null @@ -1,136 +0,0 @@ -package storage - -import ( - "context" - "fmt" - - "github.com/formancehq/payments/internal/models" -) - -func (s *Storage) GetPayment(ctx context.Context, id string) (*models.Payment, error) { - var payment models.Payment - - err := s.db.NewSelect(). - Model(&payment). - Relation("Connector"). - Relation("Metadata"). - Relation("Adjustments"). - Where("payment.id = ?", id). - Scan(ctx) - if err != nil { - return nil, e(fmt.Sprintf("failed to get payment %s", id), err) - } - - return &payment, nil -} - -func (s *Storage) UpsertPayments(ctx context.Context, payments []*models.Payment) ([]*models.PaymentID, error) { - if len(payments) == 0 { - return nil, nil - } - - var idsUpdated []string - err := s.db.NewUpdate(). - With("_data", - s.db.NewValues(&payments). - Column( - "id", - "amount", - "type", - "scheme", - "asset", - "source_account_id", - "destination_account_id", - "status", - "created_at", - ), - ). - Model((*models.Payment)(nil)). - TableExpr("_data"). - Set("amount = _data.amount"). - Set("type = _data.type"). - Set("scheme = _data.scheme"). - Set("asset = _data.asset"). - Set("source_account_id = _data.source_account_id"). - Set("destination_account_id = _data.destination_account_id"). - Set("status = _data.status"). - Set("created_at = _data.created_at"). - Where(`(payment.id = _data.id) AND - (payment.created_at != _data.created_at OR payment.amount != _data.amount OR payment.type != _data.type OR - payment.scheme != _data.scheme OR payment.asset != _data.asset OR payment.source_account_id != _data.source_account_id OR - payment.destination_account_id != _data.destination_account_id OR payment.status != _data.status)`). - Returning("payment.id"). - Scan(ctx, &idsUpdated) - if err != nil { - return nil, e("failed to update payments", err) - } - - idsUpdatedMap := make(map[string]struct{}) - for _, id := range idsUpdated { - idsUpdatedMap[id] = struct{}{} - } - - paymentsToInsert := make([]*models.Payment, 0, len(payments)) - for _, payment := range payments { - if _, ok := idsUpdatedMap[payment.ID.String()]; !ok { - paymentsToInsert = append(paymentsToInsert, payment) - } - } - - var idsInserted []string - if len(paymentsToInsert) > 0 { - err = s.db.NewInsert(). - Model(&paymentsToInsert). - On("CONFLICT (id) DO NOTHING"). - Returning("payment.id"). - Scan(ctx, &idsInserted) - if err != nil { - return nil, e("failed to create payments", err) - } - } - - res := make([]*models.PaymentID, 0, len(idsUpdated)+len(idsInserted)) - for _, id := range idsUpdated { - res = append(res, models.MustPaymentIDFromString(id)) - } - for _, id := range idsInserted { - res = append(res, models.MustPaymentIDFromString(id)) - } - - return res, nil -} - -func (s *Storage) UpsertPaymentsAdjustments(ctx context.Context, adjustments []*models.PaymentAdjustment) error { - if len(adjustments) == 0 { - return nil - } - - _, err := s.db.NewInsert(). - Model(&adjustments). - On("CONFLICT (reference) DO NOTHING"). - Exec(ctx) - if err != nil { - return e("failed to create adjustments", err) - } - - return nil -} - -func (s *Storage) UpsertPaymentsMetadata(ctx context.Context, metadata []*models.PaymentMetadata) error { - if len(metadata) == 0 { - return nil - } - - _, err := s.db.NewInsert(). - Model(&metadata). - On("CONFLICT (payment_id, key) DO UPDATE"). - Set("value = EXCLUDED.value"). - Set("changelog = payment_metadata.changelog || EXCLUDED.changelog"). - Where("payment_metadata.value != EXCLUDED.value"). - Exec(ctx) - if err != nil { - return e("failed to create metadata", err) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/storage/payments_test.go b/components/payments/cmd/connectors/internal/storage/payments_test.go deleted file mode 100644 index 3ed0385399..0000000000 --- a/components/payments/cmd/connectors/internal/storage/payments_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package storage_test - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/stretchr/testify/require" -) - -var ( - p1ID *models.PaymentID - p1T = time.Date(2023, 11, 14, 4, 55, 0, 0, time.UTC) - p1 *models.Payment - - p2ID *models.PaymentID - p2T = time.Date(2023, 11, 14, 4, 54, 0, 0, time.UTC) - p2 *models.Payment -) - -func TestPayments(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testCreatePayments(t, store) - testUpdatePayment(t, store) - testUninstallConnectors(t, store) - testPaymentsDeletedAfterConnectorUninstall(t, store) -} - -func testCreatePayments(t *testing.T, store *storage.Storage) { - p1ID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "test1", - Type: models.PaymentTypePayOut, - }, - ConnectorID: connectorID, - } - p1 = &models.Payment{ - ID: *p1ID, - CreatedAt: p1T, - Reference: "ref1", - Amount: big.NewInt(100), - ConnectorID: connectorID, - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeCardVisa, - Asset: models.Asset("USD/2"), - } - - p2ID = &models.PaymentID{ - PaymentReference: models.PaymentReference{ - Reference: "test2", - Type: models.PaymentTypeTransfer, - }, - ConnectorID: connectorID, - } - p2 = &models.Payment{ - ID: *p2ID, - CreatedAt: p2T, - Reference: "ref2", - Amount: big.NewInt(150), - ConnectorID: connectorID, - Type: models.PaymentTypePayIn, - Status: models.PaymentStatusFailed, - Scheme: models.PaymentSchemeCardVisa, - Asset: models.Asset("EUR/2"), - } - - pFail := &models.Payment{ - ID: *p1ID, - CreatedAt: p1T, - Reference: "ref1", - ConnectorID: connectorID, - Amount: big.NewInt(100), - Type: models.PaymentTypePayOut, - Status: models.PaymentStatusSucceeded, - Scheme: models.PaymentSchemeCardVisa, - Asset: models.Asset("USD/2"), - SourceAccountID: &models.AccountID{ - Reference: "not_existing", - ConnectorID: connectorID, - }, - } - - ids, err := store.UpsertPayments(context.Background(), []*models.Payment{pFail}) - require.Error(t, err) - require.Len(t, ids, 0) - - ids, err = store.UpsertPayments(context.Background(), []*models.Payment{p1}) - require.NoError(t, err) - require.Len(t, ids, 1) - - ids, err = store.UpsertPayments(context.Background(), []*models.Payment{p2}) - require.NoError(t, err) - require.Len(t, ids, 1) - - p1.Status = models.PaymentStatusPending - p2.Status = models.PaymentStatusSucceeded - ids, err = store.UpsertPayments(context.Background(), []*models.Payment{p1, p2}) - require.NoError(t, err) - require.Len(t, ids, 2) - - ids, err = store.UpsertPayments(context.Background(), []*models.Payment{p1, p2}) - require.NoError(t, err) - require.Len(t, ids, 0) - - testGetPayment(t, store, *p1ID, p1, nil) - testGetPayment(t, store, *p2ID, p2, nil) -} - -func testGetPayment( - t *testing.T, - store *storage.Storage, - paymentID models.PaymentID, - expected *models.Payment, - expectedErr error, -) { - payment, err := store.GetPayment(context.Background(), paymentID.String()) - if expectedErr != nil { - require.EqualError(t, err, expectedErr.Error()) - return - } else { - require.NoError(t, err) - } - - payment.CreatedAt = payment.CreatedAt.UTC() - checkPaymentsEqual(t, expected, payment) -} - -func checkPaymentsEqual(t *testing.T, p1, p2 *models.Payment) { - require.Equal(t, p1.ID, p2.ID) - require.Equal(t, p1.CreatedAt.UTC(), p2.CreatedAt.UTC()) - require.Equal(t, p1.Reference, p2.Reference) - require.Equal(t, p1.Amount, p2.Amount) - require.Equal(t, p1.Type, p2.Type) - require.Equal(t, p1.Status, p2.Status) - require.Equal(t, p1.Scheme, p2.Scheme) - require.Equal(t, p1.Asset, p2.Asset) - require.Equal(t, p1.SourceAccountID, p2.SourceAccountID) - require.Equal(t, p1.DestinationAccountID, p2.DestinationAccountID) - require.Equal(t, p1.RawData, p2.RawData) -} - -func testUpdatePayment(t *testing.T, store *storage.Storage) { - p1.CreatedAt = time.Date(2023, 11, 14, 5, 55, 0, 0, time.UTC) - p1.Reference = "ref1_updated" - p1.Amount = big.NewInt(150) - p1.Type = models.PaymentTypePayIn - p1.Status = models.PaymentStatusPending - p1.Scheme = models.PaymentSchemeCardVisa - p1.Asset = models.Asset("USD/2") - - ids, err := store.UpsertPayments(context.Background(), []*models.Payment{p1}) - require.NoError(t, err) - require.Len(t, ids, 1) - - payment, err := store.GetPayment(context.Background(), p1ID.String()) - require.NoError(t, err) - - require.NotEqual(t, p1.Reference, payment.Reference) - - p1.Reference = payment.Reference - testGetPayment(t, store, *p1ID, p1, nil) -} - -func testPaymentsDeletedAfterConnectorUninstall(t *testing.T, store *storage.Storage) { - testGetPayment(t, store, *p1ID, nil, storage.ErrNotFound) - testGetPayment(t, store, *p2ID, nil, storage.ErrNotFound) -} diff --git a/components/payments/cmd/connectors/internal/storage/ping.go b/components/payments/cmd/connectors/internal/storage/ping.go deleted file mode 100644 index 2832abb0b1..0000000000 --- a/components/payments/cmd/connectors/internal/storage/ping.go +++ /dev/null @@ -1,5 +0,0 @@ -package storage - -func (s *Storage) Ping() error { - return s.db.Ping() -} diff --git a/components/payments/cmd/connectors/internal/storage/repository.go b/components/payments/cmd/connectors/internal/storage/repository.go deleted file mode 100644 index f342f965fd..0000000000 --- a/components/payments/cmd/connectors/internal/storage/repository.go +++ /dev/null @@ -1,35 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/uptrace/bun" - "github.com/uptrace/bun/extra/bundebug" -) - -type Storage struct { - db *bun.DB - configEncryptionKey string -} - -const encryptionOptions = "compress-algo=1, cipher-algo=aes256" - -func NewStorage(db *bun.DB, configEncryptionKey string) *Storage { - return &Storage{db: db, configEncryptionKey: configEncryptionKey} -} - -//nolint:unused // used in debug mode -func (s *Storage) debug() { - s.db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) -} - -type Reader interface { - ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) - GetAccount(ctx context.Context, id string) (*models.Account, error) - GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) (*models.BankAccount, error) - GetWebhook(ctx context.Context, id uuid.UUID) (*models.Webhook, error) - GetPayment(ctx context.Context, id string) (*models.Payment, error) - GetTransferReversal(ctx context.Context, id models.TransferReversalID) (*models.TransferReversal, error) -} diff --git a/components/payments/cmd/connectors/internal/storage/sort.go b/components/payments/cmd/connectors/internal/storage/sort.go deleted file mode 100644 index 2ec3d5c0a0..0000000000 --- a/components/payments/cmd/connectors/internal/storage/sort.go +++ /dev/null @@ -1,33 +0,0 @@ -package storage - -import ( - "fmt" - - "github.com/uptrace/bun" -) - -type SortOrder string - -const ( - SortOrderAsc SortOrder = "asc" - SortOrderDesc SortOrder = "desc" -) - -type sortExpression struct { - Column string `json:"column"` - Order SortOrder `json:"order"` -} - -type Sorter []sortExpression - -func (s Sorter) Add(column string, order SortOrder) Sorter { - return append(s, sortExpression{column, order}) -} - -func (s Sorter) Apply(query *bun.SelectQuery) *bun.SelectQuery { - for _, expr := range s { - query = query.Order(fmt.Sprintf("%s %s", expr.Column, expr.Order)) - } - - return query -} diff --git a/components/payments/cmd/connectors/internal/storage/task.go b/components/payments/cmd/connectors/internal/storage/task.go deleted file mode 100644 index 045c24780f..0000000000 --- a/components/payments/cmd/connectors/internal/storage/task.go +++ /dev/null @@ -1,165 +0,0 @@ -package storage - -import ( - "context" - "encoding/json" - - "github.com/formancehq/go-libs/bun/bunpaginate" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -func (s *Storage) UpdateTaskStatus(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, status models.TaskStatus, taskError string) error { - _, err := s.db.NewUpdate().Model(&models.Task{}). - Set("status = ?", status). - Set("error = ?", taskError). - Where("descriptor::TEXT = ?::TEXT", descriptor.ToMessage()). - Where("connector_id = ?", connectorID). - Exec(ctx) - if err != nil { - return e("failed to update task", err) - } - - return nil -} - -func (s *Storage) UpdateTaskState(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, state json.RawMessage) error { - _, err := s.db.NewUpdate().Model(&models.Task{}). - Set("state = ?", state). - Where("descriptor::TEXT = ?::TEXT", descriptor.ToMessage()). - Where("connector_id = ?", connectorID). - Exec(ctx) - if err != nil { - return e("failed to update task", err) - } - - return nil -} - -func (s *Storage) FindAndUpsertTask( - ctx context.Context, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, - status models.TaskStatus, - schedulerOptions models.TaskSchedulerOptions, - taskErr string, -) (*models.Task, error) { - _, err := s.GetTaskByDescriptor(ctx, connectorID, descriptor) - if err != nil && !errors.Is(err, ErrNotFound) { - return nil, e("failed to get task", err) - } - - if err == nil { - err = s.UpdateTaskStatus(ctx, connectorID, descriptor, status, taskErr) - if err != nil { - return nil, e("failed to update task", err) - } - } else { - err = s.CreateTask(ctx, connectorID, descriptor, status, schedulerOptions) - if err != nil { - return nil, e("failed to upsert task", err) - } - } - - return s.GetTaskByDescriptor(ctx, connectorID, descriptor) -} - -func (s *Storage) CreateTask(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, status models.TaskStatus, schedulerOptions models.TaskSchedulerOptions) error { - _, err := s.db.NewInsert().Model(&models.Task{ - ConnectorID: connectorID, - Descriptor: descriptor.ToMessage(), - Status: status, - SchedulerOptions: schedulerOptions, - }).Exec(ctx) - if err != nil { - return e("failed to create task", err) - } - - return nil -} - -func (s *Storage) ListTasksByStatus(ctx context.Context, connectorID models.ConnectorID, status models.TaskStatus) ([]*models.Task, error) { - var tasks []*models.Task - - err := s.db.NewSelect().Model(&tasks). - Where("connector_id = ?", connectorID). - Where("status = ?", status). - Scan(ctx) - if err != nil { - return nil, e("failed to get tasks", err) - } - - return tasks, nil -} - -type TaskQuery struct{} - -type ListTasksQuery bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[TaskQuery]] - -func NewListTasksQuery(opts PaginatedQueryOptions[TaskQuery]) ListTasksQuery { - return ListTasksQuery{ - PageSize: opts.PageSize, - Order: bunpaginate.OrderAsc, - Options: opts, - } -} - -func (s *Storage) ListTasks(ctx context.Context, connectorID models.ConnectorID, q ListTasksQuery) (*bunpaginate.Cursor[models.Task], error) { - return PaginateWithOffset[PaginatedQueryOptions[TaskQuery], models.Task](s, ctx, - (*bunpaginate.OffsetPaginatedQuery[PaginatedQueryOptions[TaskQuery]])(&q), - func(query *bun.SelectQuery) *bun.SelectQuery { - query = query. - Where("connector_id = ?", connectorID). - Order("created_at DESC") - - if q.Options.Sorter != nil { - query = q.Options.Sorter.Apply(query) - } - - return query - }, - ) -} - -func (s *Storage) ReadOldestPendingTask(ctx context.Context, connectorID models.ConnectorID) (*models.Task, error) { - var task models.Task - err := s.db.NewSelect().Model(&task). - Where("connector_id = ?", connectorID). - Where("status = ?", models.TaskStatusPending). - Order("created_at ASC"). - Limit(1). - Scan(ctx) - if err != nil { - return nil, e("failed to get task", err) - } - - return &task, nil -} - -func (s *Storage) GetTask(ctx context.Context, id uuid.UUID) (*models.Task, error) { - var task models.Task - - err := s.db.NewSelect().Model(&task). - Where("id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("failed to get task", err) - } - - return &task, nil -} - -func (s *Storage) GetTaskByDescriptor(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor) (*models.Task, error) { - var task models.Task - err := s.db.NewSelect().Model(&task). - Where("connector_id = ?", connectorID). - Where("descriptor::TEXT = ?::TEXT", descriptor.ToMessage()). - Scan(ctx) - if err != nil { - return nil, e("failed to get task", err) - } - - return &task, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/transfer_initiation.go b/components/payments/cmd/connectors/internal/storage/transfer_initiation.go deleted file mode 100644 index cbaa4dd319..0000000000 --- a/components/payments/cmd/connectors/internal/storage/transfer_initiation.go +++ /dev/null @@ -1,182 +0,0 @@ -package storage - -import ( - "context" - "database/sql" - "time" - - "github.com/formancehq/payments/internal/models" - "github.com/pkg/errors" -) - -func (s *Storage) CreateTransferInitiation(ctx context.Context, transferInitiation *models.TransferInitiation) error { - tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - query := tx.NewInsert(). - Column("id", "created_at", "scheduled_at", "description", "type", "destination_account_id", "provider", "connector_id", "initial_amount", "amount", "asset", "metadata"). - Model(transferInitiation) - - if transferInitiation.SourceAccountID != nil { - query = query.Column("source_account_id") - } - - _, err = query.Exec(ctx) - if err != nil { - return e("failed to create transfer initiation", err) - } - - for _, adjustment := range transferInitiation.RelatedAdjustments { - adj := adjustment - if _, err := tx.NewInsert().Model(adj).Exec(ctx); err != nil { - return e("failed to add adjustment", err) - } - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) AddTransferInitiationAdjustment(ctx context.Context, adjustment *models.TransferInitiationAdjustment) error { - if _, err := s.db.NewInsert().Model(adjustment).Exec(ctx); err != nil { - return e("failed to add adjustment", err) - } - - return nil -} - -func (s *Storage) ReadTransferInitiation(ctx context.Context, id models.TransferInitiationID) (*models.TransferInitiation, error) { - var transferInitiation models.TransferInitiation - - query := s.db.NewSelect(). - Column("id", "created_at", "scheduled_at", "description", "type", "source_account_id", "destination_account_id", "provider", "connector_id", "amount", "asset", "metadata"). - Model(&transferInitiation). - Relation("RelatedAdjustments"). - Where("id = ?", id) - - err := query.Scan(ctx) - if err != nil { - return nil, e("failed to get transfer initiation", err) - } - - transferInitiation.SortRelatedAdjustments() - - transferInitiation.RelatedPayments, err = s.ReadTransferInitiationPayments(ctx, id) - if err != nil { - return nil, e("failed to get transfer initiation payments", err) - } - - return &transferInitiation, nil -} - -func (s *Storage) ReadTransferInitiationPayments(ctx context.Context, id models.TransferInitiationID) ([]*models.TransferInitiationPayment, error) { - var payments []*models.TransferInitiationPayment - - query := s.db.NewSelect(). - Column("transfer_initiation_id", "payment_id", "created_at", "status", "error"). - Model(&payments). - Where("transfer_initiation_id = ?", id). - Order("created_at DESC") - - err := query.Scan(ctx) - if err != nil { - return nil, e("failed to get transfer initiation payments", err) - } - - return payments, nil -} - -func (s *Storage) AddTransferInitiationPaymentID(ctx context.Context, id models.TransferInitiationID, paymentID *models.PaymentID, createdAt time.Time, metadata map[string]string) error { - tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - if paymentID == nil { - return errors.New("payment id is nil") - } - - _, err = tx.NewInsert(). - Column("transfer_initiation_id", "payment_id", "created_at", "status"). - Model(&models.TransferInitiationPayment{ - TransferInitiationID: id, - PaymentID: *paymentID, - CreatedAt: createdAt, - Status: models.TransferInitiationStatusProcessing, - }). - Exec(ctx) - if err != nil { - return e("failed to add transfer initiation payment id", err) - } - - if metadata != nil { - _, err := tx.NewUpdate(). - Model((*models.TransferInitiation)(nil)). - Set("metadata = ?", metadata). - Where("id = ?", id). - Exec(ctx) - if err != nil { - return e("failed to add metadata", err) - } - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) UpdateTransferInitiationPaymentsStatus( - ctx context.Context, - id models.TransferInitiationID, - paymentID *models.PaymentID, - adjustment *models.TransferInitiationAdjustment, -) error { - tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - if paymentID != nil { - query := tx.NewUpdate(). - Model((*models.TransferInitiationPayment)(nil)). - Set("status = ?", adjustment.Status) - - if adjustment.Error != "" { - query = query.Set("error = ?", adjustment.Error) - } - - _, err := query. - Where("transfer_initiation_id = ?", id). - Where("payment_id = ?", paymentID). - Exec(ctx) - if err != nil { - return e("failed to update transfer initiation status", err) - } - } - - if _, err = tx.NewInsert().Model(adjustment).Exec(ctx); err != nil { - return e("failed to add adjustment", err) - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) DeleteTransferInitiation(ctx context.Context, id models.TransferInitiationID) error { - _, err := s.db.NewDelete(). - Model((*models.TransferInitiation)(nil)). - Where("id = ?", id). - Exec(ctx) - if err != nil { - return e("failed to delete transfer initiation", err) - } - - return nil -} diff --git a/components/payments/cmd/connectors/internal/storage/transfer_initiation_test.go b/components/payments/cmd/connectors/internal/storage/transfer_initiation_test.go deleted file mode 100644 index 1dc127c48e..0000000000 --- a/components/payments/cmd/connectors/internal/storage/transfer_initiation_test.go +++ /dev/null @@ -1,275 +0,0 @@ -package storage_test - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - t1ID models.TransferInitiationID - t1T = time.Date(2023, 11, 14, 5, 8, 0, 0, time.UTC) - t1 *models.TransferInitiation - adjumentID1 = uuid.New() - - t2ID models.TransferInitiationID - t2T = time.Date(2023, 11, 14, 5, 7, 0, 0, time.UTC) - t2 *models.TransferInitiation - adjumentID2 = uuid.New() - - tAddPayments = time.Date(2023, 11, 14, 5, 9, 10, 0, time.UTC) - tUpdateStatus1 = time.Date(2023, 11, 14, 5, 9, 15, 0, time.UTC) - tUpdateStatus2 = time.Date(2023, 11, 14, 5, 9, 16, 0, time.UTC) -) - -func TestTransferInitiations(t *testing.T) { - store := newStore(t) - - testInstallConnectors(t, store) - testCreateAccounts(t, store) - testCreatePayments(t, store) - testCreateTransferInitiations(t, store) - testAddTransferInitiationPayments(t, store) - testUpdateTransferInitiationStatus(t, store) - testDeleteTransferInitiations(t, store) - testUninstallConnectors(t, store) - testTransferInitiationsDeletedAfterConnectorUninstall(t, store) -} - -func testCreateTransferInitiations(t *testing.T, store *storage.Storage) { - t1ID = models.TransferInitiationID{ - Reference: "test1", - ConnectorID: connectorID, - } - t1 = &models.TransferInitiation{ - ID: t1ID, - CreatedAt: t1T, - ScheduledAt: t1T, - Description: "test_description", - Type: models.TransferInitiationTypeTransfer, - ConnectorID: connectorID, - Provider: models.ConnectorProviderDummyPay, - Amount: big.NewInt(100), - Asset: models.Asset("USD/2"), - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: adjumentID1, - TransferInitiationID: t1ID, - CreatedAt: t1T, - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - } - - t2ID = models.TransferInitiationID{ - Reference: "test2", - ConnectorID: connectorID, - } - t2 = &models.TransferInitiation{ - ID: t2ID, - CreatedAt: t2T, - ScheduledAt: t2T, - Description: "test_description2", - Type: models.TransferInitiationTypeTransfer, - ConnectorID: connectorID, - Provider: models.ConnectorProviderDummyPay, - Amount: big.NewInt(150), - Asset: models.Asset("USD/2"), - SourceAccountID: &acc1ID, - DestinationAccountID: acc2ID, - RelatedAdjustments: []*models.TransferInitiationAdjustment{ - { - ID: adjumentID2, - TransferInitiationID: t2ID, - CreatedAt: t2T, - Status: models.TransferInitiationStatusWaitingForValidation, - }, - }, - } - - // Missing source account id and destination account id - err := store.CreateTransferInitiation(context.Background(), t1) - require.Error(t, err) - - t1.SourceAccountID = &acc1ID - t1.DestinationAccountID = acc2ID - err = store.CreateTransferInitiation(context.Background(), t1) - require.NoError(t, err) - - err = store.CreateTransferInitiation(context.Background(), t2) - require.NoError(t, err) - - testGetTransferInitiation(t, store, t1ID, false, t1, nil) - testGetTransferInitiation(t, store, t2ID, false, t2, nil) -} - -func testGetTransferInitiation( - t *testing.T, - store *storage.Storage, - id models.TransferInitiationID, - expand bool, - expected *models.TransferInitiation, - expectedErr error, -) { - tf, err := store.ReadTransferInitiation(context.Background(), id) - if expectedErr != nil { - require.EqualError(t, err, expectedErr.Error()) - return - } else { - require.NoError(t, err) - } - - if expand { - payments, err := store.ReadTransferInitiationPayments(context.Background(), id) - require.NoError(t, err) - tf.RelatedPayments = payments - } - - checkTransferInitiationsEqual(t, expected, tf, true) -} - -func checkTransferInitiationsEqual(t *testing.T, t1, t2 *models.TransferInitiation, checkRelatedAdjusment bool) { - require.Equal(t, t1.ID, t2.ID) - require.Equal(t, t1.CreatedAt.UTC(), t2.CreatedAt.UTC()) - require.Equal(t, t1.ScheduledAt.UTC(), t2.ScheduledAt.UTC()) - require.Equal(t, t1.Description, t2.Description) - require.Equal(t, t1.Type, t2.Type) - require.Equal(t, t1.Provider, t2.Provider) - require.Equal(t, t1.Amount, t2.Amount) - require.Equal(t, t1.Asset, t2.Asset) - require.Equal(t, t1.SourceAccountID, t2.SourceAccountID) - require.Equal(t, t1.DestinationAccountID, t2.DestinationAccountID) - for i := range t1.RelatedPayments { - require.Equal(t, t1.RelatedPayments[i].TransferInitiationID, t2.RelatedPayments[i].TransferInitiationID) - require.Equal(t, t1.RelatedPayments[i].PaymentID, t2.RelatedPayments[i].PaymentID) - require.Equal(t, t1.RelatedPayments[i].CreatedAt.UTC(), t2.RelatedPayments[i].CreatedAt.UTC()) - require.Equal(t, t1.RelatedPayments[i].Status, t2.RelatedPayments[i].Status) - require.Equal(t, t1.RelatedPayments[i].Error, t2.RelatedPayments[i].Error) - } - if checkRelatedAdjusment { - for i := range t1.RelatedAdjustments { - require.Equal(t, t1.RelatedAdjustments[i].TransferInitiationID, t2.RelatedAdjustments[i].TransferInitiationID) - require.Equal(t, t1.RelatedAdjustments[i].CreatedAt.UTC(), t2.RelatedAdjustments[i].CreatedAt.UTC()) - require.Equal(t, t1.RelatedAdjustments[i].Status, t2.RelatedAdjustments[i].Status) - require.Equal(t, t1.RelatedAdjustments[i].Error, t2.RelatedAdjustments[i].Error) - require.Equal(t, t1.RelatedAdjustments[i].Metadata, t2.RelatedAdjustments[i].Metadata) - } - } -} - -func testAddTransferInitiationPayments(t *testing.T, store *storage.Storage) { - err := store.AddTransferInitiationPaymentID( - context.Background(), - t1ID, - p1ID, - tAddPayments, - map[string]string{ - "test": "test", - }, - ) - require.NoError(t, err) - - t1.RelatedPayments = []*models.TransferInitiationPayment{ - { - TransferInitiationID: t1ID, - PaymentID: *p1ID, - CreatedAt: tAddPayments, - Status: models.TransferInitiationStatusProcessing, - Error: "", - }, - } - t1.Metadata = map[string]string{ - "test": "test", - } - testGetTransferInitiation(t, store, t1ID, true, t1, nil) - - err = store.AddTransferInitiationPaymentID( - context.Background(), - t1ID, - nil, - tAddPayments, - nil, - ) - require.Error(t, err) - - err = store.AddTransferInitiationPaymentID( - context.Background(), - models.TransferInitiationID{ - Reference: "not_existing", - ConnectorID: connectorID, - }, - p1ID, - tAddPayments, - nil, - ) - require.Error(t, err) -} - -func testUpdateTransferInitiationStatus(t *testing.T, store *storage.Storage) { - adjustment1 := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: t1ID, - CreatedAt: tUpdateStatus1, - Status: models.TransferInitiationStatusRejected, - Error: "test_error", - } - err := store.UpdateTransferInitiationPaymentsStatus( - context.Background(), - t1ID, - nil, - adjustment1, - ) - require.NoError(t, err) - - t1.RelatedAdjustments = append([]*models.TransferInitiationAdjustment{ - adjustment1, - }, t1.RelatedAdjustments...) - testGetTransferInitiation(t, store, t1ID, true, t1, nil) - - adjustment2 := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: t1ID, - CreatedAt: tUpdateStatus2, - Status: models.TransferInitiationStatusFailed, - Error: "test_error2", - } - err = store.UpdateTransferInitiationPaymentsStatus( - context.Background(), - t1ID, - p1ID, - adjustment2, - ) - require.NoError(t, err) - - t1.RelatedPayments[0].Status = models.TransferInitiationStatusFailed - t1.RelatedPayments[0].Error = "test_error2" - t1.RelatedAdjustments = append([]*models.TransferInitiationAdjustment{ - adjustment2, - }, t1.RelatedAdjustments...) - testGetTransferInitiation(t, store, t1ID, true, t1, nil) -} - -func testDeleteTransferInitiations(t *testing.T, store *storage.Storage) { - err := store.DeleteTransferInitiation(context.Background(), t1ID) - require.NoError(t, err) - - testGetTransferInitiation(t, store, t1ID, false, nil, storage.ErrNotFound) - - // Delete does not generate an error when not existing - err = store.DeleteTransferInitiation(context.Background(), models.TransferInitiationID{ - Reference: "not_existing", - ConnectorID: connectorID, - }) - require.NoError(t, err) -} - -func testTransferInitiationsDeletedAfterConnectorUninstall(t *testing.T, store *storage.Storage) { - testGetTransferInitiation(t, store, t1ID, false, nil, storage.ErrNotFound) - testGetTransferInitiation(t, store, t2ID, false, nil, storage.ErrNotFound) -} diff --git a/components/payments/cmd/connectors/internal/storage/transfer_reversal.go b/components/payments/cmd/connectors/internal/storage/transfer_reversal.go deleted file mode 100644 index 3a46dc33a9..0000000000 --- a/components/payments/cmd/connectors/internal/storage/transfer_reversal.go +++ /dev/null @@ -1,116 +0,0 @@ -package storage - -import ( - "context" - "database/sql" - "math/big" - "time" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -func (s *Storage) CreateTransferReversal(ctx context.Context, transferReversal *models.TransferReversal) error { - tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - _, err = tx.NewInsert().Model(transferReversal).Exec(ctx) - if err != nil { - return e("failed to create transfer reversal", err) - } - - adjustment := &models.TransferInitiationAdjustment{ - ID: uuid.New(), - TransferInitiationID: transferReversal.TransferInitiationID, - CreatedAt: time.Now().UTC(), - Status: models.TransferInitiationStatusAskReversed, - Error: "", - Metadata: transferReversal.Metadata, - } - - if _, err = tx.NewInsert().Model(adjustment).Exec(ctx); err != nil { - return e("failed to create adjustment", err) - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) UpdateTransferReversalStatus( - ctx context.Context, - transfer *models.TransferInitiation, - transferReversal *models.TransferReversal, - adjustment *models.TransferInitiationAdjustment, -) error { - tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return err - } - defer func() { - _ = tx.Rollback() - }() - - now := time.Now().UTC() - - _, err = tx.NewUpdate(). - Model(transferReversal). - Set("status = ?", transferReversal.Status). - Set("error = ?", transferReversal.Error). - Set("updated_at = ?", now). - Where("id = ?", transferReversal.ID). - Exec(ctx) - if err != nil { - return e("failed to update transfer reversal status", err) - } - - if transferReversal.Status == models.TransferReversalStatusProcessed { - var amount *big.Int - err = tx.NewUpdate(). - Model((*models.TransferInitiation)(nil)). - Set("amount = amount - ?", transferReversal.Amount). - Where("id = ?", transferReversal.TransferInitiationID). - Returning("amount"). - Scan(ctx, &amount) - if err != nil { - return e("failed to update transfer initiation amount", err) - } - - switch amount.Cmp(big.NewInt(0)) { - case 0: - // amount == 0, so we can mark the transfer as reversed - adjustment.Status = models.TransferInitiationStatusReversed - case 1: - // amount > 0, so we can mark the transfer as partially reversed - adjustment.Status = models.TransferInitiationStatusPartiallyReversed - case -1: - // Should not happened since we have checks in postgres - return errors.New("transfer reversal amount is greater than transfer initiation amount") - } - - transfer.Amount = amount - } - - if _, err := tx.NewInsert().Model(adjustment).Exec(ctx); err != nil { - return e("failed to add adjustment", err) - } - - return e("failed to commit transaction", tx.Commit()) -} - -func (s *Storage) GetTransferReversal(ctx context.Context, id models.TransferReversalID) (*models.TransferReversal, error) { - var ret models.TransferReversal - err := s.db.NewSelect(). - Model(&ret). - Where("id = ?", id). - Scan(ctx) - if err != nil { - return nil, e("failed to get transfer reversal", err) - } - - return &ret, nil -} diff --git a/components/payments/cmd/connectors/internal/storage/webhooks.go b/components/payments/cmd/connectors/internal/storage/webhooks.go deleted file mode 100644 index 68d1a67f4d..0000000000 --- a/components/payments/cmd/connectors/internal/storage/webhooks.go +++ /dev/null @@ -1,41 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -func (s *Storage) CreateWebhook(ctx context.Context, webhook *models.Webhook) error { - _, err := s.db.NewInsert().Model(webhook).Exec(ctx) - if err != nil { - return err - } - - return nil -} - -func (s *Storage) UpdateWebhookRequestBody(ctx context.Context, webhookID uuid.UUID, requestBody []byte) error { - if len(requestBody) == 0 { - return errors.New("requestBody cannot be empty") - } - - _, err := s.db.NewUpdate().Model((*models.Webhook)(nil)).Set("request_body = ?", requestBody).Where("id = ?", webhookID).Exec(ctx) - if err != nil { - return err - } - - return nil -} - -func (s *Storage) GetWebhook(ctx context.Context, id uuid.UUID) (*models.Webhook, error) { - webhook := &models.Webhook{} - err := s.db.NewSelect().Model(webhook).Where("id = ?", id).Scan(ctx) - if err != nil { - return nil, err - } - - return webhook, nil -} diff --git a/components/payments/cmd/connectors/internal/task/context.go b/components/payments/cmd/connectors/internal/task/context.go deleted file mode 100644 index 039bba4dac..0000000000 --- a/components/payments/cmd/connectors/internal/task/context.go +++ /dev/null @@ -1,42 +0,0 @@ -package task - -import ( - "context" -) - -type ConnectorContext interface { - Context() context.Context - Scheduler() Scheduler -} - -type ConnectorCtx struct { - ctx context.Context - scheduler Scheduler -} - -func (ctx *ConnectorCtx) Context() context.Context { - return ctx.ctx -} - -func (ctx *ConnectorCtx) Scheduler() Scheduler { - return ctx.scheduler -} - -func NewConnectorContext(ctx context.Context, scheduler Scheduler) *ConnectorCtx { - return &ConnectorCtx{ - ctx: ctx, - scheduler: scheduler, - } -} - -type taskContextKey struct{} - -var _taskContextKey = taskContextKey{} - -func ContextWithConnectorContext(ctx context.Context, task ConnectorContext) context.Context { - return context.WithValue(ctx, _taskContextKey, task) -} - -func ConnectorContextFromContext(ctx context.Context) ConnectorContext { - return ctx.Value(_taskContextKey).(ConnectorContext) -} diff --git a/components/payments/cmd/connectors/internal/task/errors.go b/components/payments/cmd/connectors/internal/task/errors.go deleted file mode 100644 index 9a1e43d7fb..0000000000 --- a/components/payments/cmd/connectors/internal/task/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -package task - -import "github.com/pkg/errors" - -var ( - // ErrRetryable will be sent by the task if we can retry the task, - // e.g. if the task failed because of a temporary network issue. - ErrRetryable = errors.New("retryable error") - - // ErrNonRetryable will be sent by the task if we can't retry the task, - // e.g. if the task failed because of a validation error. - ErrNonRetryable = errors.New("non-retryable error") -) diff --git a/components/payments/cmd/connectors/internal/task/resolver.go b/components/payments/cmd/connectors/internal/task/resolver.go deleted file mode 100644 index e12057ad7b..0000000000 --- a/components/payments/cmd/connectors/internal/task/resolver.go +++ /dev/null @@ -1,13 +0,0 @@ -package task - -import "github.com/formancehq/payments/internal/models" - -type Resolver interface { - Resolve(descriptor models.TaskDescriptor) Task -} - -type ResolverFn func(descriptor models.TaskDescriptor) Task - -func (fn ResolverFn) Resolve(descriptor models.TaskDescriptor) Task { - return fn(descriptor) -} diff --git a/components/payments/cmd/connectors/internal/task/scheduler.go b/components/payments/cmd/connectors/internal/task/scheduler.go deleted file mode 100644 index 2fd8845e2c..0000000000 --- a/components/payments/cmd/connectors/internal/task/scheduler.go +++ /dev/null @@ -1,739 +0,0 @@ -package task - -import ( - "context" - "encoding/json" - "fmt" - "runtime/debug" - "sync" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/alitto/pond" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "go.uber.org/dig" -) - -var ( - ErrValidation = errors.New("validation error") - ErrAlreadyScheduled = errors.New("already scheduled") - ErrUnableToResolve = errors.New("unable to resolve task") -) - -type Scheduler interface { - Schedule(ctx context.Context, p models.TaskDescriptor, options models.TaskSchedulerOptions) error -} - -type taskHolder struct { - descriptor models.TaskDescriptor - cancel func() - logger logging.Logger - stopChan StopChan -} - -type ContainerCreateFunc func(ctx context.Context, descriptor models.TaskDescriptor, taskID uuid.UUID) (*dig.Container, error) - -type DefaultTaskScheduler struct { - connectorID models.ConnectorID - store Repository - metricsRegistry metrics.MetricsRegistry - containerFactory ContainerCreateFunc - tasks map[string]*taskHolder - mu sync.Mutex - resolver Resolver - stopped bool - workerPool *pond.WorkerPool -} - -func (s *DefaultTaskScheduler) ListTasks(ctx context.Context, q storage.ListTasksQuery) (*bunpaginate.Cursor[models.Task], error) { - return s.store.ListTasks(ctx, s.connectorID, q) -} - -func (s *DefaultTaskScheduler) ReadTask(ctx context.Context, taskID uuid.UUID) (*models.Task, error) { - return s.store.GetTask(ctx, taskID) -} - -func (s *DefaultTaskScheduler) ReadTaskByDescriptor(ctx context.Context, descriptor models.TaskDescriptor) (*models.Task, error) { - taskDescriptor, err := json.Marshal(descriptor) - if err != nil { - return nil, err - } - - return s.store.GetTaskByDescriptor(ctx, s.connectorID, taskDescriptor) -} - -// Schedule schedules a task to be executed. -// Schedule waits for: -// - Context to be done -// - Task creation if the scheduler option is not equal to OPTIONS_RUN_NOW_SYNC -// - Task termination if the scheduler option is equal to OPTIONS_RUN_NOW_SYNC -func (s *DefaultTaskScheduler) Schedule(ctx context.Context, descriptor models.TaskDescriptor, options models.TaskSchedulerOptions) error { - select { - case err := <-s.schedule(ctx, descriptor, options): - return err - case <-ctx.Done(): - return nil - } -} - -// schedule schedules a task to be executed. -// It returns an error chan that will be closed when the task is terminated if -// the scheduler option is equal to OPTIONS_RUN_NOW_SYNC. Otherwise, it will -// return an error chan that will be closed immediately after task creation. -func (s *DefaultTaskScheduler) schedule(ctx context.Context, descriptor models.TaskDescriptor, options models.TaskSchedulerOptions) <-chan error { - s.mu.Lock() - defer s.mu.Unlock() - - returnErrorFunc := func(err error) <-chan error { - errChan := make(chan error, 1) - if err != nil { - errChan <- err - } - close(errChan) - return errChan - } - - taskID, err := descriptor.EncodeToString() - if err != nil { - return returnErrorFunc(err) - } - - if _, ok := s.tasks[taskID]; ok { - switch options.RestartOption { - case models.OPTIONS_STOP_AND_RESTART, models.OPTIONS_RESTART_ALWAYS: - // We still want to restart the task - default: - return returnErrorFunc(ErrAlreadyScheduled) - } - } - - switch options.RestartOption { - case models.OPTIONS_RESTART_NEVER: - _, err := s.ReadTaskByDescriptor(ctx, descriptor) - if err == nil { - return returnErrorFunc(nil) - } - case models.OPTIONS_RESTART_IF_NOT_ACTIVE: - task, err := s.ReadTaskByDescriptor(ctx, descriptor) - if err == nil && task.Status == models.TaskStatusActive { - return nil - } - case models.OPTIONS_STOP_AND_RESTART: - err := s.stopTask(ctx, descriptor) - if err != nil { - return returnErrorFunc(err) - } - case models.OPTIONS_RESTART_ALWAYS: - // Do nothing - } - - errChan := s.startTask(ctx, descriptor, options) - - return errChan -} - -func (s *DefaultTaskScheduler) Shutdown(ctx context.Context) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.stopped = true - - s.logger(ctx).Infof("Stopping scheduler...") - s.workerPool.Stop() - - for name, task := range s.tasks { - task.logger.Debugf("Stopping task") - - if task.stopChan != nil { - errCh := make(chan struct{}) - task.stopChan <- errCh - select { - case <-errCh: - case <-time.After(time.Second): // TODO: Make configurable - task.logger.Debugf("Stopping using stop chan timeout, canceling context") - task.cancel() - } - } else { - task.cancel() - } - - delete(s.tasks, name) - } - - return nil -} - -func (s *DefaultTaskScheduler) Restore(ctx context.Context) error { - tasks, err := s.store.ListTasksByStatus(ctx, s.connectorID, models.TaskStatusActive) - if err != nil { - return err - } - - for _, task := range tasks { - if task.SchedulerOptions.Restart { - task.SchedulerOptions.RestartOption = models.OPTIONS_RESTART_ALWAYS - } - - errChan := s.startTask(ctx, task.GetDescriptor(), task.SchedulerOptions) - select { - case err := <-errChan: - if err != nil { - s.logger(ctx).Errorf("Unable to restore task %s: %s", task.ID, err) - } - case <-ctx.Done(): - } - } - - return nil -} - -func (s *DefaultTaskScheduler) registerTaskError(ctx context.Context, holder *taskHolder, taskErr any) { - var taskError string - - switch v := taskErr.(type) { - case error: - taskError = v.Error() - default: - taskError = fmt.Sprintf("%s", v) - } - - holder.logger.Errorf("Task terminated with error: %s", taskErr) - - err := s.store.UpdateTaskStatus(ctx, s.connectorID, holder.descriptor, models.TaskStatusFailed, taskError) - if err != nil { - holder.logger.Errorf("Error updating task status: %s", taskError) - } -} - -func (s *DefaultTaskScheduler) deleteTask(ctx context.Context, holder *taskHolder) { - s.mu.Lock() - defer s.mu.Unlock() - - taskID, err := holder.descriptor.EncodeToString() - if err != nil { - holder.logger.Errorf("Error encoding task descriptor: %s", err) - - return - } - - delete(s.tasks, taskID) - - if s.stopped { - return - } - - oldestPendingTask, err := s.store.ReadOldestPendingTask(ctx, s.connectorID) - if err != nil { - if errors.Is(err, storage.ErrNotFound) { - return - } - - logging.FromContext(ctx).Error(err) - - return - } - - p := s.resolver.Resolve(oldestPendingTask.GetDescriptor()) - if p == nil { - logging.FromContext(ctx).Errorf("unable to resolve task") - return - } - - errChan := s.startTask(ctx, oldestPendingTask.GetDescriptor(), models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - }) - select { - case err, ok := <-errChan: - if !ok { - return - } - if err != nil { - logging.FromContext(ctx).Error(err) - } - case <-ctx.Done(): - return - } -} - -type StopChan chan chan struct{} - -// Lock should be held when calling this function -func (s *DefaultTaskScheduler) stopTask(ctx context.Context, descriptor models.TaskDescriptor) error { - taskID, err := descriptor.EncodeToString() - if err != nil { - return err - } - - task, ok := s.tasks[taskID] - if !ok { - return nil - } - - task.logger.Infof("Stopping task...") - - if task.stopChan != nil { - errCh := make(chan struct{}) - task.stopChan <- errCh - select { - case <-errCh: - case <-time.After(time.Second): // TODO: Make configurable - task.logger.Debugf("Stopping using stop chan timeout, canceling context") - task.cancel() - } - } else { - task.cancel() - } - - err = s.store.UpdateTaskStatus(ctx, s.connectorID, descriptor, models.TaskStatusStopped, "") - if err != nil { - task.logger.Errorf("Error updating task status: %s", err) - return err - } - - delete(s.tasks, taskID) - - return nil -} - -func (s *DefaultTaskScheduler) startTask(ctx context.Context, descriptor models.TaskDescriptor, options models.TaskSchedulerOptions) <-chan error { - errChan := make(chan error, 1) - - task, err := s.store.FindAndUpsertTask(ctx, s.connectorID, descriptor, - models.TaskStatusActive, options, "") - if err != nil { - errChan <- errors.Wrap(err, "finding task and update") - close(errChan) - return errChan - } - - logger := s.logger(ctx).WithFields(map[string]interface{}{ - "task-id": task.ID, - }) - - taskResolver := s.resolver.Resolve(task.GetDescriptor()) - if taskResolver == nil { - errChan <- ErrUnableToResolve - close(errChan) - return errChan - } - - ctx, cancel := context.WithCancel(ctx) - - holder := &taskHolder{ - cancel: cancel, - logger: logger, - descriptor: descriptor, - } - - container, err := s.containerFactory(ctx, descriptor, task.ID) - if err != nil { - // TODO: Handle error - panic(err) - } - - err = container.Provide(func() context.Context { - return ctx - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() Scheduler { - return s - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() models.ConnectorID { - return s.connectorID - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() models.TaskID { - return models.TaskID(task.ID) - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() StopChan { - s.mu.Lock() - defer s.mu.Unlock() - - holder.stopChan = make(StopChan, 1) - - return holder.stopChan - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() logging.Logger { - return logger - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() metrics.MetricsRegistry { - return s.metricsRegistry - }) - if err != nil { - panic(err) - } - - err = container.Provide(func() StateResolver { - return StateResolverFn(func(ctx context.Context, v any) error { - t, err := s.store.GetTask(ctx, task.ID) - if err != nil { - return err - } - - if t.State == nil || len(t.State) == 0 { - return nil - } - - return json.Unmarshal(t.State, v) - }) - }) - if err != nil { - panic(err) - } - - taskID, err := holder.descriptor.EncodeToString() - if err != nil { - errChan <- err - close(errChan) - return errChan - } - - s.tasks[taskID] = holder - - sendError := false - switch options.ScheduleOption { - case models.OPTIONS_RUN_NOW_SYNC: - sendError = true - fallthrough - case models.OPTIONS_RUN_NOW: - options.Duration = 0 - fallthrough - case models.OPTIONS_RUN_SCHEDULED_AT: - if !options.ScheduleAt.IsZero() { - options.Duration = time.Until(options.ScheduleAt) - if options.Duration < 0 { - options.Duration = 0 - } - } - fallthrough - case models.OPTIONS_RUN_IN_DURATION: - go s.runTaskOnce( - ctx, - logger, - holder, - descriptor, - options, - taskResolver, - container, - sendError, - errChan, - 1, - ) - case models.OPTIONS_RUN_PERIODICALLY: - go s.runTaskPeriodically( - ctx, - logger, - holder, - descriptor, - options, - taskResolver, - container, - ) - } - - if !sendError { - close(errChan) - } - - return errChan -} - -func (s *DefaultTaskScheduler) runTaskOnce( - ctx context.Context, - logger logging.Logger, - holder *taskHolder, - descriptor models.TaskDescriptor, - options models.TaskSchedulerOptions, - taskResolver Task, - container *dig.Container, - sendError bool, - errChan chan error, - attempt int, -) { - // If attempt is > 1, it means that the task is being retried, so no need - // to wait again - if options.Duration > 0 && attempt == 1 { - logger.Infof("Waiting %s before starting task...", options.Duration) - select { - case <-ctx.Done(): - return - case ch := <-holder.stopChan: - logger.Infof("Stopping task...") - close(ch) - return - case <-time.After(options.Duration): - } - } - - logger.Infof("Starting task...") - - defer func() { - defer s.deleteTask(ctx, holder) - - if sendError { - defer close(errChan) - } - - if e := recover(); e != nil { - switch v := e.(type) { - case error: - if errors.Is(v, pond.ErrSubmitOnStoppedPool) { - // Pool is stopped and task is marked as active, - // nothing to do as they will be restarted on - // next startup - return - } - } - - s.registerTaskError(ctx, holder, e) - debug.PrintStack() - - if sendError { - switch v := e.(type) { - case error: - errChan <- v - default: - errChan <- fmt.Errorf("%s", v) - } - } - } - }() - - runF := func() (err error) { - defer func() { - if e := recover(); e != nil { - switch v := e.(type) { - case error: - if errors.Is(v, pond.ErrSubmitOnStoppedPool) { - // In this case, the scheduler is stopped, it means that - // either the connector is uninstalled or the service - // is stopped. In case of the connector being uninstalled, - // it doesn't matter if we send an error or not since - // all data will be deleted. In case of the service being - // stopped, the task should be restarted on next startup, - // so we have to mark it as Retryable. - err = errors.Wrap(ErrRetryable, v.Error()) - return - } else { - panic(e) - } - default: - panic(v) - } - } - }() - - done := make(chan struct{}) - s.workerPool.Submit(func() { - defer close(done) - err = container.Invoke(taskResolver) - }) - select { - case <-done: - case <-ctx.Done(): - return ctx.Err() - } - - return err - } - -loop: - for { - select { - case <-ctx.Done(): - return - default: - } - - err := runF() - switch { - case err == nil: - break loop - case errors.Is(err, ErrRetryable): - logger.Infof("Task terminated with retryable error: %s", err) - continue - case errors.Is(err, ErrNonRetryable): - logger.Infof("Task terminated with non retryable error: %s", err) - fallthrough - default: - if err == context.Canceled { - // Context was canceled, which means the scheduler was stopped - // either by the application being stopped or by the connector - // being removed. In this case, we don't want to update the - // task status, as it will be restarted on next startup. - return - } - - // All other errors - s.registerTaskError(ctx, holder, err) - - if sendError { - errChan <- err - } - - return - } - } - - logger.Infof("Task terminated with success") - - err := s.store.UpdateTaskStatus(ctx, s.connectorID, descriptor, models.TaskStatusTerminated, "") - if err != nil { - logger.Errorf("Error updating task status: %s", err) - if sendError { - errChan <- err - } - } -} - -func (s *DefaultTaskScheduler) runTaskPeriodically( - ctx context.Context, - logger logging.Logger, - holder *taskHolder, - descriptor models.TaskDescriptor, - options models.TaskSchedulerOptions, - taskResolver Task, - container *dig.Container, -) { - defer func() { - defer s.deleteTask(ctx, holder) - - if e := recover(); e != nil { - switch v := e.(type) { - case error: - if errors.Is(v, pond.ErrSubmitOnStoppedPool) { - // In this case, the scheduler is stopped, it means that - // either the connector is uninstalled or the service - // is stopped. In case of the connector being uninstalled, - // it doesn't matter if we send an error or not since - // all data will be deleted. In case of the service being - // stopped, the task should be restarted on next startup, - // so we need to not mark is as an error. - return - } else { - s.registerTaskError(ctx, holder, e) - debug.PrintStack() - } - default: - s.registerTaskError(ctx, holder, e) - debug.PrintStack() - } - - return - } - }() - - processFunc := func() (bool, error) { - var err error - done := make(chan struct{}) - s.workerPool.Submit(func() { - defer close(done) - err = container.Invoke(taskResolver) - }) - select { - case <-done: - case <-ctx.Done(): - return true, nil - case ch := <-holder.stopChan: - logger.Infof("Stopping task...") - close(ch) - return true, nil - } - if err != nil { - return false, err - } - - return false, err - } - - logger.Infof("Starting task...") - ticker := time.NewTicker(options.Duration) - for { - stopped, err := processFunc() - switch { - case err == nil: - // Doing nothing, waiting for the next tick - case errors.Is(err, ErrRetryable): - ticker.Reset(options.Duration) - continue - case errors.Is(err, ErrNonRetryable): - fallthrough - default: - // All other errors - s.registerTaskError(ctx, holder, err) - return - } - - if stopped { - // Task is stopped or context is done - return - } - - select { - case ch := <-holder.stopChan: - logger.Infof("Stopping task...") - close(ch) - return - case <-ctx.Done(): - return - case <-ticker.C: - logger.Infof("Polling trigger, running task...") - } - } -} - -func (s *DefaultTaskScheduler) logger(ctx context.Context) logging.Logger { - return logging.FromContext(ctx).WithFields(map[string]any{ - "component": "scheduler", - "connectorID": s.connectorID, - }) -} - -var _ Scheduler = &DefaultTaskScheduler{} - -func NewDefaultScheduler( - connectorID models.ConnectorID, - store Repository, - containerFactory ContainerCreateFunc, - resolver Resolver, - metricsRegistry metrics.MetricsRegistry, - maxTasks int, -) *DefaultTaskScheduler { - return &DefaultTaskScheduler{ - connectorID: connectorID, - store: store, - metricsRegistry: metricsRegistry, - tasks: map[string]*taskHolder{}, - containerFactory: containerFactory, - resolver: resolver, - workerPool: pond.New(maxTasks, maxTasks), - } -} diff --git a/components/payments/cmd/connectors/internal/task/scheduler_test.go b/components/payments/cmd/connectors/internal/task/scheduler_test.go deleted file mode 100644 index 95734b7d44..0000000000 --- a/components/payments/cmd/connectors/internal/task/scheduler_test.go +++ /dev/null @@ -1,419 +0,0 @@ -package task - -import ( - "context" - "testing" - "time" - - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" - "go.uber.org/dig" -) - -//nolint:gochecknoglobals // allow in tests -var DefaultContainerFactory = ContainerCreateFunc(func(ctx context.Context, descriptor models.TaskDescriptor, taskID uuid.UUID) (*dig.Container, error) { - return dig.New(), nil -}) - -func newDescriptor() models.TaskDescriptor { - return []byte(uuid.New().String()) -} - -func TaskTerminatedWithStatus( - store *InMemoryStore, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, - expectedStatus models.TaskStatus, - errString string, -) func() bool { - return func() bool { - status, resultErr, ok := store.Result(connectorID, descriptor) - if !ok { - return false - } - - if resultErr != errString { - return false - } - - return status == expectedStatus - } -} - -func TaskTerminated(store *InMemoryStore, connectorID models.ConnectorID, descriptor models.TaskDescriptor) func() bool { - return TaskTerminatedWithStatus(store, connectorID, descriptor, models.TaskStatusTerminated, "") -} - -func TaskFailed(store *InMemoryStore, connectorID models.ConnectorID, descriptor models.TaskDescriptor, errStr string) func() bool { - return TaskTerminatedWithStatus(store, connectorID, descriptor, models.TaskStatusFailed, errStr) -} - -func TaskPending(store *InMemoryStore, connectorID models.ConnectorID, descriptor models.TaskDescriptor) func() bool { - return TaskTerminatedWithStatus(store, connectorID, descriptor, models.TaskStatusPending, "") -} - -func TaskActive(store *InMemoryStore, connectorID models.ConnectorID, descriptor models.TaskDescriptor) func() bool { - return TaskTerminatedWithStatus(store, connectorID, descriptor, models.TaskStatusActive, "") -} - -func TestTaskScheduler(t *testing.T) { - t.Parallel() - - l := logrus.New() - if testing.Verbose() { - l.SetLevel(logrus.DebugLevel) - } - - t.Run("Nominal", func(t *testing.T) { - t.Parallel() - - store := NewInMemoryStore() - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - done := make(chan struct{}) - scheduler := NewDefaultScheduler(connectorID, store, - DefaultContainerFactory, ResolverFn(func(descriptor models.TaskDescriptor) Task { - return func(ctx context.Context) error { - select { - case <-ctx.Done(): - return ctx.Err() - case <-done: - return nil - } - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - descriptor := newDescriptor() - err := scheduler.Schedule(context.TODO(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - require.NoError(t, err) - - require.Eventually(t, TaskActive(store, connectorID, descriptor), time.Second, 100*time.Millisecond) - close(done) - require.Eventually(t, TaskTerminated(store, connectorID, descriptor), time.Second, 100*time.Millisecond) - }) - - t.Run("Duplicate task", func(t *testing.T) { - t.Parallel() - - store := NewInMemoryStore() - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - return func(ctx context.Context) error { - <-ctx.Done() - - return ctx.Err() - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - descriptor := newDescriptor() - err := scheduler.Schedule(context.TODO(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - require.NoError(t, err) - require.Eventually(t, TaskActive(store, connectorID, descriptor), time.Second, 100*time.Millisecond) - - err = scheduler.Schedule(context.TODO(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - require.Equal(t, ErrAlreadyScheduled, err) - }) - - t.Run("Error", func(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - store := NewInMemoryStore() - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - return func() error { - return errors.New("test") - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - descriptor := newDescriptor() - err := scheduler.Schedule(context.TODO(), descriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - }) - require.NoError(t, err) - require.Eventually(t, TaskFailed(store, connectorID, descriptor, "test"), time.Second, - 100*time.Millisecond) - }) - - t.Run("Pending", func(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - store := NewInMemoryStore() - descriptor1 := newDescriptor() - descriptor2 := newDescriptor() - - task1Launched := make(chan struct{}) - task2Launched := make(chan struct{}) - - task1Terminated := make(chan struct{}) - task2Terminated := make(chan struct{}) - - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - switch string(descriptor) { - case string(descriptor1): - return func(ctx context.Context) error { - close(task1Launched) - select { - case <-task1Terminated: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - case string(descriptor2): - return func(ctx context.Context) error { - close(task2Launched) - select { - case <-task2Terminated: - return nil - case <-ctx.Done(): - return ctx.Err() - } - } - } - - panic("unknown descriptor") - }), metrics.NewNoOpMetricsRegistry(), 1) - - require.NoError(t, scheduler.Schedule(context.TODO(), descriptor1, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.NoError(t, scheduler.Schedule(context.TODO(), descriptor2, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - - select { - case <-task1Launched: - require.Eventually(t, TaskActive(store, connectorID, descriptor1), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, descriptor2), time.Second, 100*time.Millisecond) - close(task1Terminated) - require.Eventually(t, TaskTerminated(store, connectorID, descriptor1), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, descriptor2), time.Second, 100*time.Millisecond) - close(task2Terminated) - require.Eventually(t, TaskTerminated(store, connectorID, descriptor2), time.Second, 100*time.Millisecond) - case <-task2Launched: - require.Eventually(t, TaskActive(store, connectorID, descriptor1), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, descriptor2), time.Second, 100*time.Millisecond) - close(task2Terminated) - require.Eventually(t, TaskTerminated(store, connectorID, descriptor2), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, descriptor1), time.Second, 100*time.Millisecond) - close(task1Terminated) - require.Eventually(t, TaskTerminated(store, connectorID, descriptor1), time.Second, 100*time.Millisecond) - } - }) - - t.Run("Stop scheduler", func(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - store := NewInMemoryStore() - mainDescriptor := newDescriptor() - workerDescriptor := newDescriptor() - - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - switch string(descriptor) { - case string(mainDescriptor): - return func(ctx context.Context, scheduler Scheduler) { - <-ctx.Done() - require.NoError(t, scheduler.Schedule(ctx, workerDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - } - default: - return func() { - - } - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - require.NoError(t, scheduler.Schedule(context.TODO(), mainDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskActive(store, connectorID, mainDescriptor), time.Second, 100*time.Millisecond) - require.NoError(t, scheduler.Shutdown(context.TODO())) - // the main task should be still marked as active since it failed to - // schedule the worker task because the scheduler was stopped - require.Eventually(t, TaskActive(store, connectorID, mainDescriptor), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, workerDescriptor), time.Second, 100*time.Millisecond) - }) - - t.Run("errors and retryable errors", func(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - store := NewInMemoryStore() - nonRetryableDescriptor := newDescriptor() - retryableDescriptor := newDescriptor() - otherErrorDescriptor := newDescriptor() - noErrorDescriptor := newDescriptor() - - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - switch string(descriptor) { - case string(nonRetryableDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return ErrNonRetryable - } - case string(retryableDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return ErrRetryable - } - case string(otherErrorDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return errors.New("test") - } - case string(noErrorDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return nil - } - default: - return func() { - - } - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - require.NoError(t, scheduler.Schedule(context.TODO(), nonRetryableDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskFailed(store, connectorID, nonRetryableDescriptor, "non-retryable error"), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), otherErrorDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskFailed(store, connectorID, otherErrorDescriptor, "test"), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), noErrorDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskTerminated(store, connectorID, noErrorDescriptor), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), retryableDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_NOW, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskActive(store, connectorID, retryableDescriptor), time.Second, 100*time.Millisecond) - require.NoError(t, scheduler.Shutdown(context.TODO())) - - require.Eventually(t, TaskFailed(store, connectorID, nonRetryableDescriptor, "non-retryable error"), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskFailed(store, connectorID, otherErrorDescriptor, "test"), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskTerminated(store, connectorID, noErrorDescriptor), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, retryableDescriptor), time.Second, 100*time.Millisecond) - }) - - t.Run("errors and retryable errors", func(t *testing.T) { - t.Parallel() - - connectorID := models.ConnectorID{ - Reference: uuid.New(), - Provider: models.ConnectorProviderDummyPay, - } - store := NewInMemoryStore() - nonRetryableDescriptor := newDescriptor() - retryableDescriptor := newDescriptor() - otherErrorDescriptor := newDescriptor() - noErrorDescriptor := newDescriptor() - - scheduler := NewDefaultScheduler(connectorID, store, DefaultContainerFactory, - ResolverFn(func(descriptor models.TaskDescriptor) Task { - switch string(descriptor) { - case string(nonRetryableDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return ErrNonRetryable - } - case string(retryableDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return ErrRetryable - } - case string(otherErrorDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return errors.New("test") - } - case string(noErrorDescriptor): - return func(ctx context.Context, scheduler Scheduler) error { - return nil - } - default: - return func() { - - } - } - }), metrics.NewNoOpMetricsRegistry(), 1) - - require.NoError(t, scheduler.Schedule(context.TODO(), nonRetryableDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: 1 * time.Second, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskFailed(store, connectorID, nonRetryableDescriptor, "non-retryable error"), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), otherErrorDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: 1 * time.Second, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskFailed(store, connectorID, otherErrorDescriptor, "test"), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), noErrorDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: 1 * time.Second, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskActive(store, connectorID, noErrorDescriptor), time.Second, 100*time.Millisecond) - - require.NoError(t, scheduler.Schedule(context.TODO(), retryableDescriptor, models.TaskSchedulerOptions{ - ScheduleOption: models.OPTIONS_RUN_PERIODICALLY, - Duration: 1 * time.Second, - RestartOption: models.OPTIONS_RESTART_NEVER, - })) - require.Eventually(t, TaskActive(store, connectorID, retryableDescriptor), time.Second, 100*time.Millisecond) - require.NoError(t, scheduler.Shutdown(context.TODO())) - - require.Eventually(t, TaskFailed(store, connectorID, nonRetryableDescriptor, "non-retryable error"), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskFailed(store, connectorID, otherErrorDescriptor, "test"), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, noErrorDescriptor), time.Second, 100*time.Millisecond) - require.Eventually(t, TaskActive(store, connectorID, retryableDescriptor), time.Second, 100*time.Millisecond) - }) -} diff --git a/components/payments/cmd/connectors/internal/task/state.go b/components/payments/cmd/connectors/internal/task/state.go deleted file mode 100644 index f1e811f8e4..0000000000 --- a/components/payments/cmd/connectors/internal/task/state.go +++ /dev/null @@ -1,39 +0,0 @@ -package task - -import ( - "context" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/pkg/errors" -) - -type StateResolver interface { - ResolveTo(ctx context.Context, v any) error -} -type StateResolverFn func(ctx context.Context, v any) error - -func (fn StateResolverFn) ResolveTo(ctx context.Context, v any) error { - return fn(ctx, v) -} - -func ResolveTo[State any](ctx context.Context, resolver StateResolver, to *State) (*State, error) { - err := resolver.ResolveTo(ctx, to) - if err != nil { - return nil, err - } - - return to, nil -} - -func MustResolveTo[State any](ctx context.Context, resolver StateResolver, to State) State { - state, err := ResolveTo(ctx, resolver, &to) - if errors.Is(err, storage.ErrNotFound) { - return to - } - - if err != nil { - panic(err) - } - - return *state -} diff --git a/components/payments/cmd/connectors/internal/task/store.go b/components/payments/cmd/connectors/internal/task/store.go deleted file mode 100644 index 63d7f9a3ab..0000000000 --- a/components/payments/cmd/connectors/internal/task/store.go +++ /dev/null @@ -1,21 +0,0 @@ -package task - -import ( - "context" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -type Repository interface { - UpdateTaskStatus(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, status models.TaskStatus, err string) error - FindAndUpsertTask(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor, status models.TaskStatus, schedulerOptions models.TaskSchedulerOptions, err string) (*models.Task, error) - ListTasksByStatus(ctx context.Context, connectorID models.ConnectorID, status models.TaskStatus) ([]*models.Task, error) - ListTasks(ctx context.Context, connectorID models.ConnectorID, q storage.ListTasksQuery) (*bunpaginate.Cursor[models.Task], error) - ReadOldestPendingTask(ctx context.Context, connectorID models.ConnectorID) (*models.Task, error) - GetTask(ctx context.Context, taskID uuid.UUID) (*models.Task, error) - GetTaskByDescriptor(ctx context.Context, connectorID models.ConnectorID, descriptor models.TaskDescriptor) (*models.Task, error) -} diff --git a/components/payments/cmd/connectors/internal/task/storememory.go b/components/payments/cmd/connectors/internal/task/storememory.go deleted file mode 100644 index 90112c715e..0000000000 --- a/components/payments/cmd/connectors/internal/task/storememory.go +++ /dev/null @@ -1,250 +0,0 @@ -package task - -import ( - "context" - "encoding/base64" - "encoding/json" - "fmt" - "strings" - "sync" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/formancehq/payments/internal/models" - "github.com/google/uuid" -) - -type InMemoryStore struct { - mu sync.RWMutex - tasks map[uuid.UUID]models.Task - statuses map[string]models.TaskStatus - created map[string]time.Time - errors map[string]string -} - -func (s *InMemoryStore) GetTask(ctx context.Context, id uuid.UUID) (*models.Task, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - task, ok := s.tasks[id] - if !ok { - return nil, storage.ErrNotFound - } - - return &task, nil -} - -func (s *InMemoryStore) GetTaskByDescriptor( - ctx context.Context, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, -) (*models.Task, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - id, err := descriptor.EncodeToString() - if err != nil { - return nil, err - } - - status, ok := s.statuses[id] - if !ok { - return nil, storage.ErrNotFound - } - - return &models.Task{ - Descriptor: descriptor.ToMessage(), - Status: status, - Error: s.errors[id], - State: nil, - CreatedAt: s.created[id], - }, nil -} - -func (s *InMemoryStore) ListTasks(ctx context.Context, - connectorID models.ConnectorID, - q storage.ListTasksQuery, -) (*bunpaginate.Cursor[models.Task], error) { - s.mu.RLock() - defer s.mu.RUnlock() - - ret := make([]models.Task, 0) - - for id, status := range s.statuses { - if !strings.HasPrefix(id, fmt.Sprintf("%s/", connectorID)) { - continue - } - - var descriptor models.TaskDescriptor - - ret = append(ret, models.Task{ - Descriptor: descriptor.ToMessage(), - Status: status, - Error: s.errors[id], - State: nil, - CreatedAt: s.created[id], - }) - } - - return &bunpaginate.Cursor[models.Task]{ - PageSize: 15, - HasMore: false, - Previous: "", - Next: "", - Data: ret, - }, nil -} - -func (s *InMemoryStore) ReadOldestPendingTask( - ctx context.Context, - connectorID models.ConnectorID, -) (*models.Task, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - var ( - oldestDate time.Time - oldestID string - ) - - for id, status := range s.statuses { - if status != models.TaskStatusPending { - continue - } - - if oldestDate.IsZero() || s.created[id].Before(oldestDate) { - oldestDate = s.created[id] - oldestID = id - } - } - - if oldestDate.IsZero() { - return nil, storage.ErrNotFound - } - - descriptorStr := strings.Split(oldestID, "/")[1] - - var descriptor models.TaskDescriptor - - data, err := base64.StdEncoding.DecodeString(descriptorStr) - if err != nil { - return nil, err - } - - err = json.Unmarshal(data, &descriptor) - if err != nil { - return nil, err - } - - return &models.Task{ - Descriptor: descriptor.ToMessage(), - Status: models.TaskStatusPending, - State: nil, - CreatedAt: s.created[oldestID], - }, nil -} - -func (s *InMemoryStore) ListTasksByStatus( - ctx context.Context, - connectorID models.ConnectorID, - taskStatus models.TaskStatus, -) ([]*models.Task, error) { - cursor, err := s.ListTasks(ctx, connectorID, storage.NewListTasksQuery(storage.NewPaginatedQueryOptions(storage.TaskQuery{}))) - if err != nil { - return nil, err - } - - ret := make([]*models.Task, 0) - - for _, v := range cursor.Data { - if v.Status != taskStatus { - continue - } - - ret = append(ret, &v) - } - - return ret, nil -} - -func (s *InMemoryStore) FindAndUpsertTask( - ctx context.Context, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, - status models.TaskStatus, - options models.TaskSchedulerOptions, - taskErr string, -) (*models.Task, error) { - err := s.UpdateTaskStatus(ctx, connectorID, descriptor, status, taskErr) - if err != nil { - return nil, err - } - - return &models.Task{ - Descriptor: descriptor.ToMessage(), - Status: status, - Error: taskErr, - State: nil, - }, nil -} - -func (s *InMemoryStore) UpdateTaskStatus( - ctx context.Context, - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, - status models.TaskStatus, - taskError string, -) error { - s.mu.Lock() - defer s.mu.Unlock() - - taskID, err := descriptor.EncodeToString() - if err != nil { - return err - } - - key := fmt.Sprintf("%s/%s", connectorID, taskID) - - s.statuses[key] = status - - s.errors[key] = taskError - if _, ok := s.created[key]; !ok { - s.created[key] = time.Now() - } - - return nil -} - -func (s *InMemoryStore) Result( - connectorID models.ConnectorID, - descriptor models.TaskDescriptor, -) (models.TaskStatus, string, bool) { - s.mu.RLock() - defer s.mu.RUnlock() - - taskID, err := descriptor.EncodeToString() - if err != nil { - panic(err) - } - - key := fmt.Sprintf("%s/%s", connectorID, taskID) - - status, ok := s.statuses[key] - if !ok { - return "", "", false - } - - return status, s.errors[key], true -} - -func NewInMemoryStore() *InMemoryStore { - return &InMemoryStore{ - statuses: make(map[string]models.TaskStatus), - errors: make(map[string]string), - created: make(map[string]time.Time), - } -} - -var _ Repository = &InMemoryStore{} diff --git a/components/payments/cmd/connectors/internal/task/task.go b/components/payments/cmd/connectors/internal/task/task.go deleted file mode 100644 index ce2673226a..0000000000 --- a/components/payments/cmd/connectors/internal/task/task.go +++ /dev/null @@ -1,3 +0,0 @@ -package task - -type Task any diff --git a/components/payments/cmd/connectors/root.go b/components/payments/cmd/connectors/root.go deleted file mode 100644 index 8c46a05a23..0000000000 --- a/components/payments/cmd/connectors/root.go +++ /dev/null @@ -1,46 +0,0 @@ -package connectors - -import ( - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/otlp" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/service" - "github.com/spf13/cobra" -) - -func NewConnectors( - version string, - addAutoMigrateCommandFunc func(cmd *cobra.Command), -) *cobra.Command { - - root := &cobra.Command{ - Use: "connectors", - Short: "connectors", - DisableAutoGenTag: true, - } - - cobra.EnableTraverseRunHooks = true - - server := newServer(version) - addAutoMigrateCommandFunc(server) - root.AddCommand(server) - - server.Flags().BoolP("toggle", "t", false, "Help message for toggle") - server.Flags().String(postgresURIFlag, "postgres://localhost/payments", "PostgreSQL DB address") - server.Flags().String(configEncryptionKeyFlag, "", "Config encryption key") - server.Flags().String(envFlag, "local", "Environment") - server.Flags().String(listenFlag, ":8080", "Listen address") - - service.AddFlags(server.Flags()) - otlp.AddFlags(server.Flags()) - otlptraces.AddFlags(server.Flags()) - otlpmetrics.AddFlags(server.Flags()) - publish.AddFlags(serviceName, server.Flags()) - iam.AddFlags(server.Flags()) - auth.AddFlags(server.Flags()) - - return root -} diff --git a/components/payments/cmd/connectors/serve.go b/components/payments/cmd/connectors/serve.go deleted file mode 100644 index 2a9f364912..0000000000 --- a/components/payments/cmd/connectors/serve.go +++ /dev/null @@ -1,94 +0,0 @@ -package connectors - -import ( - "github.com/bombsimon/logrusr/v3" - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/payments/cmd/connectors/internal/api" - "github.com/formancehq/payments/cmd/connectors/internal/metrics" - "github.com/formancehq/payments/cmd/connectors/internal/storage" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - "go.uber.org/fx" -) - -const ( - stackURLFlag = "stack-url" - postgresURIFlag = "postgres-uri" - configEncryptionKeyFlag = "config-encryption-key" - envFlag = "env" - listenFlag = "listen" - - serviceName = "Payments" -) - -func newServer(version string) *cobra.Command { - return &cobra.Command{ - Use: "serve", - Aliases: []string{"server"}, - Short: "Launch server", - SilenceUsage: true, - RunE: runServer(version), - } -} - -func runServer(version string) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - setLogger() - - databaseOptions, err := prepareDatabaseOptions(cmd, service.IsDebug(cmd)) - if err != nil { - return err - } - - options := make([]fx.Option, 0) - - options = append(options, databaseOptions) - options = append(options, - otlptraces.FXModuleFromFlags(cmd), - otlpmetrics.FXModuleFromFlags(cmd), - auth.FXModuleFromFlags(cmd), - fx.Provide(fx.Annotate(noop.NewMeterProvider, fx.As(new(metric.MeterProvider)))), - fx.Provide(metrics.RegisterMetricsRegistry), - ) - options = append(options, publish.FXModuleFromFlags(cmd, service.IsDebug(cmd))) - listen, _ := cmd.Flags().GetString(listenFlag) - stackURL, _ := cmd.Flags().GetString(stackURLFlag) - otelTraces, _ := cmd.Flags().GetBool(otlptraces.OtelTracesFlag) - - options = append(options, api.HTTPModule(sharedapi.ServiceInfo{ - Version: version, - Debug: service.IsDebug(cmd), - }, listen, stackURL, otelTraces)) - - return service.New(cmd.OutOrStdout(), options...).Run(cmd) - } -} - -func setLogger() { - // Add a dedicated logger for opentelemetry in case of error - otel.SetLogger(logrusr.New(logrus.New().WithField("component", "otlp"))) -} - -func prepareDatabaseOptions(cmd *cobra.Command, debug bool) (fx.Option, error) { - configEncryptionKey, _ := cmd.Flags().GetString(configEncryptionKeyFlag) - if configEncryptionKey == "" { - return nil, errors.New("missing config encryption key") - } - - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return nil, err - } - - return storage.Module(*connectionOptions, configEncryptionKey, debug), nil -} diff --git a/components/payments/cmd/migrate.go b/components/payments/cmd/migrate.go deleted file mode 100644 index 050f361b64..0000000000 --- a/components/payments/cmd/migrate.go +++ /dev/null @@ -1,36 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/bun/bunmigrate" - - "github.com/formancehq/payments/internal/storage" - "github.com/spf13/cobra" - "github.com/uptrace/bun" - - // Import the postgres driver. - _ "github.com/lib/pq" -) - -var ( - configEncryptionKeyFlag = "config-encryption-key" - autoMigrateFlag = "auto-migrate" -) - -func newMigrate() *cobra.Command { - return bunmigrate.NewDefaultCommand(Migrate, func(cmd *cobra.Command) { - cmd.Flags().String(configEncryptionKeyFlag, "", "Config encryption key") - }) -} - -func Migrate(cmd *cobra.Command, args []string, db *bun.DB) error { - cfgEncryptionKey, _ := cmd.Flags().GetString(configEncryptionKeyFlag) - if cfgEncryptionKey == "" { - cfgEncryptionKey = cmd.Flag(configEncryptionKeyFlag).Value.String() - } - - if cfgEncryptionKey != "" { - storage.EncryptionKey = cfgEncryptionKey - } - - return storage.Migrate(cmd.Context(), db) -} diff --git a/components/payments/cmd/root.go b/components/payments/cmd/root.go deleted file mode 100644 index 59ce23bb51..0000000000 --- a/components/payments/cmd/root.go +++ /dev/null @@ -1,56 +0,0 @@ -//nolint:gochecknoglobals,golint,revive // allow for cobra & logrus init -package cmd - -import ( - "github.com/formancehq/go-libs/bun/bunmigrate" - "github.com/formancehq/go-libs/service" - - _ "github.com/bombsimon/logrusr/v3" - "github.com/formancehq/payments/cmd/api" - "github.com/formancehq/payments/cmd/connectors" - "github.com/spf13/cobra" -) - -var ( - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func NewRootCommand() *cobra.Command { - root := &cobra.Command{ - Use: "payments", - Short: "payments", - DisableAutoGenTag: true, - Version: Version, - } - - version := newVersion() - root.AddCommand(version) - - migrate := newMigrate() - root.AddCommand(migrate) - - api := api.NewAPI(Version, addAutoMigrateCommand) - root.AddCommand(api) - - connectors := connectors.NewConnectors(Version, addAutoMigrateCommand) - root.AddCommand(connectors) - - return root -} - -func Execute() { - service.Execute(NewRootCommand()) -} - -func addAutoMigrateCommand(cmd *cobra.Command) { - cmd.Flags().Bool(autoMigrateFlag, false, "Auto migrate database") - cmd.PreRunE = func(cmd *cobra.Command, args []string) error { - autoMigrate, _ := cmd.Flags().GetBool(autoMigrateFlag) - if autoMigrate { - return bunmigrate.Run(cmd, args, Migrate) - } - return nil - } -} diff --git a/components/payments/cmd/version.go b/components/payments/cmd/version.go deleted file mode 100644 index 9cad90866d..0000000000 --- a/components/payments/cmd/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "log" - - "github.com/spf13/cobra" -) - -func newVersion() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Get version", - Run: printVersion, - } -} - -func printVersion(cmd *cobra.Command, args []string) { - log.Printf("Version: %s \n", Version) - log.Printf("Date: %s \n", BuildDate) - log.Printf("Commit: %s \n", Commit) -} diff --git a/components/payments/docker-compose.yml b/components/payments/docker-compose.yml deleted file mode 100644 index e58c15f77a..0000000000 --- a/components/payments/docker-compose.yml +++ /dev/null @@ -1,82 +0,0 @@ -version: '3.8' -volumes: - postgres: - -services: - postgres: - image: "postgres:14-alpine" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U payments -p ${POSTGRES_PORT:-5432}"] - interval: 10s - timeout: 5s - retries: 5 - ports: - - "${POSTGRES_PORT:-5432}:${POSTGRES_PORT:-5432}" - environment: - POSTGRES_USER: "payments" - POSTGRES_PASSWORD: "payments" - POSTGRES_DB: "payments" - command: -p ${POSTGRES_PORT:-5432} - volumes: - - ./local_env/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql - - payments-migrate: - image: golang:1.22.4-alpine3.19 - command: go run ./ migrate up - depends_on: - postgres: - condition: service_healthy - volumes: - - .:/app/components/payments - - ../../libs:/app/libs - working_dir: /app/components/payments - environment: - POSTGRES_URI: postgres://payments:payments@postgres:${POSTGRES_PORT:-5432}/payments?sslmode=disable - - payments-api: - image: golang:1.22.4-alpine3.19 - command: go run ./ api server - healthcheck: - test: [ "CMD", "curl", "-f", "http://127.0.0.1:8080/_healthcheck" ] - interval: 10s - timeout: 5s - retries: 5 - depends_on: - postgres: - condition: service_healthy - payments-migrate: - condition: service_completed_successfully - ports: - - "8080:8080" - volumes: - - .:/app/components/payments - - ../../libs:/app/libs - working_dir: /app/components/payments - environment: - DEBUG: ${DEBUG:-"true"} - POSTGRES_URI: postgres://payments:payments@postgres:${POSTGRES_PORT:-5432}/payments?sslmode=disable - CONFIG_ENCRYPTION_KEY: mysuperencryptionkey - - payments-connectors: - image: golang:1.22.4-alpine3.19 - command: go run ./ connectors server - healthcheck: - test: [ "CMD", "curl", "-f", "http://127.0.0.1:8081/_healthcheck" ] - interval: 10s - timeout: 5s - retries: 5 - depends_on: - postgres: - condition: service_healthy - payments-migrate: - condition: service_completed_successfully - ports: - - "8081:8080" - volumes: - - .:/app/components/payments - - ../../libs:/app/libs - working_dir: /app/components/payments - environment: - DEBUG: ${DEBUG:-"true"} - POSTGRES_URI: postgres://payments:payments@postgres:${POSTGRES_PORT:-5432}/payments?sslmode=disable - CONFIG_ENCRYPTION_KEY: mysuperencryptionkey \ No newline at end of file diff --git a/components/payments/go.mod b/components/payments/go.mod deleted file mode 100644 index ce4081d2f1..0000000000 --- a/components/payments/go.mod +++ /dev/null @@ -1,205 +0,0 @@ -module github.com/formancehq/payments - -go 1.22.0 - -toolchain go1.22.7 - -require ( - github.com/ThreeDotsLabs/watermill v1.3.7 - github.com/adyen/adyen-go-api-library/v7 v7.3.1 - github.com/alitto/pond v1.8.3 - github.com/bombsimon/logrusr/v3 v3.1.0 - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/formancehq/go-libs v1.7.1 - github.com/formancehq/payments/genericclient v0.0.0-00010101000000-000000000000 - github.com/get-momo/atlar-v1-go-client v1.2.1 - github.com/gibson042/canonicaljson-go v1.0.3 - github.com/go-openapi/runtime v0.26.0 - github.com/go-openapi/strfmt v0.21.8 - github.com/golang/mock v1.6.0 - github.com/google/uuid v1.6.0 - github.com/gorilla/mux v1.8.1 - github.com/hashicorp/golang-lru/v2 v2.0.4 - github.com/jackc/pgx/v5 v5.7.1 - github.com/lib/pq v1.10.9 - github.com/pkg/errors v0.9.1 - github.com/rs/cors v1.11.1 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/afero v1.11.0 - github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 - github.com/stripe/stripe-go/v72 v72.122.0 - github.com/uptrace/bun v1.2.3 - github.com/uptrace/bun/dialect/pgdialect v1.2.3 - github.com/uptrace/bun/extra/bundebug v1.2.3 - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/metric v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/dig v1.18.0 - go.uber.org/fx v1.22.2 - go.uber.org/mock v0.4.0 - golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 -) - -require ( - dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/IBM/sarama v1.43.3 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 // indirect - github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 // indirect - github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 // indirect - github.com/ajg/form v1.5.1 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/docker v27.3.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect - github.com/eapache/queue v1.1.0 // indirect - github.com/fatih/color v1.17.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect - github.com/go-chi/render v1.0.3 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/analysis v0.21.4 // indirect - github.com/go-openapi/errors v0.20.4 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/loads v0.21.2 // indirect - github.com/go-openapi/spec v0.20.9 // indirect - github.com/go-openapi/swag v0.22.4 // indirect - github.com/go-openapi/validate v0.22.2 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/muhlemmer/gu v0.3.1 // indirect - github.com/muhlemmer/httpforwarded v0.1.0 // indirect - github.com/nats-io/nats.go v1.37.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/ory/dockertest/v3 v3.11.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/shirou/gopsutil/v4 v4.24.8 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun/extra/bunotel v1.2.3 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/dburl v0.23.2 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zitadel/oidc/v2 v2.12.2 // indirect - go.mongodb.org/mongo-driver v1.12.0 // indirect - go.opentelemetry.io/contrib/instrumentation/host v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/formancehq/payments/genericclient => ./cmd/connectors/internal/connectors/generic/client/generated diff --git a/components/payments/go.sum b/components/payments/go.sum deleted file mode 100644 index fc175621c1..0000000000 --- a/components/payments/go.sum +++ /dev/null @@ -1,2026 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= -cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= -cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= -cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= -cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= -cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= -cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= -cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= -cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= -cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= -cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= -cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= -cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= -cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= -cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= -cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= -cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= -cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= -cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= -cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= -cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= -cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= -cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= -cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= -cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= -cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= -cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= -cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= -cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= -cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= -cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= -cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= -cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= -cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= -cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= -cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= -cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= -cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= -cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= -cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= -cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= -cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= -cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= -cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= -cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= -cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= -cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= -cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= -cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= -cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= -cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= -cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= -cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= -cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= -cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= -cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= -cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= -cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= -cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= -cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= -cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= -cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= -cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= -cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= -cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= -cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= -cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= -cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= -cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= -cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= -cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= -cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= -cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= -cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= -cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= -cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= -cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= -cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= -cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= -cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= -cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= -cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= -cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= -cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= -cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= -cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= -cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= -cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= -cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= -cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= -cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= -cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= -cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= -cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= -cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= -cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= -cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= -cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= -cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= -cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= -cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= -cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= -cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= -cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= -cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= -cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= -cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= -cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= -cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= -cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= -cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= -cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= -cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= -cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= -cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= -cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= -cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= -cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= -cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= -cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= -cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= -cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= -cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= -cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= -cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= -cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= -cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= -cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= -cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= -cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= -cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= -cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= -cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= -cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= -cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= -cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= -cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= -cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= -cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= -cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= -cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= -cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= -cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= -cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= -cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= -cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= -cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= -cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= -cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= -cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= -cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= -cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= -cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= -cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= -cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= -cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= -cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= -cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= -cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= -cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= -cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= -cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= -cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= -cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= -cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= -cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= -cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= -cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= -cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= -cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= -cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= -cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= -cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= -cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= -cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= -cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= -cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= -cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= -cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= -cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= -cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= -cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= -cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= -cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= -cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= -cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= -cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= -cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= -cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= -cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= -cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= -cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= -cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= -cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= -cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= -cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= -cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= -cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= -cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= -cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= -cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= -cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= -cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= -cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= -cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= -cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= -cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= -cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= -cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= -cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= -cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= -cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= -cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= -cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= -cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= -cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= -cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= -cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= -cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= -cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= -cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= -cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= -cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= -cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= -cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= -cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= -cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= -cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= -cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= -cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= -cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= -cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= -cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= -cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= -cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= -cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= -cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= -cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= -cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= -cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= -cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= -cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= -cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= -cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= -cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= -cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= -cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= -cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= -cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= -cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= -cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= -cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= -git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= -github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= -github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 h1:M0iYM5HsGcoxtiQqprRlYZNZnGk3w5LsE9RbC2R8myQ= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1/go.mod h1:RwGHEzGsEEXC/rQNLWQqR83+WPlABgOgnv2kTB56Y4Y= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 h1:ud+4txnRgtr3kZXfXZ5+C7kVQEvsLc5HSNUEa0g+X1Q= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5/go.mod h1:t4o+4A6GB+XC8WL3DandhzPwd265zQuyWMQC/I+WIOU= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 h1:afAkAFzeooBRQvxElR+6xoigXKCukcZXnE9ACxhwlPI= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1/go.mod h1:stjbT+s4u/s5ime5jdIyvPyjBGwGeJewIN7jxH8gp4k= -github.com/adyen/adyen-go-api-library/v7 v7.3.1 h1:NToWy5oZDH5Juz45h9GTlidGFldW10xvaihCJIOWZcw= -github.com/adyen/adyen-go-api-library/v7 v7.3.1/go.mod h1:z9oHJsUpqgCkBhKa8hpBgQvTU8ObRfvO0NKEYUoocx0= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= -github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= -github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= -github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bombsimon/logrusr/v3 v3.1.0 h1:zORbLM943D+hDMGgyjMhSAz/iDz86ZV72qaak/CA0zQ= -github.com/bombsimon/logrusr/v3 v3.1.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco= -github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= -github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/get-momo/atlar-v1-go-client v1.2.1 h1:sKWd0maMshxBErGXsYVGhGIB+zFxynrWLNHnegB4lXs= -github.com/get-momo/atlar-v1-go-client v1.2.1/go.mod h1:qcLoXEhjTCOeBqAzG2tucpvxGJS2LYNwaU7WnJYnO64= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gibson042/canonicaljson-go v1.0.3 h1:EAyF8L74AWabkyUmrvEFHEt/AGFQeD6RfwbAuf0j1bI= -github.com/gibson042/canonicaljson-go v1.0.3/go.mod h1:DsLpJTThXyGNO+KZlI85C1/KDcImpP67k/RKVjcaEqo= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= -github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= -github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= -github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= -github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= -github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWLG6M= -github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= -github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= -github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc= -github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= -github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= -github.com/go-openapi/strfmt v0.21.8 h1:VYBUoKYRLAlgKDrIxR/I0lKrztDQ0tuTDrbhLVP8Erg= -github.com/go-openapi/strfmt v0.21.8/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KAzYjclFs3ew= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/validate v0.22.2 h1:Lda8nadL/5kIvS5mdXCAIuZ7IVXvKFIppLnw+EZh+n0= -github.com/go-openapi/validate v0.22.2/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M= -github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru/v2 v2.0.4 h1:7GHuZcgid37q8o5i3QI9KMT4nCWQQ3Kx3Ov6bb9MfK0= -github.com/hashicorp/golang-lru/v2 v2.0.4/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= -github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= -github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= -github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= -github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= -github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= -github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw= -github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.20 h1:CXDTYNHeBiAKBTAIP2gjpgbWap2GhATnTLgP8etyvEI= -github.com/nats-io/nats-server/v2 v2.10.20/go.mod h1:hgcPnoUtMfxz1qVOvLZGurVypQ+Cg6GXVXjG53iHk+M= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= -github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= -github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= -github.com/shirou/gopsutil/v4 v4.24.8 h1:pVQjIenQkIhqO81mwTaXjTzOMT7d3TZkf43PlVFHENI= -github.com/shirou/gopsutil/v4 v4.24.8/go.mod h1:wE0OrJtj4dG+hYkxqDH3QiBICdKSf04/npcvLLc/oRg= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stripe/stripe-go/v72 v72.122.0 h1:eRXWqnEwGny6dneQ5BsxGzUCED5n180u8n665JHlut8= -github.com/stripe/stripe-go/v72 v72.122.0/go.mod h1:QwqJQtduHubZht9mek5sds9CtQcKFdsykV9ZepRWwo0= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc= -github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -github.com/zitadel/oidc/v2 v2.12.2 h1:3kpckg4rurgw7w7aLJrq7yvRxb2pkNOtD08RH42vPEs= -github.com/zitadel/oidc/v2 v2.12.2/go.mod h1:vhP26g1g4YVntcTi0amMYW3tJuid70nxqxf+kb6XKgg= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= -go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0 h1:QaNUlLvmettd1vnmFHrgBYQHearxWP3uO4h4F3pVtkM= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0/go.mod h1:cJu+5jZwoZfkBOECSFtBZK/O7h/pY5djn0fwnIGnQ4A= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0 h1:V/Cy5A2ydwvyED4ewwXJ441R3QllG+U8tXXVOjPeX4Y= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0/go.mod h1:fsY+EfHPwa1bQcxOUPv1FWaQXAwY+RliLRs6B6qgJes= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 h1:GotCpbh7YkCHdFs+hYMdvAEyGsBZifFognqrOnBwyJM= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0/go.mod h1:6b0AS55EEPj7qP44khqF5dqTUq+RkakDMShFaW1EcA4= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 h1:VrMAbeJz4gnVDg2zEzjHG4dEH86j4jO6VYB+NgtGD8s= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0/go.mod h1:qqN/uFdpeitTvm+JDqqnjm517pmQRYxTORbETHq5tOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 h1:IyFlqNsi8VT/nwYlLJfdM0y1gavxGpEvnf6FtVfZ6X4= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0/go.mod h1:bxiX8eUeKoAEQmbq/ecUT8UqZwCjZW52yJrXJUSozsk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= -go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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-20220601150217-0de741cfad7f/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-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= -gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= -google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= -google.golang.org/api v0.124.0/go.mod h1:xu2HQurE5gi/3t1aFCvhPD781p0a3p11sdunTJ2BlP4= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= -google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= -modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= -modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/components/payments/internal/messages/accounts.go b/components/payments/internal/messages/accounts.go deleted file mode 100644 index 07636f9fd3..0000000000 --- a/components/payments/internal/messages/accounts.go +++ /dev/null @@ -1,49 +0,0 @@ -package messages - -import ( - "encoding/json" - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type accountMessagePayload struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - Reference string `json:"reference"` - ConnectorID string `json:"connectorId"` - Provider string `json:"provider"` - DefaultAsset string `json:"defaultAsset"` - AccountName string `json:"accountName"` - Type string `json:"type"` - RawData json.RawMessage `json:"rawData"` -} - -func (m *Messages) NewEventSavedAccounts(provider models.ConnectorProvider, account *models.Account) publish.EventMessage { - payload := accountMessagePayload{ - ID: account.ID.String(), - CreatedAt: account.CreatedAt, - Reference: account.Reference, - ConnectorID: account.ConnectorID.String(), - DefaultAsset: account.DefaultAsset.String(), - AccountName: account.AccountName, - Type: string(account.Type), - Provider: provider.String(), - RawData: account.RawData, - } - - if account.Type == models.AccountTypeExternalFormance { - payload.Type = models.AccountTypeExternal.String() - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedAccounts, - Payload: payload, - } -} diff --git a/components/payments/internal/messages/balances.go b/components/payments/internal/messages/balances.go deleted file mode 100644 index f65139c372..0000000000 --- a/components/payments/internal/messages/balances.go +++ /dev/null @@ -1,39 +0,0 @@ -package messages - -import ( - "math/big" - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type balanceMessagePayload struct { - AccountID string `json:"accountID"` - ConnectorID string `json:"connectorId"` - Provider string `json:"provider"` - CreatedAt time.Time `json:"createdAt"` - Asset string `json:"asset"` - Balance *big.Int `json:"balance"` -} - -func (m *Messages) NewEventSavedBalances(balance *models.Balance) publish.EventMessage { - payload := balanceMessagePayload{ - CreatedAt: balance.CreatedAt, - ConnectorID: balance.ConnectorID.String(), - Provider: balance.ConnectorID.Provider.String(), - AccountID: balance.AccountID.String(), - Asset: balance.Asset.String(), - Balance: balance.Balance, - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedBalances, - Payload: payload, - } -} diff --git a/components/payments/internal/messages/bank_account.go b/components/payments/internal/messages/bank_account.go deleted file mode 100644 index a661f27900..0000000000 --- a/components/payments/internal/messages/bank_account.go +++ /dev/null @@ -1,63 +0,0 @@ -package messages - -import ( - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type bankAccountMessagePayload struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - Name string `json:"name"` - AccountNumber string `json:"accountNumber"` - IBAN string `json:"iban"` - SwiftBicCode string `json:"swiftBicCode"` - Country string `json:"country"` - RelatedAccounts []bankAccountRelatedAccountsPayload `json:"adjustments"` -} - -type bankAccountRelatedAccountsPayload struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - AccountID string `json:"accountID"` - ConnectorID string `json:"connectorID"` - Provider string `json:"provider"` -} - -func (m *Messages) NewEventSavedBankAccounts(bankAccount *models.BankAccount) publish.EventMessage { - bankAccount.Offuscate() - - payload := bankAccountMessagePayload{ - ID: bankAccount.ID.String(), - CreatedAt: bankAccount.CreatedAt, - Name: bankAccount.Name, - AccountNumber: bankAccount.AccountNumber, - IBAN: bankAccount.IBAN, - SwiftBicCode: bankAccount.SwiftBicCode, - Country: bankAccount.Country, - } - - for _, relatedAccount := range bankAccount.RelatedAccounts { - relatedAccount := bankAccountRelatedAccountsPayload{ - ID: relatedAccount.ID.String(), - CreatedAt: relatedAccount.CreatedAt, - AccountID: relatedAccount.AccountID.String(), - Provider: relatedAccount.ConnectorID.Provider.String(), - ConnectorID: relatedAccount.ConnectorID.String(), - } - - payload.RelatedAccounts = append(payload.RelatedAccounts, relatedAccount) - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedBankAccount, - Payload: payload, - } -} diff --git a/components/payments/internal/messages/connectors.go b/components/payments/internal/messages/connectors.go deleted file mode 100644 index 0349b73326..0000000000 --- a/components/payments/internal/messages/connectors.go +++ /dev/null @@ -1,28 +0,0 @@ -package messages - -import ( - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type connectorMessagePayload struct { - CreatedAt time.Time `json:"createdAt"` - ConnectorID string `json:"connectorId"` -} - -func (m *Messages) NewEventResetConnector(connectorID models.ConnectorID) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeConnectorReset, - Payload: connectorMessagePayload{ - CreatedAt: time.Now().UTC(), - ConnectorID: connectorID.String(), - }, - } -} diff --git a/components/payments/internal/messages/messages.go b/components/payments/internal/messages/messages.go deleted file mode 100644 index 0da1b6c2ae..0000000000 --- a/components/payments/internal/messages/messages.go +++ /dev/null @@ -1,11 +0,0 @@ -package messages - -type Messages struct { - stackURL string -} - -func NewMessages(stackURL string) *Messages { - return &Messages{ - stackURL: stackURL, - } -} diff --git a/components/payments/internal/messages/payments.go b/components/payments/internal/messages/payments.go deleted file mode 100644 index 8d35854247..0000000000 --- a/components/payments/internal/messages/payments.go +++ /dev/null @@ -1,93 +0,0 @@ -package messages - -import ( - "encoding/json" - "math/big" - "time" - - "github.com/formancehq/go-libs/api" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type paymentMessagePayload struct { - ID string `json:"id"` - Reference string `json:"reference"` - CreatedAt time.Time `json:"createdAt"` - ConnectorID string `json:"connectorId"` - Provider string `json:"provider"` - Type models.PaymentType `json:"type"` - Status models.PaymentStatus `json:"status"` - Scheme models.PaymentScheme `json:"scheme"` - Asset models.Asset `json:"asset"` - SourceAccountID string `json:"sourceAccountId,omitempty"` - DestinationAccountID string `json:"destinationAccountId,omitempty"` - Links []api.Link `json:"links"` - RawData json.RawMessage `json:"rawData"` - - // TODO: Remove 'initialAmount' once frontend has switched to 'amount - InitialAmount *big.Int `json:"initialAmount"` - Amount *big.Int `json:"amount"` - Metadata map[string]string `json:"metadata"` -} - -func (m *Messages) NewEventSavedPayments(provider models.ConnectorProvider, payment *models.Payment) publish.EventMessage { - payload := paymentMessagePayload{ - ID: payment.ID.String(), - Reference: payment.Reference, - Type: payment.Type, - Status: payment.Status, - InitialAmount: payment.InitialAmount, - Amount: payment.Amount, - Scheme: payment.Scheme, - Asset: payment.Asset, - CreatedAt: payment.CreatedAt, - ConnectorID: payment.ConnectorID.String(), - Provider: provider.String(), - SourceAccountID: func() string { - if payment.SourceAccountID == nil { - return "" - } - return payment.SourceAccountID.String() - }(), - DestinationAccountID: func() string { - if payment.DestinationAccountID == nil { - return "" - } - return payment.DestinationAccountID.String() - }(), - RawData: payment.RawData, - Metadata: func() map[string]string { - ret := make(map[string]string) - for _, m := range payment.Metadata { - ret[m.Key] = m.Value - } - return ret - }(), - } - - if payment.SourceAccountID != nil { - payload.Links = append(payload.Links, api.Link{ - Name: "source_account", - URI: m.stackURL + "/api/payments/accounts/" + payment.SourceAccountID.String(), - }) - } - - if payment.DestinationAccountID != nil { - payload.Links = append(payload.Links, api.Link{ - Name: "destination_account", - URI: m.stackURL + "/api/payments/accounts/" + payment.DestinationAccountID.String(), - }) - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedPayments, - Payload: payload, - } -} diff --git a/components/payments/internal/messages/pools.go b/components/payments/internal/messages/pools.go deleted file mode 100644 index 9269c3f1fc..0000000000 --- a/components/payments/internal/messages/pools.go +++ /dev/null @@ -1,57 +0,0 @@ -package messages - -import ( - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" - "github.com/google/uuid" -) - -type poolMessagePayload struct { - ID string `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - AccountIDs []string `json:"accountIDs"` -} - -func (m *Messages) NewEventSavedPool(pool *models.Pool) publish.EventMessage { - payload := poolMessagePayload{ - ID: pool.ID.String(), - Name: pool.Name, - CreatedAt: pool.CreatedAt, - } - - payload.AccountIDs = make([]string, len(pool.PoolAccounts)) - for i, a := range pool.PoolAccounts { - payload.AccountIDs[i] = a.AccountID.String() - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedPool, - Payload: payload, - } -} - -type deletePoolMessagePayload struct { - CreatedAt time.Time `json:"createdAt"` - ID string `json:"id"` -} - -func (m *Messages) NewEventDeletePool(id uuid.UUID) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeDeletePool, - Payload: deletePoolMessagePayload{ - CreatedAt: time.Now().UTC(), - ID: id.String(), - }, - } -} diff --git a/components/payments/internal/messages/transfer_initiations.go b/components/payments/internal/messages/transfer_initiations.go deleted file mode 100644 index 20bcf82d21..0000000000 --- a/components/payments/internal/messages/transfer_initiations.go +++ /dev/null @@ -1,97 +0,0 @@ -package messages - -import ( - "math/big" - "time" - - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/payments/internal/models" - "github.com/formancehq/payments/pkg/events" -) - -type transferInitiationsPaymentsMessagePayload struct { - TransferInitiationID string `json:"transferInitiationId"` - PaymentID string `json:"paymentId"` - CreatedAt time.Time `json:"createdAt"` - Status string `json:"status"` - Error string `json:"error"` -} - -type transferInitiationsMessagePayload struct { - ID string `json:"id"` - CreatedAt time.Time `json:"createdAt"` - ScheduleAt time.Time `json:"scheduledAt"` - ConnectorID string `json:"connectorId"` - Provider string `json:"provider"` - Description string `json:"description"` - Type string `json:"type"` - SourceAccountID string `json:"sourceAccountId"` - DestinationAccountID string `json:"destinationAccountId"` - Amount *big.Int `json:"amount"` - Asset models.Asset `json:"asset"` - Attempts int `json:"attempts"` - Status string `json:"status"` - Error string `json:"error"` - RelatedPayments []*transferInitiationsPaymentsMessagePayload `json:"relatedPayments"` -} - -func (m *Messages) NewEventSavedTransferInitiations(tf *models.TransferInitiation) publish.EventMessage { - payload := transferInitiationsMessagePayload{ - ID: tf.ID.String(), - CreatedAt: tf.CreatedAt, - ScheduleAt: tf.ScheduledAt, - ConnectorID: tf.ConnectorID.String(), - Provider: tf.Provider.String(), - Description: tf.Description, - Type: tf.Type.String(), - SourceAccountID: tf.SourceAccountID.String(), - DestinationAccountID: tf.DestinationAccountID.String(), - Amount: tf.Amount, - Asset: tf.Asset, - Attempts: len(tf.RelatedAdjustments), - } - - if len(tf.RelatedAdjustments) > 0 { - // Take the status and error from the last adjustment - payload.Status = tf.RelatedAdjustments[0].Status.String() - payload.Error = tf.RelatedAdjustments[0].Error - } - - payload.RelatedPayments = make([]*transferInitiationsPaymentsMessagePayload, len(tf.RelatedPayments)) - for i, p := range tf.RelatedPayments { - payload.RelatedPayments[i] = &transferInitiationsPaymentsMessagePayload{ - TransferInitiationID: p.TransferInitiationID.String(), - PaymentID: p.PaymentID.String(), - CreatedAt: p.CreatedAt, - Status: p.Status.String(), - Error: p.Error, - } - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeSavedTransferInitiation, - Payload: payload, - } -} - -type deleteTransferInitiationMessagePayload struct { - CreatedAt time.Time `json:"createdAt"` - ID string `json:"id"` -} - -func (m *Messages) NewEventDeleteTransferInitiation(id models.TransferInitiationID) publish.EventMessage { - return publish.EventMessage{ - Date: time.Now().UTC(), - App: events.EventApp, - Version: events.EventVersion, - Type: events.EventTypeDeleteTransferInitiation, - Payload: deleteTransferInitiationMessagePayload{ - CreatedAt: time.Now().UTC(), - ID: id.String(), - }, - } -} diff --git a/components/payments/internal/models/account.go b/components/payments/internal/models/account.go deleted file mode 100644 index e58ee4052e..0000000000 --- a/components/payments/internal/models/account.go +++ /dev/null @@ -1,128 +0,0 @@ -package models - -import ( - "database/sql/driver" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/gibson042/canonicaljson-go" - "github.com/uptrace/bun" -) - -type Account struct { - bun.BaseModel `bun:"accounts.account"` - - ID AccountID `bun:",pk,type:character varying,nullzero"` - ConnectorID ConnectorID `bun:",type:character varying"` - CreatedAt time.Time `bun:",nullzero"` - Reference string - DefaultAsset Asset `bun:"default_currency"` // Is optional and default to '' - AccountName string // Is optional and default to '' - Type AccountType - Metadata map[string]string - - RawData json.RawMessage - - PoolAccounts []*PoolAccounts `bun:"rel:has-many,join:id=account_id"` -} - -type AccountType string - -const ( - AccountTypeUnknown AccountType = "UNKNOWN" - // Refers to an account that is internal to the psp, an account that we - // can actually fetch the balance. - AccountTypeInternal AccountType = "INTERNAL" - // Refers to an external accounts such as user's bank accounts. - AccountTypeExternal AccountType = "EXTERNAL" - // Refers to an external accounts created inside formance database. - // This is used only internally and will be transformed to EXTERNAL when - // returned to the user. - AccountTypeExternalFormance AccountType = "EXTERNAL_FORMANCE" -) - -func (at AccountType) String() string { - return string(at) -} - -func AccountTypeFromString(t string) (AccountType, error) { - switch t { - case AccountTypeInternal.String(): - return AccountTypeInternal, nil - case AccountTypeExternal.String(): - return AccountTypeExternal, nil - case AccountTypeExternalFormance.String(): - return AccountTypeExternalFormance, nil - } - - return AccountTypeUnknown, fmt.Errorf("unknown account type: %s", t) -} - -type AccountID struct { - Reference string - ConnectorID ConnectorID -} - -func (aid *AccountID) String() string { - if aid == nil || aid.Reference == "" { - return "" - } - - data, err := canonicaljson.Marshal(aid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data) -} - -func AccountIDFromString(value string) (*AccountID, error) { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - return nil, err - } - ret := AccountID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return nil, err - } - - return &ret, nil -} - -func MustAccountIDFromString(value string) AccountID { - id, err := AccountIDFromString(value) - if err != nil { - panic(err) - } - return *id -} - -func (aid AccountID) Value() (driver.Value, error) { - return aid.String(), nil -} - -func (aid *AccountID) Scan(value interface{}) error { - if value == nil { - return errors.New("account id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := AccountIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse account id %s: %v", v, err) - } - - *aid = *id - return nil - } - } - - return fmt.Errorf("failed to scan account id: %v", value) -} diff --git a/components/payments/internal/models/balance.go b/components/payments/internal/models/balance.go deleted file mode 100644 index 4cc10169b9..0000000000 --- a/components/payments/internal/models/balance.go +++ /dev/null @@ -1,19 +0,0 @@ -package models - -import ( - "math/big" - "time" - - "github.com/uptrace/bun" -) - -type Balance struct { - bun.BaseModel `bun:"accounts.balances"` - - AccountID AccountID `bun:"type:character varying,nullzero"` - Asset Asset `bun:"currency"` - Balance *big.Int `bun:"type:numeric"` - CreatedAt time.Time - LastUpdatedAt time.Time - ConnectorID ConnectorID `bun:"-"` -} diff --git a/components/payments/internal/models/bank_account.go b/components/payments/internal/models/bank_account.go deleted file mode 100644 index 53ada2648d..0000000000 --- a/components/payments/internal/models/bank_account.go +++ /dev/null @@ -1,67 +0,0 @@ -package models - -import ( - "errors" - "strings" - "time" - - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type BankAccount struct { - bun.BaseModel `bun:"accounts.bank_account"` - - ID uuid.UUID `bun:",pk,nullzero"` - CreatedAt time.Time `bun:",nullzero"` - Name string - AccountNumber string `bun:"decrypted_account_number,scanonly"` - IBAN string `bun:"decrypted_iban,scanonly"` - SwiftBicCode string `bun:"decrypted_swift_bic_code,scanonly"` - Country string `bun:"country"` - Metadata map[string]string - - RelatedAccounts []*BankAccountRelatedAccount `bun:"rel:has-many,join:id=bank_account_id"` -} - -func (a *BankAccount) Offuscate() error { - if a.IBAN != "" { - length := len(a.IBAN) - if length < 8 { - return errors.New("IBAN is not valid") - } - - a.IBAN = a.IBAN[:4] + strings.Repeat("*", length-8) + a.IBAN[length-4:] - } - - if a.AccountNumber != "" { - length := len(a.AccountNumber) - if length < 5 { - return errors.New("Account number is not valid") - } - - a.AccountNumber = a.AccountNumber[:2] + strings.Repeat("*", length-5) + a.AccountNumber[length-3:] - } - - return nil -} - -type BankAccountRelatedAccount struct { - bun.BaseModel `bun:"accounts.bank_account_related_accounts"` - - ID uuid.UUID `bun:",pk,nullzero"` - CreatedAt time.Time `bun:",nullzero"` - BankAccountID uuid.UUID `bun:",nullzero"` - ConnectorID ConnectorID `bun:",nullzero"` - AccountID AccountID `bun:",nullzero"` -} - -const ( - bankAccountOwnerNamespace = formanceMetadataSpecNamespace + "owner/" - - BankAccountOwnerAddressLine1MetadataKey = bankAccountOwnerNamespace + "addressLine1" - BankAccountOwnerAddressLine2MetadataKey = bankAccountOwnerNamespace + "addressLine2" - BankAccountOwnerCityMetadataKey = bankAccountOwnerNamespace + "city" - BankAccountOwnerRegionMetadataKey = bankAccountOwnerNamespace + "region" - BankAccountOwnerPostalCodeMetadataKey = bankAccountOwnerNamespace + "postalCode" -) diff --git a/components/payments/internal/models/connector.go b/components/payments/internal/models/connector.go deleted file mode 100644 index ad5ea0a61b..0000000000 --- a/components/payments/internal/models/connector.go +++ /dev/null @@ -1,203 +0,0 @@ -package models - -import ( - "database/sql/driver" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "strings" - "time" - - "github.com/gibson042/canonicaljson-go" - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type Connector struct { - bun.BaseModel `bun:"connectors.connector"` - - ID ConnectorID `bun:",pk,nullzero"` - Name string - CreatedAt time.Time `bun:",nullzero"` - Provider ConnectorProvider - - // EncryptedConfig is a PGP-encrypted JSON string. - EncryptedConfig string `bun:"config"` - - // Config is a decrypted config. It is not stored in the database. - Config json.RawMessage `bun:"decrypted_config,scanonly"` - - Tasks []*Task `bun:"rel:has-many,join:id=connector_id"` -} - -type ConnectorID struct { - Reference uuid.UUID - Provider ConnectorProvider -} - -func (cid *ConnectorID) String() string { - if cid == nil || cid.Reference == uuid.Nil { - return "" - } - - data, err := canonicaljson.Marshal(cid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data) -} - -func ConnectorIDFromString(value string) (ConnectorID, error) { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - return ConnectorID{}, err - } - ret := ConnectorID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return ConnectorID{}, err - } - - return ret, nil -} - -func MustConnectorIDFromString(value string) ConnectorID { - id, err := ConnectorIDFromString(value) - if err != nil { - panic(err) - } - return id -} - -func (cid ConnectorID) Value() (driver.Value, error) { - return cid.String(), nil -} - -func (cid *ConnectorID) Scan(value interface{}) error { - if value == nil { - return errors.New("connector id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := ConnectorIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse connector id %s: %v", v, err) - } - - *cid = id - return nil - } - } - - return fmt.Errorf("failed to scan connector id: %v", value) -} - -func (c Connector) String() string { - c.EncryptedConfig = "****" - c.Config = nil - - var t any = c - - return fmt.Sprintf("%+v", t) -} - -type ConnectorProvider string - -const ( - ConnectorProviderBankingCircle ConnectorProvider = "BANKING-CIRCLE" - ConnectorProviderCurrencyCloud ConnectorProvider = "CURRENCY-CLOUD" - ConnectorProviderDummyPay ConnectorProvider = "DUMMY-PAY" - ConnectorProviderModulr ConnectorProvider = "MODULR" - ConnectorProviderStripe ConnectorProvider = "STRIPE" - ConnectorProviderWise ConnectorProvider = "WISE" - ConnectorProviderMangopay ConnectorProvider = "MANGOPAY" - ConnectorProviderMoneycorp ConnectorProvider = "MONEYCORP" - ConnectorProviderAtlar ConnectorProvider = "ATLAR" - ConnectorProviderAdyen ConnectorProvider = "ADYEN" - ConnectorProviderGeneric ConnectorProvider = "GENERIC" -) - -func (p ConnectorProvider) String() string { - return string(p) -} - -func (p ConnectorProvider) StringLower() string { - return strings.ToLower(string(p)) -} - -func ConnectorProviderFromString(s string) (ConnectorProvider, error) { - switch s { - case "BANKING-CIRCLE": - return ConnectorProviderBankingCircle, nil - case "CURRENCY-CLOUD": - return ConnectorProviderCurrencyCloud, nil - case "DUMMY-PAY": - return ConnectorProviderDummyPay, nil - case "MODULR": - return ConnectorProviderModulr, nil - case "STRIPE": - return ConnectorProviderStripe, nil - case "WISE": - return ConnectorProviderWise, nil - case "MANGOPAY": - return ConnectorProviderMangopay, nil - case "MONEYCORP": - return ConnectorProviderMoneycorp, nil - case "ATLAR": - return ConnectorProviderAtlar, nil - case "ADYEN": - return ConnectorProviderAdyen, nil - case "GENERIC": - return ConnectorProviderGeneric, nil - default: - return "", errors.New("unknown connector provider") - } -} - -func MustConnectorProviderFromString(s string) ConnectorProvider { - p, err := ConnectorProviderFromString(s) - if err != nil { - panic(err) - } - return p -} - -func (c Connector) ParseConfig(to interface{}) error { - if c.Config == nil { - return nil - } - - err := json.Unmarshal(c.Config, to) - if err != nil { - return fmt.Errorf("failed to parse config (%s): %w", string(c.Config), err) - } - - return nil -} - -type ConnectorConfigObject interface { - ConnectorName() string - Validate() error - Marshal() ([]byte, error) -} - -type EmptyConnectorConfig struct { - Name string -} - -func (cfg EmptyConnectorConfig) ConnectorName() string { - return cfg.Name -} - -func (cfg EmptyConnectorConfig) Validate() error { - return nil -} - -func (cfg EmptyConnectorConfig) Marshal() ([]byte, error) { - return nil, nil -} diff --git a/components/payments/internal/models/metadata.go b/components/payments/internal/models/metadata.go deleted file mode 100644 index 1be8e6ed5a..0000000000 --- a/components/payments/internal/models/metadata.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -const ( - formanceMetadataSpecNamespace = "com.formance.spec/" -) - -func ExtractNamespacedMetadata(metadata map[string]string, key string) string { - value, ok := metadata[key] - if !ok { - return "" - } - return value -} diff --git a/components/payments/internal/models/payment.go b/components/payments/internal/models/payment.go deleted file mode 100644 index 3358adf42e..0000000000 --- a/components/payments/internal/models/payment.go +++ /dev/null @@ -1,321 +0,0 @@ -package models - -import ( - "database/sql/driver" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "math/big" - "strconv" - "strings" - "time" - - "github.com/gibson042/canonicaljson-go" - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type PaymentReference struct { - Reference string - Type PaymentType -} - -type PaymentID struct { - PaymentReference - ConnectorID ConnectorID -} - -func (pid PaymentID) String() string { - data, err := canonicaljson.Marshal(pid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data) -} - -func PaymentIDFromString(value string) (*PaymentID, error) { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - return nil, err - } - ret := PaymentID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return nil, err - } - - return &ret, nil -} - -func MustPaymentIDFromString(value string) *PaymentID { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - panic(err) - } - ret := PaymentID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - panic(err) - } - - return &ret -} - -func (pid PaymentID) Value() (driver.Value, error) { - return pid.String(), nil -} - -func (pid *PaymentID) Scan(value interface{}) error { - if value == nil { - return errors.New("payment id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := PaymentIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse paymentid %s: %v", v, err) - } - - *pid = *id - return nil - } - } - - return fmt.Errorf("failed to scan paymentid: %v", value) -} - -type Payment struct { - bun.BaseModel `bun:"payments.payment"` - - ID PaymentID `bun:",pk,type:character varying,nullzero"` - ConnectorID ConnectorID `bun:",nullzero"` - CreatedAt time.Time `bun:",nullzero"` - Reference string - Amount *big.Int `bun:"type:numeric"` - InitialAmount *big.Int `bun:"type:numeric"` - Type PaymentType `bun:",type:payment_type"` - Status PaymentStatus `bun:",type:payment_status"` - Scheme PaymentScheme - Asset Asset - - RawData json.RawMessage - - SourceAccountID *AccountID `bun:",type:character varying,nullzero"` - DestinationAccountID *AccountID `bun:",type:character varying,nullzero"` - - // Read only fields - Metadata []*PaymentMetadata `bun:"rel:has-many,join:id=payment_id"` - Adjustments []*PaymentAdjustment `bun:"rel:has-many,join:id=payment_id"` - Connector *Connector `bun:"rel:has-one,join:connector_id=id"` -} - -type ( - PaymentType string - PaymentStatus string - PaymentScheme string - Asset string -) - -const ( - PaymentTypePayIn PaymentType = "PAY-IN" - PaymentTypePayOut PaymentType = "PAYOUT" - PaymentTypeTransfer PaymentType = "TRANSFER" - PaymentTypeOther PaymentType = "OTHER" -) - -const ( - PaymentStatusPending PaymentStatus = "PENDING" - PaymentStatusSucceeded PaymentStatus = "SUCCEEDED" - PaymentStatusCancelled PaymentStatus = "CANCELLED" - PaymentStatusFailed PaymentStatus = "FAILED" - PaymentStatusExpired PaymentStatus = "EXPIRED" - PaymentStatusRefunded PaymentStatus = "REFUNDED" - PaymentStatusRefundedFailure PaymentStatus = "REFUNDED_FAILURE" - PaymentStatusDispute PaymentStatus = "DISPUTE" - PaymentStatusDisputeWon PaymentStatus = "DISPUTE_WON" - PaymentStatusDisputeLost PaymentStatus = "DISPUTE_LOST" - PaymentStatusOther PaymentStatus = "OTHER" -) - -const ( - PaymentSchemeUnknown PaymentScheme = "unknown" - PaymentSchemeOther PaymentScheme = "other" - - PaymentSchemeCardVisa PaymentScheme = "visa" - PaymentSchemeCardMasterCard PaymentScheme = "mastercard" - PaymentSchemeCardAmex PaymentScheme = "amex" - PaymentSchemeCardDiners PaymentScheme = "diners" - PaymentSchemeCardDiscover PaymentScheme = "discover" - PaymentSchemeCardJCB PaymentScheme = "jcb" - PaymentSchemeCardUnionPay PaymentScheme = "unionpay" - PaymentSchemeCardAlipay PaymentScheme = "alipay" - PaymentSchemeCardCUP PaymentScheme = "cup" - - PaymentSchemeSepaDebit PaymentScheme = "sepa debit" - PaymentSchemeSepaCredit PaymentScheme = "sepa credit" - PaymentSchemeSepa PaymentScheme = "sepa" - - PaymentSchemeApplePay PaymentScheme = "apple pay" - PaymentSchemeGooglePay PaymentScheme = "google pay" - - PaymentSchemeDOKU PaymentScheme = "doku" - PaymentSchemeDragonPay PaymentScheme = "dragonpay" - PaymentSchemeMaestro PaymentScheme = "maestro" - PaymentSchemeMolPay PaymentScheme = "molpay" - - PaymentSchemeA2A PaymentScheme = "a2a" - PaymentSchemeACHDebit PaymentScheme = "ach debit" - PaymentSchemeACH PaymentScheme = "ach" - PaymentSchemeRTP PaymentScheme = "rtp" -) - -func (t PaymentType) String() string { - return string(t) -} - -func PaymentTypeFromString(value string) (PaymentType, error) { - switch value { - case "PAY-IN": - return PaymentTypePayIn, nil - case "PAYOUT": - return PaymentTypePayOut, nil - case "TRANSFER": - return PaymentTypeTransfer, nil - case "OTHER": - return PaymentTypeOther, nil - default: - return "", errors.New("invalid payment type") - } -} - -func (t PaymentStatus) String() string { - return string(t) -} - -func PaymentStatusFromString(value string) (PaymentStatus, error) { - switch value { - case "PENDING": - return PaymentStatusPending, nil - case "SUCCEEDED": - return PaymentStatusSucceeded, nil - case "CANCELLED": - return PaymentStatusCancelled, nil - case "FAILED": - return PaymentStatusFailed, nil - case "EXPIRED": - return PaymentStatusExpired, nil - case "REFUNDED": - return PaymentStatusRefunded, nil - case "REFUNDED_FAILURE": - return PaymentStatusRefundedFailure, nil - case "DISPUTE": - return PaymentStatusDispute, nil - case "DISPUTE_WON": - return PaymentStatusDisputeWon, nil - case "DISPUTE_LOST": - return PaymentStatusDisputeLost, nil - case "OTHER": - return PaymentStatusOther, nil - default: - return "", errors.New("invalid payment status") - } -} - -func (t PaymentScheme) String() string { - return string(t) -} - -func PaymentSchemeFromString(value string) (PaymentScheme, error) { - switch strings.ToLower(value) { - case "unknown": - return PaymentSchemeUnknown, nil - case "other": - return PaymentSchemeOther, nil - case "visa": - return PaymentSchemeCardVisa, nil - case "mastercard": - return PaymentSchemeCardMasterCard, nil - case "amex": - return PaymentSchemeCardAmex, nil - case "diners": - return PaymentSchemeCardDiners, nil - case "discover": - return PaymentSchemeCardDiscover, nil - case "jcb": - return PaymentSchemeCardJCB, nil - case "unionpay": - return PaymentSchemeCardUnionPay, nil - case "sepa debit": - return PaymentSchemeSepaDebit, nil - case "sepa credit": - return PaymentSchemeSepaCredit, nil - case "sepa": - return PaymentSchemeSepa, nil - case "apple pay": - return PaymentSchemeApplePay, nil - case "google pay": - return PaymentSchemeGooglePay, nil - case "a2a": - return PaymentSchemeA2A, nil - case "ach debit": - return PaymentSchemeACHDebit, nil - case "ach": - return PaymentSchemeACH, nil - case "rtp": - return PaymentSchemeRTP, nil - default: - return "", errors.New("invalid payment scheme") - } -} - -func (t Asset) String() string { - return string(t) -} - -func GetCurrencyAndPrecisionFromAsset(asset Asset) (string, int64, error) { - parts := strings.Split(asset.String(), "/") - if len(parts) != 2 { - return "", 0, errors.New("invalid asset") - } - - precision, err := strconv.ParseInt(parts[1], 10, 64) - if err != nil { - return "", 0, errors.New("invalid asset precision") - } - - return parts[0], precision, nil -} - -type PaymentAdjustment struct { - bun.BaseModel `bun:"payments.adjustment"` - - ID uuid.UUID `bun:",pk,nullzero"` - PaymentID PaymentID `bun:",pk,nullzero"` - CreatedAt time.Time `bun:",nullzero"` - Reference string - Amount *big.Int - Status PaymentStatus - - RawData json.RawMessage -} - -type PaymentMetadata struct { - bun.BaseModel `bun:"payments.metadata"` - - PaymentID PaymentID `bun:",pk,nullzero"` - CreatedAt time.Time `bun:",nullzero"` - Key string `bun:",pk,nullzero"` - Value string - - Changelog []MetadataChangelog `bun:",nullzero"` -} - -type MetadataChangelog struct { - CreatedAt time.Time `json:"createdAt"` - Value string `json:"value"` -} diff --git a/components/payments/internal/models/pools.go b/components/payments/internal/models/pools.go deleted file mode 100644 index ab865b1359..0000000000 --- a/components/payments/internal/models/pools.go +++ /dev/null @@ -1,25 +0,0 @@ -package models - -import ( - "time" - - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type PoolAccounts struct { - bun.BaseModel `bun:"accounts.pool_accounts"` - - PoolID uuid.UUID `bun:",pk,notnull"` - AccountID AccountID `bun:",pk,notnull"` -} - -type Pool struct { - bun.BaseModel `bun:"accounts.pools"` - - ID uuid.UUID `bun:",pk,nullzero"` - Name string - CreatedAt time.Time - - PoolAccounts []*PoolAccounts `bun:"rel:has-many,join:id=pool_id"` -} diff --git a/components/payments/internal/models/task.go b/components/payments/internal/models/task.go deleted file mode 100644 index 566c16305b..0000000000 --- a/components/payments/internal/models/task.go +++ /dev/null @@ -1,124 +0,0 @@ -package models - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "time" - - "github.com/uptrace/bun" - - "github.com/google/uuid" -) - -type TaskID uuid.UUID - -func (id TaskID) String() string { - return uuid.UUID(id).String() -} - -type ScheduleOption int - -const ( - OPTIONS_RUN_NOW ScheduleOption = iota - OPTIONS_RUN_IN_DURATION - OPTIONS_RUN_PERIODICALLY - OPTIONS_RUN_NOW_SYNC - OPTIONS_RUN_SCHEDULED_AT -) - -type RestartOption int - -const ( - OPTIONS_RESTART_NEVER RestartOption = iota - OPTIONS_RESTART_ALWAYS - OPTIONS_RESTART_IF_NOT_ACTIVE - OPTIONS_STOP_AND_RESTART -) - -type Task struct { - bun.BaseModel `bun:"tasks.task"` - - ID uuid.UUID `bun:",pk,nullzero"` - ConnectorID ConnectorID - CreatedAt time.Time `bun:",nullzero"` - UpdatedAt time.Time `bun:",nullzero"` - Name string - Descriptor json.RawMessage - SchedulerOptions TaskSchedulerOptions - Status TaskStatus - Error string - State json.RawMessage - - Connector *Connector `bun:"rel:belongs-to,join:connector_id=id"` -} - -func (t Task) GetDescriptor() TaskDescriptor { - return TaskDescriptor(t.Descriptor) -} - -type TaskSchedulerOptions struct { - ScheduleOption ScheduleOption - Duration time.Duration - ScheduleAt time.Time - - // TODO(polo): Deprecated, will be removed in the next release, use - // RestartOption instead. - // We have to keep it for now for db compatibility. - Restart bool - RestartOption RestartOption -} - -type TaskDescriptor json.RawMessage - -func (td TaskDescriptor) ToMessage() json.RawMessage { - return json.RawMessage(td) -} - -func (td TaskDescriptor) EncodeToString() (string, error) { - data, err := json.Marshal(td) - if err != nil { - return "", fmt.Errorf("failed to encode task descriptor: %w", err) - } - - return base64.StdEncoding.EncodeToString(data), nil -} - -func EncodeTaskDescriptor(descriptor any) (TaskDescriptor, error) { - res, err := json.Marshal(descriptor) - if err != nil { - return nil, fmt.Errorf("failed to encode task descriptor: %w", err) - } - - return res, nil -} - -func DecodeTaskDescriptor[descriptor any](data TaskDescriptor) (descriptor, error) { - var res descriptor - - err := json.Unmarshal(data, &res) - if err != nil { - return res, fmt.Errorf("failed to decode task descriptor: %w", err) - } - - return res, nil -} - -type TaskStatus string - -const ( - TaskStatusStopped TaskStatus = "STOPPED" - TaskStatusPending TaskStatus = "PENDING" - TaskStatusActive TaskStatus = "ACTIVE" - TaskStatusTerminated TaskStatus = "TERMINATED" - TaskStatusFailed TaskStatus = "FAILED" -) - -func (t Task) ParseDescriptor(to interface{}) error { - err := json.Unmarshal(t.Descriptor, to) - if err != nil { - return fmt.Errorf("failed to parse descriptor: %w", err) - } - - return nil -} diff --git a/components/payments/internal/models/transfer.go b/components/payments/internal/models/transfer.go deleted file mode 100644 index cc79f475eb..0000000000 --- a/components/payments/internal/models/transfer.go +++ /dev/null @@ -1,114 +0,0 @@ -package models - -import "errors" - -type TransferInitiationStatus int - -const ( - TransferInitiationStatusWaitingForValidation TransferInitiationStatus = iota - TransferInitiationStatusProcessing - TransferInitiationStatusProcessed - TransferInitiationStatusFailed - TransferInitiationStatusRejected - TransferInitiationStatusValidated - TransferInitiationStatusAskRetried - TransferInitiationStatusAskReversed - TransferInitiationStatusReverseProcessing - TransferInitiationStatusReverseFailed - TransferInitiationStatusPartiallyReversed - TransferInitiationStatusReversed -) - -func (s TransferInitiationStatus) String() string { - return [...]string{ - "WAITING_FOR_VALIDATION", - "PROCESSING", - "PROCESSED", - "FAILED", - "REJECTED", - "VALIDATED", - "ASK_RETRIED", - "ASK_REVERSED", - "REVERSE_PROCESSING", - "REVERSE_FAILED", - "PARTIALLY_REVERSED", - "REVERSED", - }[s] -} - -func TransferInitiationStatusFromString(s string) (TransferInitiationStatus, error) { - switch s { - case "WAITING_FOR_VALIDATION": - return TransferInitiationStatusWaitingForValidation, nil - case "PROCESSING": - return TransferInitiationStatusProcessing, nil - case "PROCESSED": - return TransferInitiationStatusProcessed, nil - case "FAILED": - return TransferInitiationStatusFailed, nil - case "REJECTED": - return TransferInitiationStatusRejected, nil - case "VALIDATED": - return TransferInitiationStatusValidated, nil - case "ASK_RETRIED": - return TransferInitiationStatusAskRetried, nil - case "ASK_REVERSED": - return TransferInitiationStatusAskReversed, nil - case "REVERSE_PROCESSING": - return TransferInitiationStatusReverseProcessing, nil - case "REVERSE_FAILED": - return TransferInitiationStatusReverseFailed, nil - case "PARTIALLY_REVERSED": - return TransferInitiationStatusPartiallyReversed, nil - case "REVERSED": - return TransferInitiationStatusReversed, nil - default: - return TransferInitiationStatusWaitingForValidation, errors.New("invalid status") - } -} - -type TransferReversalStatus int - -const ( - TransferReversalStatusProcessing TransferReversalStatus = iota - TransferReversalStatusProcessed - TransferReversalStatusFailed -) - -func (s TransferReversalStatus) String() string { - return [...]string{ - "CREATED", - "PROCESSING", - "PROCESSED", - "FAILED", - }[s] -} - -func TransferReversalStatusFromString(s string) (TransferReversalStatus, error) { - switch s { - case "PROCESSING": - return TransferReversalStatusProcessing, nil - case "PROCESSED": - return TransferReversalStatusProcessed, nil - case "FAILED": - return TransferReversalStatusFailed, nil - default: - return TransferReversalStatusProcessing, errors.New("invalid status") - } -} - -func (s TransferReversalStatus) ToTransferInitiationStatus(isFullyReversed bool) TransferInitiationStatus { - switch s { - case TransferReversalStatusProcessing: - return TransferInitiationStatusReverseProcessing - case TransferReversalStatusProcessed: - if isFullyReversed { - return TransferInitiationStatusReversed - } - return TransferInitiationStatusPartiallyReversed - case TransferReversalStatusFailed: - return TransferInitiationStatusReverseFailed - default: - return TransferInitiationStatusProcessed - } -} diff --git a/components/payments/internal/models/transfer_initiation.go b/components/payments/internal/models/transfer_initiation.go deleted file mode 100644 index 71860aba04..0000000000 --- a/components/payments/internal/models/transfer_initiation.go +++ /dev/null @@ -1,180 +0,0 @@ -package models - -import ( - "database/sql/driver" - "encoding/base64" - "errors" - "fmt" - "math/big" - "sort" - "time" - - "github.com/gibson042/canonicaljson-go" - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type TransferInitiationID struct { - Reference string - ConnectorID ConnectorID -} - -func (tid TransferInitiationID) String() string { - data, err := canonicaljson.Marshal(tid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data) -} - -func TransferInitiationIDFromString(value string) (TransferInitiationID, error) { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - return TransferInitiationID{}, err - } - ret := TransferInitiationID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return TransferInitiationID{}, err - } - - return ret, nil -} - -func MustTransferInitiationIDFromString(value string) TransferInitiationID { - id, err := TransferInitiationIDFromString(value) - if err != nil { - panic(err) - } - return id -} - -func (tid TransferInitiationID) Value() (driver.Value, error) { - return tid.String(), nil -} - -func (tid *TransferInitiationID) Scan(value interface{}) error { - if value == nil { - return errors.New("payment id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := TransferInitiationIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse paymentid %s: %v", v, err) - } - - *tid = id - return nil - } - } - - return fmt.Errorf("failed to scan paymentid: %v", value) -} - -type TransferInitiationType int - -const ( - TransferInitiationTypeTransfer TransferInitiationType = iota - TransferInitiationTypePayout -) - -func (t TransferInitiationType) String() string { - return [...]string{ - "TRANSFER", - "PAYOUT", - }[t] -} - -func TransferInitiationTypeFromString(s string) (TransferInitiationType, error) { - switch s { - case "TRANSFER": - return TransferInitiationTypeTransfer, nil - case "PAYOUT": - return TransferInitiationTypePayout, nil - default: - return TransferInitiationTypeTransfer, errors.New("invalid type") - } -} - -func MustTransferInitiationTypeFromString(s string) TransferInitiationType { - t, err := TransferInitiationTypeFromString(s) - if err != nil { - panic(err) - } - return t -} - -type TransferInitiation struct { - bun.BaseModel `bun:"transfers.transfer_initiation"` - - // Filled when created in DB - ID TransferInitiationID `bun:",pk,nullzero"` - - CreatedAt time.Time `bun:",nullzero"` - ScheduledAt time.Time `bun:",nullzero"` - Description string - - Type TransferInitiationType - - SourceAccountID *AccountID - DestinationAccountID AccountID - Provider ConnectorProvider - ConnectorID ConnectorID - - Amount *big.Int `bun:"type:numeric"` - InitialAmount *big.Int `bun:"type:numeric"` - Asset Asset - - Metadata map[string]string - - SourceAccount *Account `bun:"-"` - DestinationAccount *Account `bun:"-"` - - RelatedAdjustments []*TransferInitiationAdjustment `bun:"rel:has-many,join:id=transfer_initiation_id"` - RelatedPayments []*TransferInitiationPayment `bun:"-"` -} - -func (t *TransferInitiation) SortRelatedAdjustments() { - // Sort adjustments by created_at - sort.Slice(t.RelatedAdjustments, func(i, j int) bool { - return t.RelatedAdjustments[i].CreatedAt.After(t.RelatedAdjustments[j].CreatedAt) - }) -} - -func (t *TransferInitiation) CountRetries() int { - res := 0 - for _, adjustment := range t.RelatedAdjustments { - if adjustment.Status == TransferInitiationStatusAskRetried { - res++ - } - } - - return res -} - -type TransferInitiationPayment struct { - bun.BaseModel `bun:"transfers.transfer_initiation_payments"` - - TransferInitiationID TransferInitiationID `bun:",pk"` - PaymentID PaymentID `bun:",pk"` - - CreatedAt time.Time `bun:",nullzero"` - Status TransferInitiationStatus - Error string -} - -type TransferInitiationAdjustment struct { - bun.BaseModel `bun:"transfers.transfer_initiation_adjustments"` - - ID uuid.UUID `bun:",pk"` - TransferInitiationID TransferInitiationID - CreatedAt time.Time `bun:",nullzero"` - Status TransferInitiationStatus - Error string - Metadata map[string]string -} diff --git a/components/payments/internal/models/transfer_reversal.go b/components/payments/internal/models/transfer_reversal.go deleted file mode 100644 index 357c8da41a..0000000000 --- a/components/payments/internal/models/transfer_reversal.go +++ /dev/null @@ -1,96 +0,0 @@ -package models - -import ( - "database/sql/driver" - "encoding/base64" - "errors" - "fmt" - "math/big" - "time" - - "github.com/gibson042/canonicaljson-go" - "github.com/uptrace/bun" -) - -type TransferReversalID struct { - Reference string - ConnectorID ConnectorID -} - -func (tid TransferReversalID) String() string { - data, err := canonicaljson.Marshal(tid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data) -} - -func TransferReversalIDFromString(value string) (TransferReversalID, error) { - data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(value) - if err != nil { - return TransferReversalID{}, err - } - ret := TransferReversalID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return TransferReversalID{}, err - } - - return ret, nil -} - -func MustTransferReversalIDFromString(value string) TransferReversalID { - id, err := TransferReversalIDFromString(value) - if err != nil { - panic(err) - } - return id -} - -func (tid TransferReversalID) Value() (driver.Value, error) { - return tid.String(), nil -} - -func (tid *TransferReversalID) Scan(value interface{}) error { - if value == nil { - return errors.New("payment id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := TransferReversalIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse paymentid %s: %v", v, err) - } - - *tid = id - return nil - } - } - - return fmt.Errorf("failed to scan paymentid: %v", value) -} - -type TransferReversal struct { - bun.BaseModel `bun:"transfers.transfer_reversal"` - - ID TransferReversalID `bun:",pk"` - TransferInitiationID TransferInitiationID - - CreatedAt time.Time - UpdatedAt time.Time - Description string - - ConnectorID ConnectorID - - Amount *big.Int - Asset Asset - - Status TransferReversalStatus - Error string - - Metadata map[string]string -} diff --git a/components/payments/internal/models/webhook.go b/components/payments/internal/models/webhook.go deleted file mode 100644 index f1a55a02a9..0000000000 --- a/components/payments/internal/models/webhook.go +++ /dev/null @@ -1,14 +0,0 @@ -package models - -import ( - "github.com/google/uuid" - "github.com/uptrace/bun" -) - -type Webhook struct { - bun.BaseModel `bun:"connectors.webhook"` - - ID uuid.UUID - ConnectorID ConnectorID - RequestBody []byte -} diff --git a/components/payments/internal/otel/tracer.go b/components/payments/internal/otel/tracer.go deleted file mode 100644 index 84ab67da21..0000000000 --- a/components/payments/internal/otel/tracer.go +++ /dev/null @@ -1,27 +0,0 @@ -package otel - -import ( - "sync" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -var ( - once sync.Once - tracer trace.Tracer -) - -func Tracer() trace.Tracer { - once.Do(func() { - tracer = otel.Tracer("com.formance.payments") - }) - - return tracer -} - -func RecordError(span trace.Span, err error) { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) -} diff --git a/components/payments/internal/storage/migrations.go b/components/payments/internal/storage/migrations.go deleted file mode 100644 index 88686eb838..0000000000 --- a/components/payments/internal/storage/migrations.go +++ /dev/null @@ -1,22 +0,0 @@ -package storage - -import ( - "context" - - "github.com/formancehq/go-libs/migrations" - "github.com/uptrace/bun" -) - -// EncryptionKey is set from the migration utility to specify default encryption key to migrate to. -// This can remain empty. Then the config will be removed. -// -//nolint:gochecknoglobals // This is a global variable by design. -var EncryptionKey string - -func Migrate(ctx context.Context, db *bun.DB) error { - migrator := migrations.NewMigrator() - registerMigrationsV0(migrator) - registerMigrationsV1(ctx, migrator) - - return migrator.Up(ctx, db) -} diff --git a/components/payments/internal/storage/migrations_v0.x.go b/components/payments/internal/storage/migrations_v0.x.go deleted file mode 100644 index 29a890ba8f..0000000000 --- a/components/payments/internal/storage/migrations_v0.x.go +++ /dev/null @@ -1,625 +0,0 @@ -package storage - -import ( - "fmt" - - "github.com/formancehq/go-libs/migrations" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -func registerMigrationsV0(migrator *migrations.Migrator) { - migrator.RegisterMigrations( - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE SCHEMA IF NOT EXISTS connectors; - CREATE SCHEMA IF NOT EXISTS tasks; - CREATE SCHEMA IF NOT EXISTS accounts; - CREATE SCHEMA IF NOT EXISTS payments; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TYPE "public".connector_provider AS ENUM ('BANKING-CIRCLE', 'CURRENCY-CLOUD', 'DUMMY-PAY', 'MODULR', 'STRIPE', 'WISE');; - CREATE TABLE connectors.connector ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - provider connector_provider NOT NULL UNIQUE, - enabled boolean NOT NULL DEFAULT false, - config json NULL, - CONSTRAINT connector_pk PRIMARY KEY (id) - ); - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TYPE "public".task_status AS ENUM ('STOPPED', 'PENDING', 'ACTIVE', 'TERMINATED', 'FAILED');; - CREATE TABLE tasks.task ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - connector_id uuid NOT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - updated_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=updated_at), - name text NOT NULL, - descriptor json NULL, - status task_status NOT NULL, - error text NULL, - state json NULL, - CONSTRAINT task_pk PRIMARY KEY (id) - ); - ALTER TABLE tasks.task ADD CONSTRAINT task_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TYPE "public".account_type AS ENUM('SOURCE', 'TARGET', 'UNKNOWN');; - - CREATE TABLE accounts.account ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - reference text NOT NULL UNIQUE, - provider text NOT NULL, - type account_type NOT NULL, - CONSTRAINT account_pk PRIMARY KEY (id) - ); - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TYPE "public".payment_type AS ENUM ('PAY-IN', 'PAYOUT', 'TRANSFER', 'OTHER'); - CREATE TYPE "public".payment_status AS ENUM ('SUCCEEDED', 'CANCELLED', 'FAILED', 'PENDING', 'OTHER');; - - CREATE TABLE payments.adjustment ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - payment_id uuid NOT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - amount bigint NOT NULL DEFAULT 0, - reference text NOT NULL UNIQUE, - status payment_status NOT NULL, - absolute boolean NOT NULL DEFAULT FALSE, - raw_data json NULL, - CONSTRAINT adjustment_pk PRIMARY KEY (id) - ); - - CREATE TABLE payments.metadata ( - payment_id uuid NOT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - key text NOT NULL, - value text NOT NULL, - changelog jsonb NOT NULL, - CONSTRAINT metadata_pk PRIMARY KEY (payment_id,key) - ); - - CREATE TABLE payments.payment ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - connector_id uuid NOT NULL, - account_id uuid DEFAULT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - reference text NOT NULL UNIQUE, - type payment_type NOT NULL, - status payment_status NOT NULL, - amount bigint NOT NULL DEFAULT 0, - raw_data json NULL, - scheme text NOT NULL, - asset text NOT NULL, - CONSTRAINT payment_pk PRIMARY KEY (id) - ); - - ALTER TABLE payments.adjustment ADD CONSTRAINT adjustment_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.metadata ADD CONSTRAINT metadata_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment ADD CONSTRAINT payment_account - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment ADD CONSTRAINT payment_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - //nolint:varnamelen - migrations.Migration{ - Up: func(tx bun.Tx) error { - var exists bool - - err := tx.QueryRow("SELECT EXISTS(SELECT 1 FROM connectors.connector)").Scan(&exists) - if err != nil { - return fmt.Errorf("failed to check if connectors table exists: %w", err) - } - - if exists && EncryptionKey == "" { - return errors.New("encryption key is not set") - } - - _, err = tx.Exec(` - CREATE EXTENSION IF NOT EXISTS pgcrypto; - ALTER TABLE connectors.connector RENAME COLUMN config TO config_unencrypted; - ALTER TABLE connectors.connector ADD COLUMN config bytea NULL; - `) - if err != nil { - return fmt.Errorf("failed to create config column: %w", err) - } - - _, err = tx.Exec(` - UPDATE connectors.connector SET config = pgp_sym_encrypt(config_unencrypted::TEXT, ?, 'compress-algo=1, cipher-algo=aes256'); - `, EncryptionKey) - if err != nil { - return fmt.Errorf("failed to encrypt config: %w", err) - } - - _, err = tx.Exec(` - ALTER TABLE connectors.connector DROP COLUMN config_unencrypted; - `) - if err != nil { - return fmt.Errorf("failed to drop config_unencrypted column: %w", err) - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TYPE "public".transfer_status AS ENUM ('PENDING', 'SUCCEEDED', 'FAILED'); - - CREATE TABLE payments.transfers ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - connector_id uuid NOT NULL, - payment_id uuid NULL, - reference text UNIQUE, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - amount bigint NOT NULL DEFAULT 0, - currency text NOT NULL, - source text NOT NULL, - destination text NOT NULL, - status transfer_status NOT NULL DEFAULT 'PENDING', - error text NULL, - CONSTRAINT transfer_pk PRIMARY KEY (id) - ); - - ALTER TABLE payments.transfers ADD CONSTRAINT transfer_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.transfers ADD CONSTRAINT transfer_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE payments.payment ALTER COLUMN id DROP DEFAULT; - - ALTER TABLE payments.adjustment drop constraint IF EXISTS adjustment_payment; - ALTER TABLE payments.metadata drop constraint IF EXISTS metadata_payment; - ALTER TABLE payments.transfers drop constraint IF EXISTS transfer_payment; - ALTER TABLE payments.payment ALTER COLUMN id TYPE CHARACTER VARYING; - ALTER TABLE payments.adjustment ALTER COLUMN payment_id TYPE CHARACTER VARYING; - ALTER TABLE payments.metadata ALTER COLUMN payment_id TYPE CHARACTER VARYING; - ALTER TABLE payments.transfers ALTER COLUMN payment_id TYPE CHARACTER VARYING; - - ALTER TABLE payments.metadata ADD CONSTRAINT metadata_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.adjustment ADD CONSTRAINT adjustment_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'MANGOPAY'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'MONEYCORP'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE tasks.task ADD COLUMN IF NOT EXISTS "scheduler_options" json; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.account DROP COLUMN IF EXISTS "type"; - DROP TYPE IF EXISTS "public".account_type; - CREATE TYPE "public".account_type AS ENUM('INTERNAL', 'EXTERNAL', 'UNKNOWN');; - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS "type" "public".account_type; - - ALTER TABLE accounts.account drop constraint IF EXISTS account_reference_key; - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS "raw_data" json; - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS "default_currency" text NOT NULL DEFAULT ''; - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS "account_name" text NOT NULL DEFAULT ''; - - ALTER TABLE accounts.account ALTER COLUMN id DROP DEFAULT; - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_account; - ALTER TABLE payments.payment DROP COLUMN IF EXISTS "account_id"; - ALTER TABLE payments.payment ADD COLUMN IF NOT EXISTS "source_account_id" CHARACTER VARYING; - ALTER TABLE payments.payment ADD COLUMN IF NOT EXISTS "destination_account_id" CHARACTER VARYING; - ALTER TABLE accounts.account ALTER COLUMN id TYPE CHARACTER VARYING; - - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_source_account; - ALTER TABLE payments.payment ADD CONSTRAINT payment_source_account - FOREIGN KEY (source_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_destination_account; - ALTER TABLE payments.payment ADD CONSTRAINT payment_destination_account - FOREIGN KEY (destination_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - // Since only one connector is inserting accounts, - // let's just delete the table, since connectors will be - // resetted. Delete it cascade, or we will have an error - _, err := tx.Exec(` - DELETE FROM accounts.account CASCADE; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - // Since only one connector is inserting accounts, - // let's just delete the table, since connectors will be - // resetted. Delete it cascade, or we will have an error - _, err := tx.Exec(` - CREATE TABLE IF NOT EXISTS accounts.balances ( - created_at timestamp with time zone NOT NULL, - account_id CHARACTER VARYING NOT NULL, - currency text NOT NULL, - balance numeric NOT NULL DEFAULT 0, - last_updated_at timestamp with time zone NOT NULL, - PRIMARY KEY (account_id, created_at, currency) - ); - - DROP INDEX IF EXISTS accounts.idx_created_at_account_id_currency; - CREATE INDEX idx_created_at_account_id_currency ON accounts.balances(account_id, last_updated_at desc, currency); - - ALTER TABLE accounts.balances DROP CONSTRAINT IF EXISTS balances_account; - ALTER TABLE accounts.balances ADD CONSTRAINT balances_account - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE payments.adjustment ALTER COLUMN amount TYPE numeric; - ALTER TABLE payments.payment ALTER COLUMN amount TYPE numeric; - ALTER TABLE payments.transfers ALTER COLUMN amount TYPE numeric; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - // In this migration, we have to delete the accounts table since - // we wanna reset the connector, but the connector_id was not - // added, hence the table will not be cleaned up when resetting. - _, err := tx.Exec(` - DELETE FROM accounts.account CASCADE; - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS "connector_id" uuid; - - ALTER TABLE accounts.account ADD CONSTRAINT accounts_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.account ADD COLUMN IF NOT EXISTS metadata jsonb; - - CREATE SCHEMA IF NOT EXISTS transfers; - - CREATE TABLE IF NOT EXISTS transfers.transfer_initiation ( - id character varying NOT NULL, - reference text, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - description text NOT NULL, - type int NOT NULL, - source_account_id character varying NOT NULL, - destination_account_id character varying NOT NULL, - provider connector_provider NOT NULL, - amount numeric NOT NULL, - asset text NOT NULL, - status int NOT NULL, - error text, - PRIMARY KEY (id) - ); - - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT source_account - FOREIGN KEY (source_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT destination_account - FOREIGN KEY (destination_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation ALTER COLUMN source_account_id DROP NOT NULL; - ALTER TABLE transfers.transfer_initiation RENAME COLUMN reference TO payment_id; - ALTER TYPE "public".account_type ADD VALUE IF NOT EXISTS 'EXTERNAL_FORMANCE'; - ALTER TABLE transfers.transfer_initiation ADD COLUMN attempts int NOT NULL DEFAULT 0; - - CREATE TABLE IF NOT EXISTS accounts.bank_account ( - id uuid NOT NULL DEFAULT gen_random_uuid(), - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - name text NOT NULL, - provider connector_provider NOT NULL, - account_number bytea, - iban bytea, - swift_bic_code bytea, - country text, - CONSTRAINT bank_account_pk PRIMARY KEY (id) - ); - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation DROP COLUMN payment_id; - - CREATE TABLE IF NOT EXISTS transfers.transfer_initiation_payments ( - transfer_initiation_id CHARACTER VARYING NOT NULL, - payment_id CHARACTER VARYING NOT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - status int NOT NULL, - error text, - PRIMARY KEY (transfer_initiation_id, payment_id) - ); - - ALTER TABLE transfers.transfer_initiation_payments ADD CONSTRAINT transfer_initiation_id_constraint - FOREIGN KEY (transfer_initiation_id) - REFERENCES transfers.transfer_initiation (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.bank_account ADD COLUMN account_id CHARACTER VARYING; - - ALTER TABLE accounts.bank_account ADD CONSTRAINT bank_account_account_id - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation ADD COLUMN scheduled_at timestamp with time zone; - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.bank_account ADD COLUMN IF NOT EXISTS "connector_id" uuid; - - ALTER TABLE accounts.bank_account ADD CONSTRAINT bank_accounts_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - ) -} diff --git a/components/payments/internal/storage/migrations_v1.x.go b/components/payments/internal/storage/migrations_v1.x.go deleted file mode 100644 index 2b9e50a988..0000000000 --- a/components/payments/internal/storage/migrations_v1.x.go +++ /dev/null @@ -1,1090 +0,0 @@ -package storage - -import ( - "context" - "database/sql/driver" - "encoding/base64" - "encoding/json" - "fmt" - "time" - - "github.com/formancehq/go-libs/migrations" - "github.com/formancehq/payments/internal/models" - "github.com/gibson042/canonicaljson-go" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -func registerMigrationsV1(ctx context.Context, migrator *migrations.Migrator) { - migrator.RegisterMigrations( - migrations.Migration{ - Name: "", - Up: func(tx bun.Tx) error { - if err := migrateConnectors(ctx, tx); err != nil { - return err - } - - if err := migrateAccountID(ctx, tx); err != nil { - return err - } - - if err := migratePaymentID(ctx, tx); err != nil { - return err - } - - if err := migrateTransferInitiationID(ctx, tx); err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'ATLAR'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TABLE IF NOT EXISTS accounts.pool_accounts ( - pool_id uuid NOT NULL, - account_id CHARACTER VARYING NOT NULL, - CONSTRAINT pool_accounts_pk PRIMARY KEY (pool_id, account_id) - ); - - CREATE TABLE IF NOT EXISTS accounts.pools ( - id uuid NOT NULL, - name text NOT NULL UNIQUE, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - CONSTRAINT pools_pk PRIMARY KEY (id) - ); - - ALTER TABLE accounts.pool_accounts ADD CONSTRAINT pool_accounts_pool_id - FOREIGN KEY (pool_id) - REFERENCES accounts.pools (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.pool_accounts ADD CONSTRAINT pool_accounts_account_id - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'ADYEN'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.pools ALTER COLUMN id SET DEFAULT gen_random_uuid(); - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.bank_account ADD COLUMN IF NOT EXISTS metadata jsonb; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE payments.payment ADD COLUMN IF NOT EXISTS initial_amount numeric NOT NULL DEFAULT 0; - UPDATE payments.payment SET initial_amount = amount; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'EXPIRED'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'REFUNDED'; - - CREATE TABLE IF NOT EXISTS connectors.webhook ( - id uuid NOT NULL, - connector_id CHARACTER VARYING NOT NULL, - request_body bytea NOT NULL, - CONSTRAINT webhook_pk PRIMARY KEY (id) - ); - - ALTER TABLE connectors.webhook DROP CONSTRAINT IF EXISTS webhook_connector_id; - ALTER TABLE connectors.webhook ADD CONSTRAINT webhook_connector_id - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation ADD COLUMN IF NOT EXISTS metadata jsonb; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation ALTER COLUMN description DROP NOT NULL; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TABLE IF NOT EXISTS transfers.transfer_initiation_adjustments ( - id uuid NOT NULL, - transfer_initiation_id CHARACTER VARYING NOT NULL, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - status int NOT NULL, - error text, - metadata jsonb, - CONSTRAINT transfer_initiation_adjustments_pk PRIMARY KEY (id) - ); - - ALTER TABLE transfers.transfer_initiation_adjustments ADD CONSTRAINT adjusmtents_transfer_initiation_id_constraint - FOREIGN KEY (transfer_initiation_id) - REFERENCES transfers.transfer_initiation (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - INSERT INTO transfers.transfer_initiation_adjustments (id, transfer_initiation_id, created_at, status, error, metadata) - SELECT gen_random_uuid(), id, updated_at, status, error, '{}'::jsonb FROM transfers.transfer_initiation; - - ALTER TABLE transfers.transfer_initiation DROP COLUMN IF EXISTS status; - ALTER TABLE transfers.transfer_initiation DROP COLUMN IF EXISTS error; - ALTER TABLE transfers.transfer_initiation DROP COLUMN IF EXISTS updated_at; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - // Drop check constraint on created at since it's created by the code and - // not by the user. - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation_adjustments DROP CONSTRAINT transfer_initiation_adjustments_created_at_check; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - // Drop check constraint on created at since it's created by the code and - // not by the user. - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation_payments DROP CONSTRAINT transfer_initiation_payments_created_at_check; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TABLE IF NOT EXISTS transfers.transfer_reversal ( - id character varying NOT NULL, - transfer_initiation_id character varying NOT NULL, - reference text, - created_at timestamp with time zone NOT NULL, - updated_at timestamp with time zone NOT NULL, - description text NOT NULL, - connector_id CHARACTER VARYING NOT NULL, - amount numeric NOT NULL, - asset text NOT NULL, - status int NOT NULL, - error text, - metadata jsonb, - PRIMARY KEY (id) - ); - - -- UNIQUE constrait for processing only one reversal at a time. - CREATE UNIQUE INDEX transfer_reversal_processing_unique_constraint ON transfers.transfer_reversal (transfer_initiation_id) WHERE status = 1; - - ALTER TABLE transfers.transfer_reversal ADD CONSTRAINT transfer_reversal_connector_id - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE transfers.transfer_reversal ADD CONSTRAINT transfer_reversal_transfer_initiation_id - FOREIGN KEY (transfer_initiation_id) - REFERENCES transfers.transfer_initiation (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE transfers.transfer_initiation ADD COLUMN IF NOT EXISTS initial_amount numeric NOT NULL DEFAULT 0; - UPDATE transfers.transfer_initiation SET initial_amount = amount; - - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT amount_non_negative CHECK (amount >= 0); - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT initial_amount_non_negative CHECK (initial_amount >= 0); - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'EXPIRED'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'REFUNDED'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'REFUNDED_FAILURE'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'DISPUTE'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'DISPUTE_WON'; - ALTER TYPE "public".payment_status ADD VALUE IF NOT EXISTS 'DISPUTE_LOST'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - CREATE TABLE IF NOT EXISTS accounts.bank_account_adjustments ( - id uuid NOT NULL, - created_at timestamp with time zone NOT NULL, - bank_account_id uuid NOT NULL, - connector_id CHARACTER VARYING NOT NULL, - account_id CHARACTER VARYING NOT NULL, - CONSTRAINT transfer_initiation_adjustments_pk PRIMARY KEY (id) - ); - - ALTER TABLE accounts.bank_account_adjustments ADD CONSTRAINT bank_account_adjustments_bank_account_id - FOREIGN KEY (bank_account_id) - REFERENCES accounts.bank_account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.bank_account_adjustments ADD CONSTRAINT bank_account_adjustments_connector_id - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.bank_account_adjustments ADD CONSTRAINT bank_account_adjustments_account_id - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - INSERT INTO accounts.bank_account_adjustments (id, created_at, bank_account_id, connector_id, account_id) - SELECT gen_random_uuid(), created_at, id, connector_id, account_id FROM accounts.bank_account WHERE account_id IS NOT NULL; - - ALTER TABLE accounts.bank_account DROP COLUMN IF EXISTS account_id; - ALTER TABLE accounts.bank_account DROP COLUMN IF EXISTS connector_id; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.bank_account_adjustments RENAME TO bank_account_related_accounts; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TYPE connector_provider ADD VALUE IF NOT EXISTS 'GENERIC'; - `) - if err != nil { - return err - } - - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - return fixExpandingChangelogs(ctx, tx) - }, - }, - ) -} - -type PreviousConnector struct { - bun.BaseModel `bun:"connectors.connector"` - - ID uuid.UUID `bun:",pk,nullzero"` - CreatedAt time.Time `bun:",nullzero"` - Provider models.ConnectorProvider - - // EncryptedConfig is a PGP-encrypted JSON string. - EncryptedConfig []byte `bun:"config"` - - // Config is a decrypted config. It is not stored in the database. - Config json.RawMessage `bun:"decrypted_config,scanonly"` -} - -type Connector struct { - bun.BaseModel `bun:"connectors.connector_v2"` - - ID models.ConnectorID `bun:",pk,nullzero"` - Name string - CreatedAt time.Time `bun:",nullzero"` - Provider models.ConnectorProvider - - // EncryptedConfig is a PGP-encrypted JSON string. - EncryptedConfig []byte `bun:"config"` -} - -func migrateConnectors(ctx context.Context, tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.account ALTER COLUMN connector_id SET NOT NULL; - ALTER TABLE accounts.bank_account ALTER COLUMN connector_id SET NOT NULL; - - CREATE TABLE connectors.connector_v2 ( - id CHARACTER VARYING NOT NULL, - name text NOT NULL UNIQUE, - created_at timestamp with time zone NOT NULL DEFAULT NOW() CHECK (created_at<=NOW()), - provider connector_provider NOT NULL, - config bytea NULL, - CONSTRAINT connector_v2_pk PRIMARY KEY (id) - ); - `) - if err != nil { - return err - } - - var oldConnectors []*PreviousConnector - err = tx.NewSelect(). - Model(&oldConnectors). - Scan(ctx) - if err != nil { - return err - } - - newConnectors := make([]*Connector, 0, len(oldConnectors)) - for _, oldConnector := range oldConnectors { - newConnectors = append(newConnectors, &Connector{ - ID: models.ConnectorID{ - Reference: uuid.New(), - Provider: oldConnector.Provider, - }, - Name: oldConnector.Provider.String(), - CreatedAt: oldConnector.CreatedAt, - Provider: oldConnector.Provider, - EncryptedConfig: oldConnector.EncryptedConfig, - }) - } - - if len(newConnectors) > 0 { - _, err = tx.NewInsert(). - Model(&newConnectors). - Exec(ctx) - if err != nil { - return err - } - } - - _, err = tx.Exec(` - ALTER TABLE tasks.task ADD COLUMN IF NOT EXISTS provider connector_provider; - UPDATE tasks.task SET provider = (SELECT provider FROM connectors.connector WHERE id = task.connector_id); - ALTER TABLE payments.payment ADD COLUMN IF NOT EXISTS provider connector_provider; - UPDATE payments.payment SET provider = (SELECT provider FROM connectors.connector WHERE id = payment.connector_id); - ALTER TABLE tasks.task DROP CONSTRAINT IF EXISTS task_connector; - ALTER TABLE tasks.task ALTER COLUMN connector_id TYPE CHARACTER VARYING; - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_connector; - ALTER TABLE payments.payment ALTER COLUMN connector_id TYPE CHARACTER VARYING; - ALTER TABLE payments.transfers DROP CONSTRAINT IF EXISTS transfer_connector; - ALTER TABLE payments.transfers ALTER COLUMN connector_id TYPE CHARACTER VARYING; - ALTER TABLE accounts.account DROP CONSTRAINT IF EXISTS accounts_connector; - ALTER TABLE accounts.account ALTER COLUMN connector_id TYPE CHARACTER VARYING; - ALTER TABLE accounts.bank_account DROP CONSTRAINT IF EXISTS bank_accounts_connector; - ALTER TABLE accounts.bank_account ALTER COLUMN connector_id TYPE CHARACTER VARYING; - ALTER TABLE transfers.transfer_initiation DROP CONSTRAINT IF EXISTS transfer_initiation_connector_id; - - DROP TABLE connectors.connector; - ALTER TABLE connectors.connector_v2 RENAME TO connector; - - UPDATE tasks.task SET connector_id = (SELECT id FROM connectors.connector WHERE provider = task.provider); - UPDATE payments.payment SET connector_id = (SELECT id FROM connectors.connector WHERE provider = payment.provider); - UPDATE accounts.account SET connector_id = (SELECT id FROM connectors.connector WHERE provider::text = account.provider); - UPDATE accounts.bank_account SET connector_id = (SELECT id FROM connectors.connector WHERE provider = bank_account.provider); - - ALTER TABLE tasks.task DROP COLUMN IF EXISTS provider; - ALTER TABLE accounts.account DROP COLUMN IF EXISTS provider; - ALTER TABLE accounts.bank_account DROP COLUMN IF EXISTS provider; - ALTER TABLE payments.payment DROP COLUMN IF EXISTS provider; - - ALTER TABLE tasks.task ADD CONSTRAINT task_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment ADD CONSTRAINT payment_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.account ADD CONSTRAINT accounts_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.bank_account ADD CONSTRAINT bank_accounts_connector - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE transfers.transfer_initiation ADD COLUMN IF NOT EXISTS connector_id CHARACTER VARYING NOT NULL; - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT transfer_initiation_connector_id - FOREIGN KEY (connector_id) - REFERENCES connectors.connector (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - UPDATE transfers.transfer_initiation SET connector_id = (SELECT id FROM connectors.connector WHERE provider = transfer_initiation.provider); - `) - if err != nil { - return err - } - - return nil -} - -type PreviousAccountID struct { - Reference string - Provider models.ConnectorProvider -} - -func PreviousAccountIDFromString(value string) (*PreviousAccountID, error) { - data, err := base64.URLEncoding.DecodeString(value) - if err != nil { - return nil, err - } - ret := PreviousAccountID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return nil, err - } - - return &ret, nil -} - -func (aid *PreviousAccountID) Scan(value interface{}) error { - if value == nil { - return errors.New("account id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := PreviousAccountIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse account id %s: %v", v, err) - } - - *aid = *id - return nil - } - } - - return fmt.Errorf("failed to scan account id: %v", value) -} - -func (aid PreviousAccountID) Value() (driver.Value, error) { - return aid.String(), nil -} - -func (aid *PreviousAccountID) String() string { - if aid == nil || aid.Reference == "" { - return "" - } - - data, err := canonicaljson.Marshal(aid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.EncodeToString(data) -} - -func migrateAccountID(ctx context.Context, tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE accounts.balances DROP CONSTRAINT IF EXISTS balances_account; - ALTER TABLE accounts.bank_account DROP CONSTRAINT IF EXISTS bank_account_account_id; - ALTER TABLE transfers.transfer_initiation DROP CONSTRAINT IF EXISTS destination_account; - ALTER TABLE transfers.transfer_initiation DROP CONSTRAINT IF EXISTS source_account; - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_destination_account; - ALTER TABLE payments.payment DROP CONSTRAINT IF EXISTS payment_source_account; - `) - if err != nil { - return err - } - - var previousIDs []PreviousAccountID - var connectorIDs []models.ConnectorID - err = tx.NewSelect().Model((*models.Account)(nil)).Column("id", "connector_id").Scan(ctx, &previousIDs, &connectorIDs) - if err != nil { - return err - } - - if len(previousIDs) != len(connectorIDs) { - return fmt.Errorf("migrateAccountID: previousIDs and connectorIDs have different length") - } - - type AccoutIDMigration struct { - PreviousAccountID PreviousAccountID - NewAccountID models.AccountID - } - migrations := make([]AccoutIDMigration, 0, len(previousIDs)) - for i, previousID := range previousIDs { - migrations = append(migrations, AccoutIDMigration{ - PreviousAccountID: previousID, - NewAccountID: models.AccountID{ - Reference: previousID.Reference, - ConnectorID: connectorIDs[i], - }, - }) - } - - for _, migration := range migrations { - _, err := tx.NewUpdate(). - Model((*models.Account)(nil)). - Set("id = ?", migration.NewAccountID). - Where("id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.Balance)(nil)). - Set("account_id = ?", migration.NewAccountID). - Where("account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.BankAccount)(nil)). - Set("account_id = ?", migration.NewAccountID). - Where("account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.TransferInitiation)(nil)). - Set("source_account_id = ?", migration.NewAccountID). - Where("source_account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.TransferInitiation)(nil)). - Set("destination_account_id = ?", migration.NewAccountID). - Where("destination_account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.Payment)(nil)). - Set("source_account_id = ?", migration.NewAccountID). - Where("source_account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.Payment)(nil)). - Set("destination_account_id = ?", migration.NewAccountID). - Where("destination_account_id = ?", migration.PreviousAccountID). - Exec(ctx) - if err != nil { - return err - } - } - - _, err = tx.Exec(` - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT source_account - FOREIGN KEY (source_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE transfers.transfer_initiation ADD CONSTRAINT destination_account - FOREIGN KEY (destination_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.balances ADD CONSTRAINT balances_account - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE accounts.bank_account ADD CONSTRAINT bank_account_account_id - FOREIGN KEY (account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment ADD CONSTRAINT payment_source_account - FOREIGN KEY (source_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.payment ADD CONSTRAINT payment_destination_account - FOREIGN KEY (destination_account_id) - REFERENCES accounts.account (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil -} - -type PreviousPaymentID struct { - models.PaymentReference - Provider models.ConnectorProvider -} - -func (pid PreviousPaymentID) String() string { - data, err := canonicaljson.Marshal(pid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.EncodeToString(data) -} - -func PaymentIDFromString(value string) (*PreviousPaymentID, error) { - data, err := base64.URLEncoding.DecodeString(value) - if err != nil { - return nil, err - } - ret := PreviousPaymentID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return nil, err - } - - return &ret, nil -} - -func (pid PreviousPaymentID) Value() (driver.Value, error) { - return pid.String(), nil -} - -func (pid *PreviousPaymentID) Scan(value interface{}) error { - if value == nil { - return errors.New("payment id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := PaymentIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse paymentid %s: %v", v, err) - } - - *pid = *id - return nil - } - } - - return fmt.Errorf("failed to scan paymentid: %v", value) -} - -func migratePaymentID(ctx context.Context, tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE payments.adjustment DROP CONSTRAINT IF EXISTS adjustment_payment; - ALTER TABLE payments.metadata DROP CONSTRAINT IF EXISTS metadata_payment; - `) - if err != nil { - return err - } - - var previousIDs []PreviousPaymentID - var connectorIDs []models.ConnectorID - err = tx.NewSelect().Model((*models.Payment)(nil)).Column("id", "connector_id").Scan(ctx, &previousIDs, &connectorIDs) - if err != nil { - return err - } - - if len(previousIDs) != len(connectorIDs) { - return fmt.Errorf("migrateAccountID: previousIDs and connectorIDs have different length") - } - - type PaymentIDMigration struct { - PreviousPaymentID PreviousPaymentID - NewPaymentID models.PaymentID - } - migrations := make([]PaymentIDMigration, 0, len(previousIDs)) - for i, previousID := range previousIDs { - migrations = append(migrations, PaymentIDMigration{ - PreviousPaymentID: previousID, - NewPaymentID: models.PaymentID{ - PaymentReference: previousID.PaymentReference, - ConnectorID: connectorIDs[i], - }, - }) - } - - for _, migration := range migrations { - _, err := tx.NewUpdate(). - Model((*models.Payment)(nil)). - Set("id = ?", migration.NewPaymentID). - Where("id = ?", migration.PreviousPaymentID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.PaymentAdjustment)(nil)). - Set("payment_id = ?", migration.NewPaymentID). - Where("payment_id = ?", migration.PreviousPaymentID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.PaymentMetadata)(nil)). - Set("payment_id = ?", migration.NewPaymentID). - Where("payment_id = ?", migration.PreviousPaymentID). - Exec(ctx) - if err != nil { - return err - } - } - - _, err = tx.Exec(` - ALTER TABLE payments.adjustment ADD CONSTRAINT adjustment_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - - ALTER TABLE payments.metadata ADD CONSTRAINT metadata_payment - FOREIGN KEY (payment_id) - REFERENCES payments.payment (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil -} - -type PreviousTransferInitiationID struct { - Reference string - Provider models.ConnectorProvider -} - -func (tid PreviousTransferInitiationID) String() string { - data, err := canonicaljson.Marshal(tid) - if err != nil { - panic(err) - } - - return base64.URLEncoding.EncodeToString(data) -} - -func TransferInitiationIDFromString(value string) (PreviousTransferInitiationID, error) { - data, err := base64.URLEncoding.DecodeString(value) - if err != nil { - return PreviousTransferInitiationID{}, err - } - ret := PreviousTransferInitiationID{} - err = canonicaljson.Unmarshal(data, &ret) - if err != nil { - return PreviousTransferInitiationID{}, err - } - - return ret, nil -} - -func (tid PreviousTransferInitiationID) Value() (driver.Value, error) { - return tid.String(), nil -} - -func (tid *PreviousTransferInitiationID) Scan(value interface{}) error { - if value == nil { - return errors.New("payment id is nil") - } - - if s, err := driver.String.ConvertValue(value); err == nil { - - if v, ok := s.(string); ok { - - id, err := TransferInitiationIDFromString(v) - if err != nil { - return fmt.Errorf("failed to parse paymentid %s: %v", v, err) - } - - *tid = id - return nil - } - } - - return fmt.Errorf("failed to scan paymentid: %v", value) -} - -func migrateTransferInitiationID(ctx context.Context, tx bun.Tx) error { - _, err := tx.Exec(` - ALTER TABLE transfers.transfer_initiation_payments DROP CONSTRAINT IF EXISTS transfer_initiation_id_constraint; - `) - if err != nil { - return err - } - - var previousIDs []PreviousTransferInitiationID - var connectorIDs []models.ConnectorID - err = tx.NewSelect().Model((*models.TransferInitiation)(nil)).Column("id", "connector_id").Scan(ctx, &previousIDs, &connectorIDs) - if err != nil { - return err - } - - if len(previousIDs) != len(connectorIDs) { - return fmt.Errorf("migrateAccountID: previousIDs and connectorIDs have different length") - } - - type TransferInitiationIDMigration struct { - PreviousTransferInitiationID PreviousTransferInitiationID - NewTransferInitiationID models.TransferInitiationID - } - - migrations := make([]TransferInitiationIDMigration, 0, len(previousIDs)) - for i, previousID := range previousIDs { - migrations = append(migrations, TransferInitiationIDMigration{ - PreviousTransferInitiationID: previousID, - NewTransferInitiationID: models.TransferInitiationID{ - Reference: previousID.Reference, - ConnectorID: connectorIDs[i], - }, - }) - } - - for _, migration := range migrations { - _, err := tx.NewUpdate(). - Model((*models.TransferInitiation)(nil)). - Set("id = ?", migration.NewTransferInitiationID). - Where("id = ?", migration.PreviousTransferInitiationID). - Exec(ctx) - if err != nil { - return err - } - - _, err = tx.NewUpdate(). - Model((*models.TransferInitiationPayment)(nil)). - Set("transfer_initiation_id = ?", migration.NewTransferInitiationID). - Where("transfer_initiation_id = ?", migration.PreviousTransferInitiationID). - Exec(ctx) - if err != nil { - return err - } - } - - _, err = tx.Exec(` - ALTER TABLE transfers.transfer_initiation_payments ADD CONSTRAINT transfer_initiation_id_constraint - FOREIGN KEY (transfer_initiation_id) - REFERENCES transfers.transfer_initiation (id) - ON DELETE CASCADE - NOT DEFERRABLE - INITIALLY IMMEDIATE - ; - `) - if err != nil { - return err - } - - return nil -} - -func fixExpandingChangelogs(ctx context.Context, tx bun.Tx) error { - var createdAt time.Time - for { - var metadata []models.PaymentMetadata - query := tx.NewSelect(). - Model(&metadata). - Order("created_at ASC"). - Limit(100) - - if !createdAt.IsZero() { - query.Where("created_at > ?", createdAt) - } - - err := query.Scan(ctx) - if err != nil { - return err - } - - if len(metadata) == 0 { - break - } - - for i, m := range metadata { - if m.Changelog == nil { - continue - } - - var newChangelogs []models.MetadataChangelog - for _, cl := range m.Changelog { - if len(newChangelogs) > 0 && cl.Value == newChangelogs[len(newChangelogs)-1].Value { - continue - } - - newChangelogs = append(newChangelogs, cl) - } - - metadata[i].Changelog = newChangelogs - createdAt = m.CreatedAt - } - - fmt.Println("Updating", len(metadata), "metadata") - - _, err = tx.NewInsert(). - Model(&metadata). - On("CONFLICT (payment_id, key) DO UPDATE"). - Set("changelog = EXCLUDED.changelog"). - Exec(ctx) - if err != nil { - return err - } - } - - return nil -} diff --git a/components/payments/local_env/postgres/init.sql b/components/payments/local_env/postgres/init.sql deleted file mode 100644 index 2945c921e9..0000000000 --- a/components/payments/local_env/postgres/init.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER ROLE payments SET search_path = public; \ No newline at end of file diff --git a/components/payments/main.go b/components/payments/main.go deleted file mode 100644 index 2c94d7e02a..0000000000 --- a/components/payments/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/formancehq/payments/cmd" - -func main() { - cmd.Execute() -} diff --git a/components/payments/openapi.yaml b/components/payments/openapi.yaml deleted file mode 100644 index 7788a4579b..0000000000 --- a/components/payments/openapi.yaml +++ /dev/null @@ -1,2592 +0,0 @@ -openapi: 3.0.3 -info: - title: Payments API - version: PAYMENTS_VERSION -paths: - /_info: - get: - summary: Get server info - operationId: getServerInfo - tags: - - payments.v1 - responses: - '200': - $ref: '#/components/responses/ServerInfo' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /payments: - post: - summary: Create a payment - tags: - - payments.v1 - operationId: createPayment - description: Create a payment - requestBody: - $ref: '#/components/requestBodies/Payment' - responses: - '200': - $ref: '#/components/responses/Payment' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - get: - summary: List payments - operationId: listPayments - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/Query' - responses: - '200': - $ref: '#/components/responses/Payments' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /payments/{paymentId}: - get: - summary: Get a payment - tags: - - payments.v1 - operationId: getPayment - parameters: - - $ref: '#/components/parameters/PaymentId' - responses: - '200': - $ref: '#/components/responses/Payment' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /payments/{paymentId}/metadata: - patch: - summary: Update metadata - tags: - - payments.v1 - operationId: updateMetadata - parameters: - - $ref: '#/components/parameters/PaymentId' - requestBody: - $ref: '#/components/requestBodies/UpdateMetadata' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /transfer-initiations: - get: - summary: List Transfer Initiations - operationId: listTransferInitiations - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/Query' - responses: - '200': - $ref: '#/components/responses/TransferInitiations' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - post: - summary: Create a TransferInitiation - tags: - - payments.v1 - operationId: createTransferInitiation - description: Create a transfer initiation - requestBody: - $ref: '#/components/requestBodies/TransferInitiation' - responses: - '200': - $ref: '#/components/responses/TransferInitiation' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /transfer-initiations/{transferId}: - get: - summary: Get a transfer initiation - tags: - - payments.v1 - operationId: getTransferInitiation - parameters: - - $ref: '#/components/parameters/TransferId' - responses: - '200': - $ref: '#/components/responses/TransferInitiation' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - delete: - summary: Delete a transfer initiation - operationId: deleteTransferInitiation - tags: - - payments.v1 - description: Delete a transfer initiation by its id. - parameters: - - $ref: '#/components/parameters/TransferId' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /transfer-initiations/{transferId}/status: - post: - summary: Update the status of a transfer initiation - tags: - - payments.v1 - operationId: udpateTransferInitiationStatus - description: Update a transfer initiation status - parameters: - - $ref: '#/components/parameters/TransferId' - requestBody: - $ref: '#/components/requestBodies/UpdateTransferInitiationStatus' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /transfer-initiations/{transferId}/reverse: - post: - summary: Reverse a transfer initiation - tags: - - payments.v1 - operationId: reverseTransferInitiation - description: Reverse transfer initiation - parameters: - - $ref: '#/components/parameters/TransferId' - requestBody: - $ref: '#/components/requestBodies/ReverseTransferInitiation' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /transfer-initiations/{transferId}/retry: - post: - summary: Retry a failed transfer initiation - tags: - - payments.v1 - operationId: retryTransferInitiation - description: Retry a failed transfer initiation - parameters: - - $ref: '#/components/parameters/TransferId' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /pools: - get: - summary: List Pools - operationId: listPools - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/Query' - responses: - '200': - $ref: '#/components/responses/Pools' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - post: - summary: Create a Pool - tags: - - payments.v1 - operationId: createPool - description: Create a Pool - requestBody: - $ref: '#/components/requestBodies/Pool' - responses: - '200': - $ref: '#/components/responses/Pool' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /pools/{poolId}: - get: - summary: Get a Pool - tags: - - payments.v1 - operationId: getPool - parameters: - - $ref: '#/components/parameters/PoolId' - responses: - '200': - $ref: '#/components/responses/Pool' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - delete: - summary: Delete a Pool - operationId: deletePool - tags: - - payments.v1 - description: Delete a pool by its id. - parameters: - - $ref: '#/components/parameters/PoolId' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /pools/{poolId}/accounts: - post: - summary: Add an account to a pool - tags: - - payments.v1 - operationId: addAccountToPool - description: Add an account to a pool - parameters: - - $ref: '#/components/parameters/PoolId' - requestBody: - $ref: '#/components/requestBodies/AddAccountToPool' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /pools/{poolId}/accounts/{accountId}: - delete: - summary: Remove an account from a pool - operationId: removeAccountFromPool - tags: - - payments.v1 - description: Remove an account from a pool by its id. - parameters: - - $ref: '#/components/parameters/PoolId' - - $ref: '#/components/parameters/AccountId' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /pools/{poolId}/balances: - get: - summary: Get pool balances - operationId: getPoolBalances - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PoolId' - - name: at - in: query - description: | - Filter balances by date. - required: true - schema: - type: string - format: date-time - responses: - '200': - $ref: '#/components/responses/PoolBalances' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /accounts: - post: - summary: Create an account - tags: - - payments.v1 - operationId: createAccount - description: Create an account - requestBody: - $ref: '#/components/requestBodies/Account' - responses: - '200': - $ref: '#/components/responses/Account' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - get: - summary: List accounts - operationId: listAccounts - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - - $ref: '#/components/parameters/Query' - requestBody: - content: - application/json: - schema: - type: object - additionalProperties: true - responses: - '200': - $ref: '#/components/responses/Accounts' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /accounts/{accountId}: - get: - summary: Get an account - tags: - - payments.v1 - operationId: getAccount - parameters: - - $ref: '#/components/parameters/AccountId' - responses: - '200': - $ref: '#/components/responses/Account' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /accounts/{accountId}/balances: - get: - summary: Get account balances - operationId: getAccountBalances - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/AccountId' - - name: limit - in: query - description: The maximum number of results to return per page. - required: false - schema: - type: integer - format: int64 - - name: asset - in: query - description: | - Filter balances by currency. - If not specified, all account's balances will be returned. - required: false - schema: - type: string - - name: from - in: query - description: | - Filter balances by date. - If not specified, all account's balances will be returned. - required: false - schema: - type: string - format: date-time - - name: to - in: query - description: | - Filter balances by date. - If not specified, default will be set to now. - required: false - schema: - type: string - format: date-time - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - responses: - '200': - $ref: '#/components/responses/AccountBalances' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /bank-accounts: - post: - summary: Create a BankAccount in Payments and on the PSP - tags: - - payments.v1 - operationId: createBankAccount - description: Create a bank account in Payments and on the PSP. - requestBody: - $ref: '#/components/requestBodies/BankAccount' - responses: - '200': - $ref: '#/components/responses/BankAccount' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - get: - summary: List bank accounts created by user on Formance - operationId: listBankAccounts - tags: - - payments.v1 - parameters: - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - - $ref: '#/components/parameters/Sort' - description: List all bank accounts created by user on Formance. - responses: - '200': - $ref: '#/components/responses/BankAccounts' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /bank-accounts/{bankAccountId}: - get: - summary: Get a bank account created by user on Formance - tags: - - payments.v1 - operationId: getBankAccount - parameters: - - $ref: '#/components/parameters/BankAccountId' - responses: - '200': - $ref: '#/components/responses/BankAccount' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /bank-accounts/{bankAccountId}/forward: - post: - summary: Forward a bank account to a connector - tags: - - payments.v1 - operationId: forwardBankAccount - parameters: - - $ref: '#/components/parameters/BankAccountId' - requestBody: - $ref: '#/components/requestBodies/ForwardBankAccount' - responses: - '200': - $ref: '#/components/responses/BankAccount' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /bank-accounts/{bankAccountId}/metadata: - patch: - summary: Update metadata of a bank account - tags: - - payments.v1 - operationId: updateBankAccountMetadata - parameters: - - $ref: '#/components/parameters/BankAccountId' - requestBody: - $ref: '#/components/requestBodies/UpdateBankAccountMetadata' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /connectors: - get: - summary: List all installed connectors - operationId: listAllConnectors - tags: - - payments.v1 - description: List all installed connectors. - responses: - '200': - $ref: '#/components/responses/Connectors' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/configs: - get: - summary: List the configs of each available connector - operationId: listConfigsAvailableConnectors - tags: - - payments.v1 - description: List the configs of each available connector. - responses: - '200': - $ref: '#/components/responses/ConnectorsConfigs' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}: - post: - summary: Install a connector - tags: - - payments.v1 - operationId: installConnector - description: Install a connector by its name and config. - parameters: - - $ref: '#/components/parameters/Connector' - requestBody: - $ref: '#/components/requestBodies/ConnectorConfig' - responses: - '201': - $ref: '#/components/responses/ConnectorInstalled' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - delete: - summary: Uninstall a connector - deprecated: true - operationId: uninstallConnector - tags: - - payments.v1 - description: Uninstall a connector by its name. - parameters: - - $ref: '#/components/parameters/Connector' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /connectors/{connector}/{connectorId}: - delete: - summary: Uninstall a connector - operationId: uninstallConnectorV1 - tags: - - payments.v1 - description: Uninstall a connector by its name. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /connectors/{connector}/config: - get: - summary: Read the config of a connector - deprecated: true - operationId: readConnectorConfig - tags: - - payments.v1 - description: Read connector config - parameters: - - $ref: '#/components/parameters/Connector' - responses: - '200': - $ref: '#/components/responses/ConnectorConfig' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/{connectorId}/config: - post: - summary: Update the config of a connector - operationId: updateConnectorConfigV1 - tags: - - payments.v1 - description: Update connector config - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - requestBody: - $ref: '#/components/requestBodies/ConnectorConfig' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - get: - summary: Read the config of a connector - operationId: readConnectorConfigV1 - tags: - - payments.v1 - description: Read connector config - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - responses: - '200': - $ref: '#/components/responses/ConnectorConfig' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/reset: - post: - summary: Reset a connector - deprecated: true - operationId: resetConnector - tags: - - payments.v1 - description: | - Reset a connector by its name. - It will remove the connector and ALL PAYMENTS generated with it. - parameters: - - $ref: '#/components/parameters/Connector' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /connectors/{connector}/{connectorId}/reset: - post: - summary: Reset a connector - operationId: resetConnectorV1 - tags: - - payments.v1 - description: | - Reset a connector by its name. - It will remove the connector and ALL PAYMENTS generated with it. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - responses: - '204': - $ref: '#/components/responses/NoContent' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write - /connectors/{connector}/tasks: - get: - summary: List tasks from a connector - deprecated: true - tags: - - payments.v1 - operationId: listConnectorTasks - description: List all tasks associated with this connector. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - responses: - '200': - $ref: '#/components/responses/Tasks' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/{connectorId}/tasks: - get: - summary: List tasks from a connector - tags: - - payments.v1 - operationId: listConnectorTasksV1 - description: List all tasks associated with this connector. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - - $ref: '#/components/parameters/PageSize' - - $ref: '#/components/parameters/Cursor' - responses: - '200': - $ref: '#/components/responses/Tasks' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/tasks/{taskId}: - get: - summary: Read a specific task of the connector - deprecated: true - tags: - - payments.v1 - operationId: getConnectorTask - description: Get a specific task associated to the connector. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/TaskId' - responses: - '200': - $ref: '#/components/responses/Task' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/{connectorId}/tasks/{taskId}: - get: - summary: Read a specific task of the connector - tags: - - payments.v1 - operationId: getConnectorTaskV1 - description: Get a specific task associated to the connector. - parameters: - - $ref: '#/components/parameters/Connector' - - $ref: '#/components/parameters/ConnectorID' - - $ref: '#/components/parameters/TaskId' - responses: - '200': - $ref: '#/components/responses/Task' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:read - /connectors/{connector}/transfers: - post: - summary: Transfer funds between Connector accounts - tags: - - payments.v1 - operationId: connectorsTransfer - description: Execute a transfer between two accounts. - parameters: - - $ref: '#/components/parameters/Connector' - requestBody: - $ref: '#/components/requestBodies/Transfer' - responses: - '200': - $ref: '#/components/responses/Transfer' - default: - $ref: '#/components/responses/PaymentsErrorResponse' - security: - - Authorization: - - payments:write -components: - parameters: - Query: - name: query - in: query - required: false - description: | - Filters used to filter resources. - schema: - type: string - PageSize: - name: pageSize - in: query - description: | - The maximum number of results to return per page. - example: 100 - schema: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - default: 15 - Cursor: - name: cursor - in: query - description: | - Parameter used in pagination requests. Maximum page size is set to 15. - Set to the value of next for the next page of results. - Set to the value of previous for the previous page of results. - No other parameters can be set when this parameter is set. - schema: - type: string - example: aHR0cHM6Ly9nLnBhZ2UvTmVrby1SYW1lbj9zaGFyZQ== - Sort: - name: sort - in: query - schema: - type: array - items: - type: string - description: Fields used to sort payments (default is date:desc). - example: - - date:asc - - status:desc - PaymentId: - name: paymentId - in: path - schema: - type: string - description: The payment ID. - example: XXX - required: true - AccountId: - name: accountId - in: path - schema: - type: string - description: The account ID. - example: XXX - required: true - ConnectorID: - name: connectorId - in: path - schema: - type: string - description: The connector ID. - example: XXX - required: true - PoolId: - name: poolId - in: path - schema: - type: string - description: The pool ID. - example: XXX - required: true - TransferId: - name: transferId - in: path - schema: - type: string - description: The transfer ID. - example: XXX - required: true - BankAccountId: - name: bankAccountId - in: path - schema: - type: string - description: The bank account ID. - example: XXX - required: true - Connector: - name: connector - description: The name of the connector. - in: path - schema: - $ref: '#/components/schemas/Connector' - required: true - TaskId: - name: taskId - description: The task ID. - example: task1 - in: path - schema: - type: string - required: true - responses: - NoContent: - description: No content - PaymentsErrorResponse: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/PaymentsErrorResponse' - ServerInfo: - description: Server information - content: - application/json: - schema: - $ref: '#/components/schemas/ServerInfo' - Payments: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PaymentsCursor' - Payment: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PaymentResponse' - Pools: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PoolsCursor' - Pool: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PoolResponse' - PoolBalances: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/PoolBalancesResponse' - TransferInitiations: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInitiationsCursor' - TransferInitiation: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInitiationResponse' - Accounts: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountsCursor' - Account: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AccountResponse' - AccountBalances: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/BalancesCursor' - ConnectorInstalled: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectorResponse' - Connectors: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectorsResponse' - BankAccounts: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/BankAccountsCursor' - BankAccount: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/BankAccountResponse' - ConnectorsConfigs: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectorsConfigsResponse' - ConnectorConfig: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectorConfigResponse' - Tasks: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TasksCursor' - Task: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TaskResponse' - Transfer: - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/TransferResponse' - requestBodies: - ConnectorConfig: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ConnectorConfig' - UpdateMetadata: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PaymentMetadata' - Payment: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PaymentRequest' - Account: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AccountRequest' - Pool: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PoolRequest' - AddAccountToPool: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AddAccountToPoolRequest' - TransferInitiation: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferInitiationRequest' - ForwardBankAccount: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ForwardBankAccountRequest' - UpdateBankAccountMetadata: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateBankAccountMetadataRequest' - BankAccount: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/BankAccountRequest' - UpdateTransferInitiationStatus: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateTransferInitiationStatusRequest' - ReverseTransferInitiation: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ReverseTransferInitiationRequest' - Transfer: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TransferRequest' - schemas: - CursorBase: - type: object - required: - - pageSize - - hasMore - - data - properties: - pageSize: - type: integer - format: int64 - minimum: 1 - maximum: 1000 - example: 15 - hasMore: - type: boolean - example: false - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - next: - type: string - example: '' - PaymentsCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/Payment' - PoolsCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/Pool' - TransferInitiationsCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/TransferInitiation' - BankAccountsCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/BankAccount' - AccountsCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/Account' - BalancesCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/AccountBalance' - TasksCursor: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/CursorBase' - - type: object - required: - - data - properties: - data: - type: array - items: - oneOf: - - $ref: '#/components/schemas/TaskStripe' - - $ref: '#/components/schemas/TaskWise' - - $ref: '#/components/schemas/TaskCurrencyCloud' - - $ref: '#/components/schemas/TaskDummyPay' - - $ref: '#/components/schemas/TaskModulr' - - $ref: '#/components/schemas/TaskBankingCircle' - - $ref: '#/components/schemas/TaskMangoPay' - - $ref: '#/components/schemas/TaskMoneycorp' - ConnectorConfigResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/ConnectorConfig' - PaymentResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Payment' - PoolBalancesResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/PoolBalances' - PoolResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Pool' - TransferInitiationResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/TransferInitiation' - AccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Account' - ConnectorResponse: - type: object - required: - - data - properties: - data: - type: object - required: - - connectorID - properties: - connectorID: - type: string - ConnectorsResponse: - type: object - required: - - data - properties: - data: - type: array - items: - type: object - required: - - provider - - name - - connectorID - properties: - provider: - $ref: '#/components/schemas/Connector' - name: - type: string - connectorID: - type: string - enabled: - type: boolean - BankAccountResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/BankAccount' - ConnectorsConfigsResponse: - type: object - required: - - data - properties: - data: - type: object - required: - - connector - properties: - connector: - type: object - required: - - key - properties: - key: - type: object - required: - - dataType - - required - properties: - dataType: - type: string - required: - type: boolean - TaskResponse: - type: object - required: - - data - properties: - data: - oneOf: - - $ref: '#/components/schemas/TaskStripe' - - $ref: '#/components/schemas/TaskWise' - - $ref: '#/components/schemas/TaskCurrencyCloud' - - $ref: '#/components/schemas/TaskDummyPay' - - $ref: '#/components/schemas/TaskModulr' - - $ref: '#/components/schemas/TaskBankingCircle' - - $ref: '#/components/schemas/TaskMangoPay' - - $ref: '#/components/schemas/TaskMoneycorp' - TransferResponse: - type: object - properties: - id: - type: string - Connector: - type: string - enum: - - STRIPE - - DUMMY-PAY - - WISE - - MODULR - - CURRENCY-CLOUD - - BANKING-CIRCLE - - MANGOPAY - - MONEYCORP - - ATLAR - - ADYEN - - GENERIC - TransferInitiationStatus: - type: string - enum: - - WAITING_FOR_VALIDATION - - PROCESSING - - PROCESSED - - FAILED - - REJECTED - - VALIDATED - - ASK_RETRIED - - ASK_REVERSED - - REVERSE_PROCESSING - - REVERSE_FAILED - - PARTIALLY_REVERSED - - REVERSED - ConnectorConfig: - anyOf: - - $ref: '#/components/schemas/StripeConfig' - - $ref: '#/components/schemas/DummyPayConfig' - - $ref: '#/components/schemas/WiseConfig' - - $ref: '#/components/schemas/ModulrConfig' - - $ref: '#/components/schemas/CurrencyCloudConfig' - - $ref: '#/components/schemas/BankingCircleConfig' - - $ref: '#/components/schemas/MangoPayConfig' - - $ref: '#/components/schemas/MoneycorpConfig' - - $ref: '#/components/schemas/AtlarConfig' - - $ref: '#/components/schemas/AdyenConfig' - - $ref: '#/components/schemas/GenericConfig' - StripeConfig: - type: object - required: - - name - - apiKey - properties: - name: - type: string - example: My Stripe Account - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from Stripe API. - default: 120s - apiKey: - type: string - example: XXX - pageSize: - type: integer - format: int64 - minimum: 0 - description: | - Number of BalanceTransaction to fetch at each polling interval. - default: 10 - example: 50 - DummyPayConfig: - type: object - required: - - name - - directory - properties: - name: - type: string - example: My DummyPay Account - filePollingPeriod: - type: string - example: 60s - description: >- - The frequency at which the connector will try to fetch new payment - objects from the directory - default: 10s - directory: - type: string - example: /tmp/dummypay - prefixFileToIngest: - type: string - numberOfAccountsPreGenerated: - type: integer - format: int64 - numberOfPaymentsPreGenerated: - type: integer - format: int64 - WiseConfig: - type: object - required: - - name - - apiKey - properties: - name: - type: string - example: My Wise Account - apiKey: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from Wise API. - default: 120s - ModulrConfig: - type: object - required: - - name - - apiKey - - apiSecret - properties: - name: - type: string - example: My Modulr Account - apiKey: - type: string - example: XXX - apiSecret: - type: string - example: XXX - endpoint: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from Modulr API. - default: 120s - BankingCircleConfig: - type: object - required: - - name - - username - - password - - endpoint - - authorizationEndpoint - - userCertificate - - userCertificateKey - properties: - name: - type: string - example: My Banking Circle Account - username: - type: string - example: XXX - password: - type: string - example: XXX - endpoint: - type: string - example: XXX - authorizationEndpoint: - type: string - example: XXX - userCertificate: - type: string - example: XXX - userCertificateKey: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from Banking Circle API. - default: 120s - GenericConfig: - type: object - required: - - name - - apiKey - - endpoint - properties: - name: - type: string - example: My Generic Account - apiKey: - type: string - example: XXX - endpoint: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from the API. - default: 120s - MangoPayConfig: - type: object - required: - - name - - clientID - - apiKey - - endpoint - properties: - name: - type: string - example: My MangoPay Account - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from MangoPay API. - default: 120s - clientID: - type: string - example: XXX - apiKey: - type: string - example: XXX - endpoint: - type: string - example: XXX - MoneycorpConfig: - type: object - required: - - name - - clientID - - apiKey - - endpoint - properties: - name: - type: string - example: My Moneycorp Account - clientID: - type: string - example: XXX - apiKey: - type: string - example: XXX - endpoint: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from MoneyCorp API. - default: 120s - CurrencyCloudConfig: - type: object - required: - - name - - apiKey - - loginID - properties: - name: - type: string - example: My CurrencyCloud Account - apiKey: - type: string - example: XXX - loginID: - type: string - example: XXX - description: Username of the API Key holder - pollingPeriod: - type: string - example: 60s - description: The frequency at which the connector will fetch transactions - default: 120s - endpoint: - type: string - example: XXX - description: >- - The endpoint to use for the API. Defaults to - https://devapi.currencycloud.com - AdyenConfig: - type: object - required: - - name - - apiKey - - hmacKey - properties: - name: - type: string - example: My Adyen Account - apiKey: - type: string - example: XXX - hmacKey: - type: string - example: XXX - liveEndpointPrefix: - type: string - example: XXX - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector will try to fetch new - BalanceTransaction objects from Adyen API. - default: 120s - AtlarConfig: - type: object - required: - - name - - accessKey - - secret - properties: - name: - type: string - example: My Atlar Account - baseUrl: - type: string - example: https://api.example.com - default: https://api.atlar.com - description: > - The base URL the client uses for making requests towards the Atlar - API. - pollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector tries to fetch new Transaction - objects from the Atlar API. - default: 120s - transferInitiationStatusPollingPeriod: - type: string - example: 60s - description: > - The frequency at which the connector tries to fetch the status of - payment initiations from the Atlar API. - default: 120s - accessKey: - type: string - example: XXX - description: > - The access key used by the connector for authorizing requests to the - Atlar API. - - You can obtain it along with the associated secret from the Atlar - dashboard. - secret: - type: string - example: XXX - description: > - The secret used by the connector for authorizing requests to the - Atlar API. - - You can obtain it along with the associated access key from the - Atlar dashboard. - pageSize: - type: integer - format: int64 - minimum: 0 - description: | - Number of items to fetch when querying paginated APIs. - default: 25 - example: 50 - TransferInitiation: - type: object - required: - - id - - reference - - createdAt - - scheduledAt - - description - - sourceAccountID - - destinationAccountID - - connectorID - - type - - amount - - initialAmount - - asset - - status - - error - properties: - id: - type: string - example: XXX - reference: - type: string - createdAt: - type: string - format: date-time - scheduledAt: - type: string - format: date-time - description: - type: string - sourceAccountID: - type: string - destinationAccountID: - type: string - connectorID: - type: string - type: - type: string - enum: - - TRANSFER - - PAYOUT - amount: - type: integer - format: bigint - initialAmount: - type: integer - format: bigint - asset: - type: string - example: USD - status: - $ref: '#/components/schemas/TransferInitiationStatus' - error: - type: string - metadata: - type: object - additionalProperties: - type: string - nullable: true - relatedPayments: - type: array - items: - $ref: '#/components/schemas/TransferInitiationPayments' - relatedAdjustments: - type: array - items: - $ref: '#/components/schemas/TransferInitiationAdjusments' - TransferInitiationAdjusments: - type: object - required: - - adjustmentID - - createdAt - - status - - error - properties: - adjustmentID: - type: string - createdAt: - type: string - format: date-time - status: - $ref: '#/components/schemas/TransferInitiationStatus' - error: - type: string - metadata: - type: object - additionalProperties: - type: string - nullable: true - TransferInitiationPayments: - type: object - required: - - paymentID - - createdAt - - status - - error - properties: - paymentID: - type: string - createdAt: - type: string - format: date-time - status: - $ref: '#/components/schemas/TransferInitiationStatus' - error: - type: string - BankAccount: - type: object - required: - - id - - name - - createdAt - - country - - connectorID - properties: - id: - type: string - name: - type: string - createdAt: - type: string - format: date-time - country: - type: string - connectorID: - type: string - accountID: - type: string - provider: - type: string - iban: - type: string - accountNumber: - type: string - swiftBicCode: - type: string - relatedAccounts: - type: array - items: - $ref: '#/components/schemas/BankAccountRelatedAccounts' - metadata: - $ref: '#/components/schemas/BankAccountMetadata' - BankAccountRelatedAccounts: - type: object - required: - - id - - createdAt - - connectorID - - provider - - accountID - properties: - id: - type: string - createdAt: - type: string - format: date-time - provider: - type: string - connectorID: - type: string - accountID: - type: string - BankAccountMetadata: - type: object - additionalProperties: - type: string - nullable: true - Payment: - type: object - required: - - id - - reference - - connectorID - - sourceAccountID - - destinationAccountID - - type - - status - - initialAmount - - amount - - scheme - - asset - - createdAt - - raw - - adjustments - - metadata - properties: - id: - type: string - example: XXX - reference: - type: string - sourceAccountID: - type: string - destinationAccountID: - type: string - connectorID: - type: string - provider: - $ref: '#/components/schemas/Connector' - type: - $ref: '#/components/schemas/PaymentType' - status: - $ref: '#/components/schemas/PaymentStatus' - initialAmount: - type: integer - format: bigint - minimum: 0 - example: 100 - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - scheme: - $ref: '#/components/schemas/PaymentScheme' - asset: - type: string - example: USD - createdAt: - type: string - format: date-time - raw: - type: object - nullable: true - adjustments: - type: array - items: - $ref: '#/components/schemas/PaymentAdjustment' - metadata: - $ref: '#/components/schemas/PaymentMetadata' - PaymentAdjustment: - type: object - required: - - reference - - createdAt - - amount - - status - - raw - properties: - reference: - type: string - createdAt: - type: string - format: date-time - status: - $ref: '#/components/schemas/PaymentStatus' - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - raw: - type: object - PaymentMetadata: - type: object - additionalProperties: - type: string - nullable: true - AccountMetadata: - type: object - additionalProperties: - type: string - nullable: true - Pool: - type: object - required: - - id - - name - - accounts - properties: - id: - type: string - name: - type: string - accounts: - type: array - items: - type: string - PoolBalances: - type: object - required: - - balances - properties: - balances: - type: array - items: - $ref: '#/components/schemas/PoolBalance' - PoolBalance: - type: object - required: - - amount - - asset - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: USD - Account: - type: object - required: - - id - - createdAt - - reference - - connectorID - - defaultCurrency - - defaultAsset - - accountName - - type - - metadata - - raw - properties: - id: - type: string - createdAt: - type: string - format: date-time - reference: - type: string - connectorID: - type: string - provider: - type: string - defaultCurrency: - type: string - deprecated: true - defaultAsset: - type: string - accountName: - type: string - type: - $ref: '#/components/schemas/AccountType' - pools: - type: array - items: - type: string - metadata: - $ref: '#/components/schemas/AccountMetadata' - raw: - type: object - nullable: true - AccountBalance: - type: object - required: - - accountId - - createdAt - - lastUpdatedAt - - currency - - asset - - balance - properties: - accountId: - type: string - createdAt: - type: string - format: date-time - lastUpdatedAt: - type: string - format: date-time - currency: - type: string - deprecated: true - asset: - type: string - balance: - type: integer - format: bigint - TaskBase: - type: object - required: - - id - - connectorID - - createdAt - - updatedAt - - descriptor - - status - - state - properties: - id: - type: string - format: uuid - connectorID: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - status: - $ref: '#/components/schemas/PaymentStatus' - state: - type: object - error: - type: string - TaskStripe: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - required: - - name - - account - properties: - name: - type: string - main: - type: boolean - account: - type: string - TaskWise: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - profileID: - type: integer - format: int64 - minimum: 0 - TaskModulr: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - accountID: - type: string - TaskDummyPay: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - fileName: - type: string - TaskCurrencyCloud: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - TaskBankingCircle: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - TaskMangoPay: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - userID: - type: string - TaskMoneycorp: - allOf: - - $ref: '#/components/schemas/TaskBase' - - type: object - required: - - descriptor - properties: - descriptor: - type: object - properties: - name: - type: string - key: - type: string - accountID: - type: string - TransferRequest: - type: object - required: - - asset - - amount - - destination - properties: - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - asset: - type: string - example: USD - destination: - type: string - example: acct_1Gqj58KZcSIg2N2q - source: - type: string - example: acct_1Gqj58KZcSIg2N2q - ForwardBankAccountRequest: - type: object - required: - - connectorID - properties: - connectorID: - type: string - UpdateBankAccountMetadataRequest: - type: object - required: - - metadata - properties: - metadata: - $ref: '#/components/schemas/BankAccountMetadata' - BankAccountRequest: - type: object - required: - - country - - connectorID - - name - properties: - country: - type: string - example: GB - connectorID: - type: string - name: - type: string - example: My account - accountNumber: - type: string - iban: - type: string - swiftBicCode: - type: string - metadata: - $ref: '#/components/schemas/BankAccountMetadata' - AddAccountToPoolRequest: - type: object - required: - - accountID - properties: - accountID: - type: string - PoolRequest: - type: object - required: - - name - - accountIDs - properties: - name: - type: string - accountIDs: - type: array - items: - type: string - AccountRequest: - type: object - required: - - reference - - connectorID - - createdAt - - type - properties: - reference: - type: string - connectorID: - type: string - createdAt: - type: string - format: date-time - type: - $ref: '#/components/schemas/AccountType' - defaultAsset: - type: string - accountName: - type: string - metadata: - $ref: '#/components/schemas/AccountMetadata' - PaymentRequest: - type: object - required: - - reference - - connectorID - - createdAt - - amount - - type - - status - - scheme - - asset - properties: - reference: - type: string - connectorID: - type: string - createdAt: - type: string - format: date-time - amount: - type: integer - format: bigint - minimum: 0 - example: 100 - type: - $ref: '#/components/schemas/PaymentType' - status: - $ref: '#/components/schemas/PaymentStatus' - scheme: - $ref: '#/components/schemas/PaymentScheme' - asset: - type: string - example: USD - sourceAccountID: - type: string - destinationAccountID: - type: string - TransferInitiationRequest: - type: object - required: - - reference - - scheduledAt - - description - - sourceAccountID - - destinationAccountID - - type - - amount - - asset - - validated - properties: - reference: - type: string - example: XXX - scheduledAt: - type: string - format: date-time - description: - type: string - sourceAccountID: - type: string - destinationAccountID: - type: string - connectorID: - type: string - provider: - $ref: '#/components/schemas/Connector' - type: - type: string - enum: - - TRANSFER - - PAYOUT - amount: - type: integer - format: bigint - asset: - type: string - example: USD - validated: - type: boolean - metadata: - type: object - additionalProperties: - type: string - nullable: true - ReverseTransferInitiationRequest: - type: object - required: - - reference - - description - - amount - - asset - - metadata - properties: - reference: - type: string - example: XXX - description: - type: string - amount: - type: integer - format: bigint - asset: - type: string - example: USD - metadata: - type: object - additionalProperties: - type: string - nullable: true - UpdateTransferInitiationStatusRequest: - type: object - required: - - status - properties: - status: - type: string - enum: - - WAITING_FOR_VALIDATION - - PROCESSING - - PROCESSED - - FAILED - - REJECTED - - VALIDATED - AccountType: - type: string - enum: - - UNKNOWN - - INTERNAL - - EXTERNAL - PaymentType: - type: string - enum: - - PAY-IN - - PAYOUT - - TRANSFER - - OTHER - PaymentScheme: - type: string - enum: - - unknown - - other - - visa - - mastercard - - amex - - diners - - discover - - jcb - - unionpay - - alipay - - cup - - sepa debit - - sepa credit - - sepa - - apple pay - - google pay - - doku - - dragonpay - - maestro - - molpay - - a2a - - ach debit - - ach - - rtp - PaymentStatus: - type: string - enum: - - PENDING - - SUCCEEDED - - CANCELLED - - FAILED - - EXPIRED - - REFUNDED - - REFUNDED_FAILURE - - DISPUTE - - DISPUTE_WON - - DISPUTE_LOST - - OTHER - ServerInfo: - type: object - required: - - version - properties: - version: - type: string - PaymentsErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/PaymentsErrorsEnum' - errorMessage: - type: string - example: '[VALIDATION] missing reference' - PaymentsErrorsEnum: - type: string - enum: - - INTERNAL - - VALIDATION - - NOT_FOUND - example: VALIDATION diff --git a/components/payments/pkg/events/event.go b/components/payments/pkg/events/event.go deleted file mode 100644 index 0fc3595553..0000000000 --- a/components/payments/pkg/events/event.go +++ /dev/null @@ -1,18 +0,0 @@ -package events - -const ( - TopicPayments = "payments" - - EventVersion = "v1" - EventApp = "payments" - - EventTypeSavedPool = "SAVED_POOL" - EventTypeDeletePool = "DELETED_POOL" - EventTypeSavedPayments = "SAVED_PAYMENT" - EventTypeSavedAccounts = "SAVED_ACCOUNT" - EventTypeSavedBalances = "SAVED_BALANCE" - EventTypeSavedBankAccount = "SAVED_BANK_ACCOUNT" - EventTypeSavedTransferInitiation = "SAVED_TRANSFER_INITIATION" - EventTypeDeleteTransferInitiation = "DELETED_TRANSFER_INITIATION" - EventTypeConnectorReset = "CONNECTOR_RESET" -) diff --git a/components/payments/scratch.Dockerfile b/components/payments/scratch.Dockerfile deleted file mode 100644 index 09ab45d934..0000000000 --- a/components/payments/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY payments /usr/bin/payments -ENV OTEL_SERVICE_NAME payments -ENTRYPOINT ["/usr/bin/payments"] -CMD ["server"] diff --git a/ee/Earthfile b/ee/Earthfile index 9cb8b0e7e7..a154eaa1aa 100644 --- a/ee/Earthfile +++ b/ee/Earthfile @@ -17,11 +17,7 @@ run: LOCALLY ARG --required TARGET BUILD ./agent+$TARGET - BUILD ./auth+$TARGET - BUILD ./gateway+$TARGET BUILD ./orchestration+$TARGET BUILD ./reconciliation+$TARGET - BUILD ./search+$TARGET - BUILD ./stargate+$TARGET BUILD ./wallets+$TARGET - BUILD ./webhooks+$TARGET \ No newline at end of file + \ No newline at end of file diff --git a/ee/agent/go.mod b/ee/agent/go.mod index 293fcbc90a..ada86304cd 100644 --- a/ee/agent/go.mod +++ b/ee/agent/go.mod @@ -118,7 +118,6 @@ require ( replace ( github.com/formancehq/membership-api/client => ./client github.com/formancehq/operator => ./../../components/operator - github.com/formancehq/payments => ../../components/payments github.com/zitadel/oidc/v2 => github.com/formancehq/oidc/v2 v2.0.0-20230524073911-09bdd1dca291 k8s.io/client-go v0.26.0 => k8s.io/client-go v0.25.4 ) diff --git a/ee/auth/.gitignore b/ee/auth/.gitignore deleted file mode 100644 index 6a0715ef75..0000000000 --- a/ee/auth/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -vendor -.idea -coverage.* -auth diff --git a/ee/auth/.goreleaser.yml b/ee/auth/.goreleaser.yml deleted file mode 100644 index b7ebe1b7a0..0000000000 --- a/ee/auth/.goreleaser.yml +++ /dev/null @@ -1,38 +0,0 @@ -project_name: auth -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: auth - id: auth - ldflags: - - -X github.com/formancehq/auth/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/auth/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/auth/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - auth - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) \ No newline at end of file diff --git a/ee/auth/Earthfile b/ee/auth/Earthfile deleted file mode 100644 index 68824c9f5b..0000000000 --- a/ee/auth/Earthfile +++ /dev/null @@ -1,80 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT .. AS ee - -FROM core+base-image - -sources: - WORKDIR src - WORKDIR /src/ee/auth - COPY go.* . - COPY --dir cmd pkg . - COPY main.go . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/auth - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/auth"] - CMD ["serve"] - COPY (+compile/main) /bin/auth - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=auth --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/auth - WITH DOCKER --pull=postgres:15-alpine - DO --pass-args core+GO_TESTS - END - -lint: - FROM core+builder-image - COPY (+sources/*) /src - COPY --pass-args +tidy/go.* . - WORKDIR /src/ee/auth - DO --pass-args stack+GO_LINT - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT main.go AS LOCAL main.go - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"auth\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=auth - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - -openapi: - COPY ./openapi.yaml . - SAVE ARTIFACT ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/ee/auth - DO --pass-args stack+GO_TIDY - -release: - BUILD --pass-args stack+goreleaser --path=ee/auth \ No newline at end of file diff --git a/ee/auth/README.md b/ee/auth/README.md deleted file mode 100644 index f72184a8e0..0000000000 --- a/ee/auth/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Auth server - -## Generate client code - -``` -task generate-client -``` - -## Tests - -``` -task tests -``` - -## Lint - -``` -task lint -``` - -## Run the demo - -Execute command : -```bash -docker compose up -``` -will run all required components. - -Now, you can open http://localhost:3000 diff --git a/ee/auth/build.Dockerfile b/ee/auth/build.Dockerfile deleted file mode 100644 index 3932d23ae3..0000000000 --- a/ee/auth/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY auth /usr/bin/auth -ENV OTEL_SERVICE_NAME auth -ENTRYPOINT ["/usr/bin/auth"] -CMD ["serve"] diff --git a/ee/auth/cmd/root.go b/ee/auth/cmd/root.go deleted file mode 100644 index 3418964a49..0000000000 --- a/ee/auth/cmd/root.go +++ /dev/null @@ -1,37 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/auth/pkg/storage/sqlstorage" - "github.com/formancehq/go-libs/bun/bunmigrate" - "github.com/uptrace/bun" - - "github.com/spf13/cobra" -) - -var ( - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func NewRootCommand() *cobra.Command { - cmd := &cobra.Command{} - - cobra.EnableTraverseRunHooks = true - - cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - cmd.AddCommand( - newServeCommand(), - newVersionCommand(), - bunmigrate.NewDefaultCommand(func(cmd *cobra.Command, args []string, db *bun.DB) error { - return sqlstorage.Migrate(cmd.Context(), db) - })) - - return cmd -} - -func Execute() { - service.Execute(NewRootCommand()) -} diff --git a/ee/auth/cmd/serve.go b/ee/auth/cmd/serve.go deleted file mode 100644 index d2c428100b..0000000000 --- a/ee/auth/cmd/serve.go +++ /dev/null @@ -1,195 +0,0 @@ -package cmd - -import ( - "context" - "crypto/x509" - "encoding/pem" - "fmt" - "net/http" - "os" - - "gopkg.in/yaml.v3" - - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/licence" - - "github.com/formancehq/go-libs/otlp" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/api" - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/formancehq/auth/pkg/oidc" - "github.com/formancehq/auth/pkg/storage/sqlstorage" - sharedapi "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/service" - "github.com/pkg/errors" - "github.com/spf13/cobra" - zLogging "github.com/zitadel/logging" - "go.uber.org/fx" -) - -const ( - serviceName = "auth" - - delegatedClientIDFlag = "delegated-client-id" - delegatedClientSecretFlag = "delegated-client-secret" - delegatedIssuerFlag = "delegated-issuer" - baseUrlFlag = "base-url" - listenFlag = "listen" - signingKeyFlag = "signing-key" - configFlag = "config" - - defaultSigningKey = ` ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAth3atoCXldJgHH9EWnZQMvw5O+vVNKMcvrllEGQsLxvIA5xy -YPnFt2xU7k1dcN5ViBqPiigVHZNeyyHcdVclg26zqjEwYUqH+OPiRFeBn0SwOG+d -ZLpOIJdKt7OjmUG0xN9egq81dbPVPBPckuWqB9XMWmM+dtqydBX4lekj+Q1hFn5E -WXXuAs9aLIc8DzPz8B+oqwLKZ6k6kC5vpj+EaBt8ExVywrWftkWewGWRO7fLw0Fj -7hamaA1ZTYEqCN+MLDLEd6qmtC2cdgVhZM0RG2OnTiq5lGzNFmLXGOsquc35HSQj -OQqcLL+e/72K3giJ1YCqYWAIIJcc/kNKU8HtpwIDAQABAoIBAFY+dSEQbLjq09Er -A/fDJ9+9Sm1yFZnD1Q0NRysoBTSZ93KeWBxMrLFcgCwKP0IASIkX6voGWVmUPMP9 -2SVIi99eQX9LpBmu7g2T/cdXmW8PXFSdpu/Yur78ZsnwLH2bfDvvfBZvWuXOsCCv -VznJwWfMe+YiMaafkvsenIaBziNWwUeVGHCWl5f++KGGbWFZjhkRZyjKWfMYflig -EG5e+WaXagCjTah5pUkmvLj3jmB1iGA/Askm8S5QyTt6Z+SIEk+i5T3qCiLFNvzp -7OeSyfbmWWzBYTiSvEoHhaHfdeicUyOpRthc33bb7LnfIWDG3Z+WE0o6U1nR8o7U -t5dsj2ECgYEA7SEuBpd/3wdNVLQSI/RHKKO3sdlymh7yRFf7OAn/UxnSJbSNx4y4 -GAEdJD9KwSQlyekLITF+xc0IuyFHOmvuzp1+/LxK/QTY4dcdlwl/r1kmwBbTeR0e -yl9RtulHXmP+Ss/PZgwR081Lk7zlRkh1busyAOmCE4mJW/IvNBze0dsCgYEAxJvy -PcbaLVk497U9cUGznsSbbsyq7JGLkBgTu3eQ/yRgoE7pvagF7dV1gQGuCYjOaYml -U4d95FLPoiE+CE0g2uyouFEsD1UhggTADP33BidUKUcF1ub9VVNcWs4I5LeWPY/X -5vcpOCAkmRZWT5rieAECdIsfRTnePVyn2L7amyUCgYEAqsZAfWLSJm791Eiy383n -CW+OtbjiffhXhbzPIbaheNmZrKnxiYrgcfkrYZVrYtmDlXwOFeOtZwqYhRwcTgi5 -PXfTonSAlOPOxibEGqgumrvb2m8V8Z11NU2cbdxnF6Vv17T9qoJ6vEyXZ1iczhcU -68LaiimhEiz1DZDHSgKYvg0CgYEAjVZyQXjXVWxjqKdQ4T9TKhq6hl95rJFA3DiC -zuy4fsKe9/9ixyWoBX7DdxdHDrGbeYErKa4okV/6xdnR51PS/67L55zq6KbRbM+P -ZIeZ8oGJXhchmoj5q0I/DUQ6Xnmf9ueWVQJvTlrFFIxbReTZU12ebzuoIjLkkgYu -34DsVEUCgYEAtHm/aO7/2UJT40PMO+VDvBCEixPtt6j72fLaW8btgVRAnhp9qaWX -Cv6TRZPe2y6Bbgg4Q3FuF0DMqx6ongFKQAWo3DkqNFCGRgjJMQ9JbcfOnGySq4U+ -EL/wy5C80pa3jahniqVgO5L6zz0ZLtRIRE7aCtCIu826gctJ1+ShIso= ------END RSA PRIVATE KEY----- -` -) - -func otlpHttpClientModule(debug bool) fx.Option { - return fx.Provide(func() *http.Client { - return &http.Client{ - Transport: otlp.NewRoundTripper(http.DefaultTransport, debug, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { - str := fmt.Sprintf("%s %s", r.Method, r.URL.Path) - if len(r.URL.Query()) == 0 { - return str - } - - return fmt.Sprintf("%s?%s", str, r.URL.Query().Encode()) - })), - } - }) -} - -func newServeCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - RunE: func(cmd *cobra.Command, args []string) error { - baseUrl, _ := cmd.Flags().GetString(baseUrlFlag) - if baseUrl == "" { - return errors.New("base url must be defined") - } - - signingKey, _ := cmd.Flags().GetString(signingKeyFlag) - if signingKey == "" { - return errors.New("signing key must be defined") - } - - block, _ := pem.Decode([]byte(signingKey)) - if block == nil { - return errors.New("invalid signing key, cannot parse as PEM") - } - - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return err - } - - type configuration struct { - Clients []auth.StaticClient `json:"clients" yaml:"clients"` - } - o := configuration{} - - config, _ := cmd.Flags().GetString(configFlag) - if config != "" { - configFile, err := os.Open(config) - if err != nil { - return err - } - if err := yaml.NewDecoder(configFile).Decode(&o); err != nil { - return err - } - } - - zLogging.SetOutput(cmd.OutOrStdout()) - - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return err - } - - listen, _ := cmd.Flags().GetString(listenFlag) - options := []fx.Option{ - otlpHttpClientModule(service.IsDebug(cmd)), - fx.Supply(fx.Annotate(cmd.Context(), fx.As(new(context.Context)))), - sqlstorage.Module(*connectionOptions, key, service.IsDebug(cmd), o.Clients...), - oidc.Module(key, baseUrl, o.Clients...), - api.Module(listen, baseUrl, sharedapi.ServiceInfo{ - Version: Version, - Debug: service.IsDebug(cmd), - }, service.IsDebug(cmd)), - } - - delegatedIssuer, _ := cmd.Flags().GetString(delegatedIssuerFlag) - if delegatedIssuer != "" { - delegatedClientID, _ := cmd.Flags().GetString(delegatedClientIDFlag) - if delegatedClientID == "" { - return errors.New("delegated client id must be defined") - } - - delegatedClientSecret, _ := cmd.Flags().GetString(delegatedClientSecretFlag) - if delegatedClientSecret == "" { - return errors.New("delegated client secret must be defined") - } - - options = append(options, - fx.Supply(delegatedauth.Config{ - Issuer: delegatedIssuer, - ClientID: delegatedClientID, - ClientSecret: delegatedClientSecret, - RedirectURL: fmt.Sprintf("%s/authorize/callback", baseUrl), - }), - delegatedauth.Module(), - licence.FXModuleFromFlags(cmd, serviceName), - ) - } - - options = append(options, otlptraces.FXModuleFromFlags(cmd)) - - return service.New(cmd.OutOrStdout(), options...).Run(cmd) - }, - } - - cmd.Flags().String(delegatedIssuerFlag, "", "Delegated OIDC issuer") - cmd.Flags().String(delegatedClientIDFlag, "", "Delegated OIDC client id") - cmd.Flags().String(delegatedClientSecretFlag, "", "Delegated OIDC client secret") - cmd.Flags().String(baseUrlFlag, "http://localhost:8080", "Base service url") - cmd.Flags().String(signingKeyFlag, defaultSigningKey, "Signing key") - cmd.Flags().String(listenFlag, ":8080", "Listening address") - cmd.Flags().String(configFlag, "", "Config file name without extension") - - service.AddFlags(cmd.Flags()) - licence.AddFlags(cmd.Flags()) - otlptraces.AddFlags(cmd.Flags()) - bunconnect.AddFlags(cmd.Flags()) - iam.AddFlags(cmd.Flags()) - - return cmd -} diff --git a/ee/auth/cmd/version.go b/ee/auth/cmd/version.go deleted file mode 100644 index 58c77193d3..0000000000 --- a/ee/auth/cmd/version.go +++ /dev/null @@ -1,19 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func newVersionCommand() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Get version", - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Version: %s \n", Version) - fmt.Printf("Date: %s \n", BuildDate) - fmt.Printf("Commit: %s \n", Commit) - }, - } -} diff --git a/ee/auth/config.yaml b/ee/auth/config.yaml deleted file mode 100644 index 3380d099e1..0000000000 --- a/ee/auth/config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -clients: - - id: demo - public: true - redirectUris: - - http://localhost:3000/auth-callback - - https://oauth.pstmn.io/v1/callback - name: demo - postLogoutRedirectUris: - - http://localhost:3000/ diff --git a/ee/auth/go.mod b/ee/auth/go.mod deleted file mode 100644 index 10cd33372a..0000000000 --- a/ee/auth/go.mod +++ /dev/null @@ -1,137 +0,0 @@ -module github.com/formancehq/auth - -go 1.22.0 - -toolchain go1.22.7 - -require ( - github.com/formancehq/go-libs v1.7.1 - github.com/go-chi/chi/v5 v5.1.0 - github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/uuid v1.6.0 - github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 - github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 - github.com/uptrace/bun v1.2.3 - github.com/zitadel/logging v0.3.4 - github.com/zitadel/oidc/v2 v2.12.2 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/fx v1.22.2 - golang.org/x/oauth2 v0.23.0 - golang.org/x/text v0.18.0 - gopkg.in/go-jose/go-jose.v2 v2.6.3 - gopkg.in/yaml.v3 v3.0.1 -) - -require ( - github.com/muhlemmer/httpforwarded v0.1.0 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun/dialect/pgdialect v1.2.3 // indirect - github.com/uptrace/bun/extra/bunotel v1.2.3 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect -) - -require ( - dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/ThreeDotsLabs/watermill v1.3.7 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/docker v27.3.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx v1.2.30 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/muhlemmer/gu v0.3.1 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/ory/dockertest/v3 v3.11.0 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/rs/cors v1.11.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/dburl v0.23.2 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/mock v0.4.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/tools v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) diff --git a/ee/auth/go.sum b/ee/auth/go.sum deleted file mode 100644 index 51c6f47f7d..0000000000 --- a/ee/auth/go.sum +++ /dev/null @@ -1,353 +0,0 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= -github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= -github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= -github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= -github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= -github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= -github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 h1:TQMyrpijtkFyXpNI3rY5hsZQZw+paiH+BfAlsb81HBY= -github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282/go.mod h1:rW25Kyd08Wdn3UVn0YBsDTSvReu0jqpmJKzxITPSjks= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc= -github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zitadel/logging v0.3.4 h1:9hZsTjMMTE3X2LUi0xcF9Q9EdLo+FAezeu52ireBbHM= -github.com/zitadel/logging v0.3.4/go.mod h1:aPpLQhE+v6ocNK0TWrBrd363hZ95KcI17Q1ixAQwZF0= -github.com/zitadel/oidc/v2 v2.12.2 h1:3kpckg4rurgw7w7aLJrq7yvRxb2pkNOtD08RH42vPEs= -github.com/zitadel/oidc/v2 v2.12.2/go.mod h1:vhP26g1g4YVntcTi0amMYW3tJuid70nxqxf+kb6XKgg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/ee/auth/main.go b/ee/auth/main.go deleted file mode 100644 index 097ec58597..0000000000 --- a/ee/auth/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/formancehq/auth/cmd" - -func main() { - cmd.Execute() -} diff --git a/ee/auth/openapi.yaml b/ee/auth/openapi.yaml deleted file mode 100644 index 9d6090af13..0000000000 --- a/ee/auth/openapi.yaml +++ /dev/null @@ -1,404 +0,0 @@ -openapi: 3.0.3 -info: - title: Auth API - contact: {} - version: AUTH_VERSION -paths: - /.well-known/openid-configuration: - get: - summary: Retrieve OpenID connect well-knowns. - operationId: getOIDCWellKnowns - tags: - - auth.v1 - responses: - '200': - description: > - OpenID provider configuration. - - See - https://swagger.io/docs/specification/authentication/openid-connect-discovery/ - for details - security: - - Authorization: - - auth:read - /_info: - get: - summary: Get server info - operationId: getServerInfo - tags: - - auth.v1 - responses: - '200': - description: Server information - content: - application/json: - schema: - $ref: '#/components/schemas/ServerInfo' - security: - - Authorization: - - auth:read - /clients: - get: - summary: List clients - tags: - - auth.v1 - operationId: listClients - responses: - '200': - description: List of clients - content: - application/json: - schema: - $ref: '#/components/schemas/ListClientsResponse' - security: - - Authorization: - - auth:read - post: - summary: Create client - tags: - - auth.v1 - operationId: createClient - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/CreateClientRequest' - responses: - '201': - description: Client created - content: - application/json: - schema: - $ref: '#/components/schemas/CreateClientResponse' - security: - - Authorization: - - auth:write - /clients/{clientId}: - get: - summary: Read client - tags: - - auth.v1 - operationId: readClient - parameters: - - description: Client ID - in: path - name: clientId - required: true - schema: - type: string - responses: - '200': - description: Retrieved client - content: - application/json: - schema: - $ref: '#/components/schemas/ReadClientResponse' - security: - - Authorization: - - auth:read - put: - summary: Update client - tags: - - auth.v1 - operationId: updateClient - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateClientRequest' - parameters: - - description: Client ID - in: path - name: clientId - required: true - schema: - type: string - responses: - '200': - description: Updated client - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateClientResponse' - security: - - Authorization: - - auth:write - delete: - summary: Delete client - tags: - - auth.v1 - operationId: deleteClient - parameters: - - description: Client ID - in: path - name: clientId - required: true - schema: - type: string - responses: - '204': - description: Client deleted - security: - - Authorization: - - auth:write - /clients/{clientId}/secrets: - post: - summary: Add a secret to a client - tags: - - auth.v1 - operationId: createSecret - parameters: - - description: Client ID - in: path - name: clientId - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/CreateSecretRequest' - responses: - '200': - description: Created secret - content: - application/json: - schema: - $ref: '#/components/schemas/CreateSecretResponse' - security: - - Authorization: - - auth:write - /clients/{clientId}/secrets/{secretId}: - delete: - summary: Delete a secret from a client - tags: - - auth.v1 - operationId: deleteSecret - parameters: - - description: Client ID - in: path - name: clientId - required: true - schema: - type: string - - description: Secret ID - in: path - name: secretId - required: true - schema: - type: string - responses: - '204': - description: Secret deleted - security: - - Authorization: - - auth:write - /users: - get: - summary: List users - tags: - - auth.v1 - description: List users - operationId: listUsers - responses: - '200': - description: List of users - content: - application/json: - schema: - $ref: '#/components/schemas/ListUsersResponse' - security: - - Authorization: - - auth:read - /users/{userId}: - get: - summary: Read user - tags: - - auth.v1 - description: Read user - operationId: readUser - parameters: - - description: User ID - in: path - name: userId - required: true - schema: - type: string - responses: - '200': - description: Retrieved user - content: - application/json: - schema: - $ref: '#/components/schemas/ReadUserResponse' - security: - - Authorization: - - auth:read -components: - schemas: - Metadata: - type: object - additionalProperties: {} - ClientOptions: - type: object - properties: - public: - type: boolean - redirectUris: - type: array - items: - type: string - description: - type: string - name: - type: string - trusted: - type: boolean - postLogoutRedirectUris: - type: array - items: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - scopes: - type: array - items: - type: string - required: - - name - ClientSecret: - type: object - properties: - lastDigits: - type: string - name: - type: string - id: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - required: - - id - - lastDigits - - name - Client: - allOf: - - $ref: '#/components/schemas/ClientOptions' - - type: object - properties: - id: - type: string - secrets: - type: array - items: - $ref: '#/components/schemas/ClientSecret' - required: - - id - ScopeOptions: - type: object - properties: - label: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - required: - - label - Scope: - allOf: - - $ref: '#/components/schemas/ScopeOptions' - - type: object - properties: - id: - type: string - transient: - type: array - items: - type: string - required: - - id - SecretOptions: - type: object - properties: - name: - type: string - metadata: - $ref: '#/components/schemas/Metadata' - required: - - name - Secret: - allOf: - - $ref: '#/components/schemas/SecretOptions' - - type: object - properties: - id: - type: string - lastDigits: - type: string - clear: - type: string - required: - - id - - lastDigits - - clear - User: - type: object - properties: - id: - type: string - example: 3bb03708-312f-48a0-821a-e765837dc2c4 - subject: - type: string - example: Jane Doe - email: - type: string - example: user1@orga1.com - CreateClientRequest: - $ref: '#/components/schemas/ClientOptions' - CreateClientResponse: - type: object - properties: - data: - $ref: '#/components/schemas/Client' - ListClientsResponse: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Client' - UpdateClientRequest: - $ref: '#/components/schemas/ClientOptions' - UpdateClientResponse: - $ref: '#/components/schemas/CreateClientResponse' - ReadClientResponse: - type: object - properties: - data: - $ref: '#/components/schemas/Client' - CreateSecretRequest: - $ref: '#/components/schemas/SecretOptions' - CreateSecretResponse: - type: object - properties: - data: - $ref: '#/components/schemas/Secret' - ReadUserResponse: - type: object - properties: - data: - $ref: '#/components/schemas/User' - ListUsersResponse: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/User' - ServerInfo: - type: object - required: - - version - properties: - version: - type: string diff --git a/ee/auth/pkg/api/authorization/accesstoken.go b/ee/auth/pkg/api/authorization/accesstoken.go deleted file mode 100644 index 9bbfca4d4a..0000000000 --- a/ee/auth/pkg/api/authorization/accesstoken.go +++ /dev/null @@ -1,35 +0,0 @@ -package authorization - -import ( - "net/http" - "strings" - - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" -) - -func verifyAccessToken(r *http.Request, o op.OpenIDProvider) error { - if !strings.HasPrefix(r.URL.String(), "/clients") && - !strings.HasPrefix(r.URL.String(), "/users") { - return nil - } - - authHeader := r.Header.Get("authorization") - if authHeader == "" { - return ErrMissingAuthHeader - } - - if !strings.HasPrefix(authHeader, strings.ToLower(oidc.PrefixBearer)) && - !strings.HasPrefix(authHeader, oidc.PrefixBearer) { - return ErrMalformedAuthHeader - } - - token := strings.TrimPrefix(authHeader, strings.ToLower(oidc.PrefixBearer)) - token = strings.TrimPrefix(token, oidc.PrefixBearer) - - if _, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](r.Context(), token, o.AccessTokenVerifier(r.Context())); err != nil { - return ErrVerifyAuthToken - } - - return nil -} diff --git a/ee/auth/pkg/api/authorization/accesstoken_test.go b/ee/auth/pkg/api/authorization/accesstoken_test.go deleted file mode 100644 index 50c59dcc9a..0000000000 --- a/ee/auth/pkg/api/authorization/accesstoken_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package authorization - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "fmt" - "net" - "net/http" - "testing" - - "github.com/formancehq/go-libs/bun/bundebug" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/bun/bunconnect" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/delegatedauth" - authoidc "github.com/formancehq/auth/pkg/oidc" - "github.com/formancehq/auth/pkg/storage/sqlstorage" - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/require" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" -) - -func TestVerifyAccessToken(t *testing.T) { - t.Parallel() - - mockOIDC, err := mockoidc.Run() - require.NoError(t, err) - defer func() { - require.NoError(t, mockOIDC.Shutdown()) - }() - - // Prepare a tcp connection, listening on :0 to select a random port - l, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - - // Compute server url, it will be the "issuer" of our oidc provider - serverURL := fmt.Sprintf("http://%s", l.Addr().String()) - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - // Construct our storage - postgresDB := srv.NewDatabase(t) - db, err := bunconnect.OpenSQLDB(logging.TestingContext(), bunconnect.ConnectionOptions{ - DatabaseSourceName: postgresDB.ConnString(), - }, hooks...) - require.NoError(t, err) - defer db.Close() - - require.NoError(t, sqlstorage.Migrate(context.Background(), db)) - - storage := sqlstorage.New(db) - - serverRelyingParty, err := rp.NewRelyingPartyOIDC(mockOIDC.Issuer(), mockOIDC.ClientID, mockOIDC.ClientSecret, - fmt.Sprintf("%s/authorize/callback", serverURL), []string{"openid", "email"}) - require.NoError(t, err) - - key, _ := rsa.GenerateKey(rand.Reader, 2048) - - staticClients := []auth.StaticClient{{ - ClientOptions: auth.ClientOptions{ - Id: "test", - Public: true, - RedirectURIs: []string{"http://localhost:3000/auth-callback"}, - Name: "test", - PostLogoutRedirectUris: []string{"http://localhost:3000/"}, - Trusted: true, - }, - }} - storageFacade := authoidc.NewStorageFacade(storage, serverRelyingParty, key, staticClients...) - - keySet, err := authoidc.ReadKeySet(http.DefaultClient, context.Background(), delegatedauth.Config{ - Issuer: mockOIDC.Issuer(), - ClientID: mockOIDC.ClientID, - ClientSecret: mockOIDC.ClientSecret, - }) - require.NoError(t, err) - - provider, err := authoidc.NewOpenIDProvider(storageFacade, serverURL, mockOIDC.Issuer(), keySet) - require.NoError(t, err) - - ar := &oidc.AuthRequest{ - ClientID: staticClients[0].Id, - } - authReq, err := provider.Storage().CreateAuthRequest(context.Background(), ar, "") - require.NoError(t, err) - - client, err := provider.Storage().GetClientByClientID(context.Background(), authReq.GetClientID()) - require.NoError(t, err) - - tokenResponse, err := op.CreateTokenResponse(context.Background(), authReq, client, provider, true, "", "") - require.NoError(t, err) - - t.Run("unprotected route", func(t *testing.T) { - t.Parallel() - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/any", nil) - require.NoError(t, err) - require.NoError(t, verifyAccessToken(req, provider)) - }) - - t.Run("protected routes", func(t *testing.T) { - t.Parallel() - - protectedRoutes := []string{"/clients", "/users"} - for _, route := range protectedRoutes { - - t.Run("no token", func(t *testing.T) { - t.Parallel() - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, route, nil) - require.NoError(t, err) - - err = verifyAccessToken(req, provider) - require.Error(t, err) - require.EqualError(t, err, ErrMissingAuthHeader.Error()) - }) - - t.Run("malformed token", func(t *testing.T) { - t.Parallel() - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, route, nil) - require.NoError(t, err) - - req.Header.Set("Authorization", "malformed") - err = verifyAccessToken(req, provider) - require.Error(t, err) - require.EqualError(t, err, ErrMalformedAuthHeader.Error()) - }) - - t.Run("unverified token", func(t *testing.T) { - t.Parallel() - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, route, nil) - require.NoError(t, err) - - req.Header.Set("Authorization", oidc.PrefixBearer+"unverified") - err = verifyAccessToken(req, provider) - require.Error(t, err) - require.EqualError(t, err, ErrVerifyAuthToken.Error()) - }) - - t.Run("verified token", func(t *testing.T) { - t.Parallel() - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, route, nil) - require.NoError(t, err) - - req.Header.Set("Authorization", oidc.PrefixBearer+tokenResponse.AccessToken) - require.NoError(t, verifyAccessToken(req, provider)) - }) - } - }) -} diff --git a/ee/auth/pkg/api/authorization/main_test.go b/ee/auth/pkg/api/authorization/main_test.go deleted file mode 100644 index c092af353d..0000000000 --- a/ee/auth/pkg/api/authorization/main_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package authorization - -import ( - "testing" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -var srv *pgtesting.PostgresServer - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/ee/auth/pkg/api/authorization/middleware.go b/ee/auth/pkg/api/authorization/middleware.go deleted file mode 100644 index 25e4966b35..0000000000 --- a/ee/auth/pkg/api/authorization/middleware.go +++ /dev/null @@ -1,28 +0,0 @@ -package authorization - -import ( - "errors" - "net/http" - - "github.com/zitadel/oidc/v2/pkg/op" -) - -var ( - ErrMissingAuthHeader = errors.New("missing authorization header") - ErrMalformedAuthHeader = errors.New("malformed authorization header") - ErrVerifyAuthToken = errors.New("could not verify access token") -) - -func Middleware(o op.OpenIDProvider) func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if err := verifyAccessToken(r, o); err != nil { - http.Error(w, err.Error(), http.StatusUnauthorized) - return - } - - h.ServeHTTP(w, r) - }) - } -} diff --git a/ee/auth/pkg/api/clients.go b/ee/auth/pkg/api/clients.go deleted file mode 100644 index 19e0304a39..0000000000 --- a/ee/auth/pkg/api/clients.go +++ /dev/null @@ -1,205 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/uptrace/bun" - - auth "github.com/formancehq/auth/pkg" - _ "github.com/formancehq/go-libs/api" -) - -func addClientRoutes(db *bun.DB, r chi.Router) { - r.Route("/clients", func(r chi.Router) { - r.Post("/", createClient(db)) - r.Get("/", listClients(db)) - r.Route("/{clientId}", func(r chi.Router) { - r.Put("/", updateClient(db)) - r.Delete("/", deleteClient(db)) - r.Get("/", readClient(db)) - r.Route("/secrets", func(r chi.Router) { - r.Post("/", createSecret(db)) - r.Delete("/{secretId}", deleteSecret(db)) - }) - }) - }) -} - -type clientSecretView struct { - auth.ClientSecret - Hash string `json:"-"` -} - -type clientView struct { - auth.ClientOptions - ID string `json:"id"` - Secrets auth.Array[clientSecretView] `json:"secrets" bun:"type:text"` -} - -func mapBusinessClient(c auth.Client) clientView { - return clientView{ - ClientOptions: auth.ClientOptions{ - Public: c.Public, - RedirectURIs: c.RedirectURIs, - Description: c.Description, - Name: c.Name, - PostLogoutRedirectUris: c.PostLogoutRedirectUris, - Metadata: c.Metadata, - Scopes: c.Scopes, - }, - ID: c.Id, - Secrets: mapList(c.Secrets, func(i auth.ClientSecret) clientSecretView { - return clientSecretView{ - ClientSecret: i, - } - }), - } -} - -type secretCreateResult struct { - ID string `json:"id"` - LastDigits string `json:"lastDigits"` - Name string `json:"name"` - Clear string `json:"clear"` -} - -func deleteSecret(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - client := findById[*auth.Client](w, r, db, "clientId") - if client == nil { - return - } - - if !client.DeleteSecret(chi.URLParam(r, "secretId")) { - w.WriteHeader(http.StatusNotFound) - return - } - - _, err := db.NewUpdate(). - Model(client). - Where("id = ?", client.Id). - Exec(r.Context()) - if err != nil { - internalServerError(w, r, err) - return - } - w.WriteHeader(http.StatusNoContent) - } -} - -func createSecret(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - client := findById[*auth.Client](w, r, db, "clientId") - if client == nil { - return - } - - sc := readJSONObject[auth.SecretCreate](w, r) - if sc == nil { - return - } - - secret, clear := client.GenerateNewSecret(*sc) - - _, err := db.NewUpdate(). - Model(client). - Where("id = ?", client.Id). - Exec(r.Context()) - if err != nil { - internalServerError(w, r, err) - return - } - - writeJSONObject(w, r, secretCreateResult{ - ID: secret.ID, - LastDigits: secret.LastDigits, - Name: secret.Name, - Clear: clear, - }) - } -} - -func readClient(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - client := findById[*auth.Client](w, r, db, "clientId") - if client == nil { - return - } - writeJSONObject(w, r, mapBusinessClient(*client)) - } -} - -func deleteClient(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - _, err := db. - NewDelete(). - Model(&auth.Client{}). - Where("id = ?", chi.URLParam(r, "clientId")). - Exec(r.Context()) - if err != nil { - internalServerError(w, r, err) - return - } - w.WriteHeader(http.StatusNoContent) - } -} - -func listClients(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - clients := make([]auth.Client, 0) - if err := db. - NewSelect(). - Model(&clients). - Scan(r.Context()); err != nil { - internalServerError(w, r, err) - return - } - writeJSONObject(w, r, mapList(clients, mapBusinessClient)) - } -} - -func updateClient(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - client := findById[*auth.Client](w, r, db, "clientId") - if client == nil { - return - } - - opts := readJSONObject[auth.ClientOptions](w, r) - if opts == nil { - return - } - - client.Update(*opts) - - _, err := db.NewUpdate(). - Model(client). - Where("id = ?", client.Id). - Exec(r.Context()) - if err != nil { - internalServerError(w, r, err) - return - } - - writeJSONObject(w, r, mapBusinessClient(*client)) - } -} - -func createClient(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - opts := readJSONObject[auth.ClientOptions](w, r) - if opts == nil { - return - } - - c := auth.NewClient(*opts) - if err := createObject(w, r, db, c); err != nil { - return - } - - writeCreatedJSONObject(w, r, mapBusinessClient(*c), c.Id) - } -} diff --git a/ee/auth/pkg/api/clients_test.go b/ee/auth/pkg/api/clients_test.go deleted file mode 100644 index 28a37e8d62..0000000000 --- a/ee/auth/pkg/api/clients_test.go +++ /dev/null @@ -1,356 +0,0 @@ -package api - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/bun/bundebug" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/uptrace/bun" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/storage/sqlstorage" - "github.com/formancehq/go-libs/collectionutils" - "github.com/stretchr/testify/require" -) - -func withDbAndClientRouter(t *testing.T, callback func(router chi.Router, db *bun.DB)) { - t.Parallel() - - pgDatabase := srv.NewDatabase(t) - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - db, err := bunconnect.OpenSQLDB(logging.TestingContext(), bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDatabase.ConnString(), - }, hooks...) - require.NoError(t, err) - defer db.Close() - - require.NoError(t, sqlstorage.Migrate(context.Background(), db)) - - router := chi.NewRouter() - addClientRoutes(db, router) - - callback(router, db) -} - -func TestCreateClient(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - options auth.ClientOptions - } - for _, tc := range []testCase{ - { - name: "confidential client", - options: auth.ClientOptions{ - Name: "confidential client", - RedirectURIs: []string{"http://localhost:8080"}, - Description: "abc", - PostLogoutRedirectUris: []string{"http://localhost:8080/logout"}, - Metadata: map[string]string{ - "foo": "bar", - }, - Scopes: []string{}, - }, - }, - { - name: "public client", - options: auth.ClientOptions{ - Name: "public client", - Public: true, - RedirectURIs: []string{}, - PostLogoutRedirectUris: []string{}, - Metadata: map[string]string{}, - Scopes: []string{}, - }, - }, - { - name: "confidential client", - options: auth.ClientOptions{ - Name: "confidential client", - RedirectURIs: []string{"http://localhost:8080"}, - Description: "abc", - PostLogoutRedirectUris: []string{"http://localhost:8080/logout"}, - Metadata: map[string]string{ - "foo": "bar", - }, - Scopes: []string{"ledger:read", "ledger:write", "formance:test"}, - }, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - req := httptest.NewRequest(http.MethodPost, "/clients", createJSONBuffer(t, tc.options)) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusCreated, res.Code) - - createdClient := readTestResponse[clientView](t, res) - require.NotEmpty(t, createdClient.ID) - require.Equal(t, tc.options, createdClient.ClientOptions) - require.True(t, func() bool { - for _, scope := range tc.options.Scopes { - contain := collectionutils.Contains[string](createdClient.Scopes, scope) - if !contain { - t.Logf("scope %s not found in created client scopes", scope) - return false - } - } - - return true - }()) - tc.options.Id = createdClient.ID - clientFromDatabase := auth.Client{} - err := db.NewSelect(). - Model(&clientFromDatabase). - Where("id = ?", createdClient.ID). - Scan(context.Background()) - require.NoError(t, err) - require.Equal(t, auth.Client{ - ClientOptions: tc.options, - Secrets: []auth.ClientSecret{}, - }, clientFromDatabase) - }) - }) - - } -} - -func TestUpdateClient(t *testing.T) { - - t.Parallel() - type testCase struct { - name string - options auth.ClientOptions - } - for _, tc := range []testCase{ - { - name: "confidential client", - options: auth.ClientOptions{ - Name: "confidential client", - RedirectURIs: []string{"http://localhost:8080"}, - Description: "abc", - PostLogoutRedirectUris: []string{"http://localhost:8080/logout"}, - Metadata: map[string]string{ - "foo": "bar", - }, - Scopes: []string{}, - }, - }, - { - name: "public client", - options: auth.ClientOptions{ - Name: "public client", - Public: true, - RedirectURIs: []string{}, - PostLogoutRedirectUris: []string{}, - Metadata: map[string]string{}, - Scopes: []string{}, - }, - }, - { - name: "confidential client", - options: auth.ClientOptions{ - Name: "confidential client", - RedirectURIs: []string{"http://localhost:8080"}, - Description: "abc", - PostLogoutRedirectUris: []string{"http://localhost:8080/logout"}, - Metadata: map[string]string{ - "foo": "bar", - }, - Scopes: []string{"ledger:read", "ledger:write", "formance:test"}, - }, - }, - } { - tc := tc - t.Run(tc.name, func(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - - initialClient := auth.NewClient(auth.ClientOptions{}) - _, err := db.NewInsert().Model(initialClient).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodPut, "/clients/"+initialClient.Id, createJSONBuffer(t, tc.options)) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusOK, res.Code) - - updatedClient := readTestResponse[clientView](t, res) - require.NotEmpty(t, updatedClient.ID) - require.Equal(t, tc.options, updatedClient.ClientOptions) - - tc.options.Id = updatedClient.ID - clientFromDatabase := auth.Client{} - err = db.NewSelect(). - Model(&clientFromDatabase). - Where("id = ?", updatedClient.ID). - Scan(context.Background()) - require.Equal(t, auth.Client{ - ClientOptions: tc.options, - Secrets: []auth.ClientSecret{}, - }, clientFromDatabase) - }) - }) - } -} - -func TestListClients(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - client1 := auth.NewClient(auth.ClientOptions{}) - _, err := db.NewInsert().Model(client1).Exec(context.Background()) - require.NoError(t, err) - - client2 := auth.NewClient(auth.ClientOptions{ - Metadata: map[string]string{ - "foo": "bar", - }, - }) - _, err = db.NewInsert().Model(client2).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "/clients", nil) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusOK, res.Code) - - clients := readTestResponse[[]clientView](t, res) - require.Len(t, clients, 2) - require.Len(t, clients[1].Metadata, 1) - require.Equal(t, clients[1].Metadata["foo"], "bar") - }) -} - -func TestReadClient(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - - opts := auth.ClientOptions{ - Metadata: map[string]string{ - "foo": "bar", - }, - Scopes: []string{"XXX"}, - RedirectURIs: []string{}, - PostLogoutRedirectUris: []string{}, - } - client1 := auth.NewClient(opts) - secret, _ := client1.GenerateNewSecret(auth.SecretCreate{ - Name: "testing", - }) - _, err := db.NewInsert().Model(client1).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "/clients/"+client1.Id, nil) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusOK, res.Code) - - ret := readTestResponse[clientView](t, res) - require.Equal(t, clientView{ - ClientOptions: opts, - ID: client1.Id, - Secrets: []clientSecretView{{ - ClientSecret: secret, - }}, - }, ret) - }) -} - -func TestDeleteClient(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - - opts := auth.ClientOptions{ - Metadata: map[string]string{ - "foo": "bar", - }, - } - client1 := auth.NewClient(opts) - client1.Scopes = append(client1.Scopes, "XXX") - _, err := db.NewInsert().Model(client1).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodDelete, "/clients/"+client1.Id, nil) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusNoContent, res.Code) - }) -} - -func TestGenerateNewSecret(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - client := auth.NewClient(auth.ClientOptions{}) - _, err := db.NewInsert().Model(client).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodPost, "/clients/"+client.Id+"/secrets", createJSONBuffer(t, auth.SecretCreate{ - Name: "secret1", - })) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - result := readTestResponse[secretCreateResult](t, res) - require.NotEmpty(t, result.Clear) - require.Equal(t, result.LastDigits, result.Clear[len(result.Clear)-4:]) - require.Equal(t, result.Name, "secret1") - - require.Equal(t, http.StatusOK, res.Code) - - err = db.NewSelect(). - Model(client). - Limit(1). - Where("id = ?", client.Id). - Scan(context.Background()) - require.NoError(t, err) - require.Len(t, client.Secrets, 1) - require.True(t, client.Secrets[0].Check(result.Clear)) - }) -} - -func TestDeleteSecret(t *testing.T) { - withDbAndClientRouter(t, func(router chi.Router, db *bun.DB) { - client := auth.NewClient(auth.ClientOptions{}) - secret, _ := client.GenerateNewSecret(auth.SecretCreate{ - Name: "testing", - }) - _, err := db.NewInsert().Model(client).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodDelete, "/clients/"+client.Id+"/secrets/"+secret.ID, nil) - res := httptest.NewRecorder() - - router.ServeHTTP(res, req) - - require.Equal(t, http.StatusNoContent, res.Code) - - err = db.NewSelect(). - Model(client). - Where("id = ?", client.Id). - Scan(context.Background()) - require.NoError(t, err) - require.Len(t, client.Secrets, 0) - }) -} diff --git a/ee/auth/pkg/api/main_test.go b/ee/auth/pkg/api/main_test.go deleted file mode 100644 index 124ffb17fc..0000000000 --- a/ee/auth/pkg/api/main_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package api - -import ( - "testing" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -var srv *pgtesting.PostgresServer - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/ee/auth/pkg/api/module.go b/ee/auth/pkg/api/module.go deleted file mode 100644 index a7293f1540..0000000000 --- a/ee/auth/pkg/api/module.go +++ /dev/null @@ -1,63 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/auth/pkg/api/authorization" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/go-libs/httpserver" - "github.com/zitadel/oidc/v2/pkg/op" - "go.uber.org/fx" -) - -func CreateRootRouter(o op.OpenIDProvider, issuer string, debug bool) chi.Router { - rootRouter := chi.NewRouter() - rootRouter.Use(service.OTLPMiddleware("auth", debug)) - rootRouter.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - handler.ServeHTTP(w, r) - }) - }) - rootRouter.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r.WithContext( - op.ContextWithIssuer(r.Context(), issuer), - )) - }) - }) - rootRouter.Use(authorization.Middleware(o)) - return rootRouter -} - -func addInfoRoute(router chi.Router, serviceInfo api.ServiceInfo) { - router.Get("/_info", api.InfoHandler(serviceInfo)) -} - -func Module(addr, issuer string, serviceInfo api.ServiceInfo, debug bool) fx.Option { - return fx.Options( - health.Module(), - fx.Supply(serviceInfo), - fx.Provide(func(o op.OpenIDProvider) chi.Router { - return CreateRootRouter(o, issuer, debug) - }), - fx.Invoke( - addInfoRoute, - addClientRoutes, - addUserRoutes, - ), - fx.Invoke(func(lc fx.Lifecycle, r chi.Router, healthController *health.HealthController, o op.OpenIDProvider) { - finalRouter := chi.NewRouter() - finalRouter.Get("/_healthcheck", healthController.Check) - finalRouter.Mount("/", r) - - lc.Append(httpserver.NewHook(finalRouter, httpserver.WithAddress(addr))) - }), - ) -} diff --git a/ee/auth/pkg/api/users.go b/ee/auth/pkg/api/users.go deleted file mode 100644 index a2b5e12a37..0000000000 --- a/ee/auth/pkg/api/users.go +++ /dev/null @@ -1,44 +0,0 @@ -package api - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/uptrace/bun" - - auth "github.com/formancehq/auth/pkg" -) - -func addUserRoutes(db *bun.DB, r chi.Router) { - r.Route("/users", func(r chi.Router) { - r.Get("/", listUsers(db)) - r.Route("/{userId}", func(r chi.Router) { - r.Get("/", readUser(db)) - }) - }) -} - -func listUsers(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - users := make([]auth.User, 0) - if err := db. - NewSelect(). - Model(&users). - Scan(r.Context()); err != nil { - internalServerError(w, r, err) - return - } - writeJSONObject(w, r, users) - } -} - -func readUser(db *bun.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - user := findById[*auth.User](w, r, db, "userId") - if user == nil { - return - } - writeJSONObject(w, r, user) - } -} diff --git a/ee/auth/pkg/api/users_test.go b/ee/auth/pkg/api/users_test.go deleted file mode 100644 index 390df03018..0000000000 --- a/ee/auth/pkg/api/users_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package api - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/bun/bundebug" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/uptrace/bun" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/storage/sqlstorage" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -var ( - user1 = &auth.User{ - ID: uuid.NewString(), - Subject: "alice", - Email: "alice@formance.com", - } - - user2 = &auth.User{ - ID: uuid.NewString(), - Subject: "bob", - Email: "bob@formance.com", - } -) - -func TestListUsers(t *testing.T) { - withDbAndUserRouter(t, func(router chi.Router, db *bun.DB) { - _, err := db.NewInsert().Model(user1).Exec(context.Background()) - require.NoError(t, err) - - _, err = db.NewInsert().Model(user2).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "/users", nil) - res := httptest.NewRecorder() - router.ServeHTTP(res, req) - require.Equal(t, http.StatusOK, res.Code) - - users := readTestResponse[[]auth.User](t, res) - require.Len(t, users, 2) - }) -} - -func TestReadUser(t *testing.T) { - withDbAndUserRouter(t, func(router chi.Router, db *bun.DB) { - _, err := db.NewInsert().Model(user1).Exec(context.Background()) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodGet, "/users/"+user1.ID, nil) - res := httptest.NewRecorder() - router.ServeHTTP(res, req) - require.Equal(t, http.StatusOK, res.Code) - - user := readTestResponse[auth.User](t, res) - require.Equal(t, *user1, user) - }) -} - -func withDbAndUserRouter(t *testing.T, callback func(router chi.Router, db *bun.DB)) { - t.Parallel() - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - pgDatabase := srv.NewDatabase(t) - db, err := bunconnect.OpenSQLDB(logging.TestingContext(), bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDatabase.ConnString(), - }, hooks...) - require.NoError(t, err) - defer db.Close() - - require.NoError(t, sqlstorage.Migrate(context.Background(), db)) - - router := chi.NewRouter() - addUserRoutes(db, router) - - callback(router, db) -} diff --git a/ee/auth/pkg/api/util.go b/ee/auth/pkg/api/util.go deleted file mode 100644 index 105cfaf96a..0000000000 --- a/ee/auth/pkg/api/util.go +++ /dev/null @@ -1,97 +0,0 @@ -package api - -import ( - "database/sql" - "encoding/json" - "net/http" - "reflect" - - "github.com/go-chi/chi/v5" - - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - "go.opentelemetry.io/otel/trace" -) - -func validationError(w http.ResponseWriter, r *http.Request, err error) { - w.WriteHeader(http.StatusBadRequest) - if err := json.NewEncoder(w).Encode(api.ErrorResponse{ - ErrorCode: "VALIDATION", - ErrorMessage: err.Error(), - }); err != nil { - logging.FromContext(r.Context()).Info("Error validating request: %s", err) - } -} - -func internalServerError(w http.ResponseWriter, r *http.Request, err error) { - w.WriteHeader(http.StatusInternalServerError) - if err := json.NewEncoder(w).Encode(api.ErrorResponse{ - ErrorCode: "INTERNAL", - ErrorMessage: err.Error(), - }); err != nil { - trace.SpanFromContext(r.Context()).RecordError(err) - } -} - -func writeJSONObject[T any](w http.ResponseWriter, r *http.Request, v T) { - if err := json.NewEncoder(w).Encode(api.BaseResponse[T]{ - Data: &v, - }); err != nil { - trace.SpanFromContext(r.Context()).RecordError(err) - } -} - -func writeCreatedJSONObject(w http.ResponseWriter, r *http.Request, v any, id string) { - w.WriteHeader(http.StatusCreated) - w.Header().Set("Location", "./"+id) - - writeJSONObject(w, r, v) -} - -func readJSONObject[T any](w http.ResponseWriter, r *http.Request) *T { - var t T - if err := json.NewDecoder(r.Body).Decode(&t); err != nil { - validationError(w, r, err) - return nil - } - return &t -} - -func findById[T any](w http.ResponseWriter, r *http.Request, db *bun.DB, params string) T { - var t T - t = reflect.New(reflect.TypeOf(t).Elem()).Interface().(T) - err := db.NewSelect(). - Model(t). - Limit(1). - Where("id = ?", chi.URLParam(r, params)). - Scan(r.Context()) - if err != nil { - switch err { - case sql.ErrNoRows: - w.WriteHeader(http.StatusNotFound) - default: - internalServerError(w, r, err) - } - var zeroValue T - return zeroValue - } - return t -} - -func createObject(w http.ResponseWriter, r *http.Request, db *bun.DB, v any) error { - _, err := db.NewInsert().Model(v).Exec(r.Context()) - if err != nil { - internalServerError(w, r, err) - } - return err -} - -func mapList[I any, O any](items []I, fn func(I) O) []O { - ret := make([]O, 0) - for _, item := range items { - ret = append(ret, fn(item)) - } - return ret -} diff --git a/ee/auth/pkg/api/util_test.go b/ee/auth/pkg/api/util_test.go deleted file mode 100644 index 51094cef99..0000000000 --- a/ee/auth/pkg/api/util_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package api - -import ( - "bytes" - "encoding/json" - "io" - "net/http/httptest" - "testing" - - "github.com/formancehq/go-libs/api" - "github.com/stretchr/testify/require" -) - -func createJSONBuffer(t *testing.T, v any) io.Reader { - data, err := json.Marshal(v) - require.NoError(t, err) - - return bytes.NewBuffer(data) -} - -func readTestResponse[T any](t *testing.T, recorder *httptest.ResponseRecorder) T { - body := api.BaseResponse[T]{} - require.NoError(t, json.NewDecoder(recorder.Body).Decode(&body)) - return *body.Data -} diff --git a/ee/auth/pkg/array.go b/ee/auth/pkg/array.go deleted file mode 100644 index c3f861c1f8..0000000000 --- a/ee/auth/pkg/array.go +++ /dev/null @@ -1,56 +0,0 @@ -package auth - -import ( - "database/sql/driver" - "encoding/json" - "fmt" - "reflect" -) - -type Array[T any] []T - -// Scan implements the sql.Scanner interface. -func (a *Array[T]) Scan(src interface{}) error { - *a = make(Array[T], 0) - var err error - switch src := src.(type) { - case []byte: - err = json.Unmarshal(src, a) - case string: - err = json.Unmarshal([]byte(src), a) - case nil: - default: - return fmt.Errorf("type '%T' not handled", src) - } - if err != nil { - return err - } - return nil -} - -func (a *Array[T]) Contains(t T) bool { - for _, v := range *a { - if reflect.DeepEqual(v, t) { - return true - } - } - return false -} - -func (a *Array[T]) Append(t T) *Array[T] { - *a = append(*a, t) - return a -} - -// Value implements the driver.Valuer interface. -func (a Array[T]) Value() (driver.Value, error) { - if a == nil { - return nil, nil - } - data, err := json.Marshal(a) - if err != nil { - return nil, err - } - - return string(data), nil -} diff --git a/ee/auth/pkg/client.go b/ee/auth/pkg/client.go deleted file mode 100644 index 3ee2d6d650..0000000000 --- a/ee/auth/pkg/client.go +++ /dev/null @@ -1,177 +0,0 @@ -package auth - -import ( - "crypto/sha256" - "encoding/base64" - "errors" - - "github.com/uptrace/bun" - - "github.com/google/uuid" -) - -func newHash(v string) string { - digest := sha256.New() - digest.Write([]byte(v)) - hash := digest.Sum(nil) - - return base64.StdEncoding.EncodeToString(hash) -} - -type ClientSecret struct { - ID string `json:"id"` - Hash string `json:"hash"` - LastDigits string `json:"lastDigits"` - Name string `json:"name"` - Metadata Metadata `json:"metadata" bun:"type:text"` -} - -func (s ClientSecret) Check(clear string) bool { - return s.Hash == newHash(clear) -} - -func newSecret(opts SecretCreate) (ClientSecret, string) { - clear := uuid.NewString() - return ClientSecret{ - ID: uuid.NewString(), - Hash: newHash(clear), - LastDigits: clear[len(clear)-4:], - Name: opts.Name, - Metadata: opts.Metadata, - }, clear -} - -func (c *Client) Update(opts ClientOptions) { - c.RedirectURIs = opts.RedirectURIs - c.PostLogoutRedirectUris = opts.PostLogoutRedirectUris - c.Description = opts.Description - c.Name = opts.Name - c.Metadata = opts.Metadata - c.Trusted = opts.Trusted - c.Public = opts.Public - c.Scopes = opts.Scopes -} - -func (c *Client) GenerateNewSecret(opts SecretCreate) (ClientSecret, string) { - secret, clear := newSecret(opts) - c.Secrets = append(c.Secrets, secret) - - return secret, clear -} - -func (c *Client) ValidateSecret(secret string) error { - if !c.HasSecret(secret) { - return errors.New("invalid secret") - } - return nil -} - -func (c *Client) HasSecret(clear string) bool { - for _, secret := range c.Secrets { - if secret.Check(clear) { - return true - } - } - return false -} - -func (c *Client) DeleteSecret(id string) bool { - for i, secret := range c.Secrets { - if secret.ID == id { - if i < len(c.Secrets)-1 { - c.Secrets = append(c.Secrets[:i], c.Secrets[i+1:]...) - } else { - c.Secrets = c.Secrets[:i] - } - return true - } - } - return false -} - -func (c *Client) HasScope(id string) bool { - for _, clientScope := range c.Scopes { - if clientScope == id { - return true - } - } - return false -} - -type Client struct { - bun.BaseModel `bun:"table:clients"` - - ClientOptions - Secrets Array[ClientSecret] `bun:",type:text" json:"secrets"` -} - -func (c *Client) GetScopes() []string { - return c.Scopes -} - -type StaticClient struct { - ClientOptions `mapstructure:",squash" yaml:",inline"` - Secrets []string `json:"secrets" yaml:"secrets"` -} - -func (s StaticClient) ValidateSecret(secret string) error { - for _, clientSecret := range s.Secrets { - if clientSecret == secret { - return nil - } - } - return errors.New("invalid secret") -} - -func (s StaticClient) GetScopes() []string { - return s.Scopes -} - -type ClientOptions struct { - Id string `json:"id" yaml:"id"` - Public bool `json:"public" yaml:"public"` - RedirectURIs Array[string] `json:"redirectUris" yaml:"redirectUris" bun:"redirect_uris,type:text"` - Description string `json:"description" yaml:"description"` - Name string `json:"name" yaml:"name"` - PostLogoutRedirectUris Array[string] `json:"postLogoutRedirectUris" yaml:"postLogoutRedirectUris" bun:"post_logout_redirect_uris,type:text"` - Metadata Metadata `json:"metadata" yaml:"metadata" bun:"type:text"` - Trusted bool `json:"trusted" yaml:"trusted"` - Scopes Array[string] `bun:"type:text" json:"scopes" yaml:"scopes"` -} - -func (s *ClientOptions) IsTrusted() bool { - return s.Trusted -} - -func (c *ClientOptions) GetID() string { - return c.Id -} - -func (c *ClientOptions) GetRedirectURIs() []string { - return c.RedirectURIs -} - -func (c *ClientOptions) GetPostLogoutRedirectUris() []string { - return c.PostLogoutRedirectUris -} - -func (c *ClientOptions) IsPublic() bool { - return c.Public -} - -func NewClient(opts ClientOptions) *Client { - if opts.Id == "" { - opts.Id = uuid.NewString() - } - - client := &Client{ - ClientOptions: opts, - } - client.Update(opts) - return client -} - -type SecretCreate struct { - Name string `json:"name"` - Metadata Metadata `json:"metadata"` -} diff --git a/ee/auth/pkg/code.go b/ee/auth/pkg/code.go deleted file mode 100644 index b79380640e..0000000000 --- a/ee/auth/pkg/code.go +++ /dev/null @@ -1,24 +0,0 @@ -package auth - -import ( - "github.com/zitadel/oidc/v2/pkg/oidc" -) - -type OIDCCodeChallenge struct { - Challenge string - Method string -} - -func CodeChallengeToOIDC(challenge *OIDCCodeChallenge) *oidc.CodeChallenge { - if challenge == nil { - return nil - } - challengeMethod := oidc.CodeChallengeMethodPlain - if challenge.Method == "S256" { - challengeMethod = oidc.CodeChallengeMethodS256 - } - return &oidc.CodeChallenge{ - Challenge: challenge.Challenge, - Method: challengeMethod, - } -} diff --git a/ee/auth/pkg/delegatedauth/config.go b/ee/auth/pkg/delegatedauth/config.go deleted file mode 100644 index 7091215ea2..0000000000 --- a/ee/auth/pkg/delegatedauth/config.go +++ /dev/null @@ -1,8 +0,0 @@ -package delegatedauth - -type Config struct { - Issuer string - ClientID string - ClientSecret string - RedirectURL string -} diff --git a/ee/auth/pkg/delegatedauth/module.go b/ee/auth/pkg/delegatedauth/module.go deleted file mode 100644 index 11884a0388..0000000000 --- a/ee/auth/pkg/delegatedauth/module.go +++ /dev/null @@ -1,17 +0,0 @@ -package delegatedauth - -import ( - "net/http" - - "github.com/zitadel/oidc/v2/pkg/client/rp" - "go.uber.org/fx" -) - -func Module() fx.Option { - return fx.Options( - fx.Provide(func(cfg Config, httpClient *http.Client) (rp.RelyingParty, error) { - return rp.NewRelyingPartyOIDC(cfg.Issuer, cfg.ClientID, cfg.ClientSecret, cfg.RedirectURL, []string{"openid email"}, - rp.WithHTTPClient(httpClient)) - }), - ) -} diff --git a/ee/auth/pkg/delegatedauth/state.go b/ee/auth/pkg/delegatedauth/state.go deleted file mode 100644 index 0a45e10b30..0000000000 --- a/ee/auth/pkg/delegatedauth/state.go +++ /dev/null @@ -1,27 +0,0 @@ -package delegatedauth - -import ( - "bytes" - "encoding/base64" - "encoding/json" -) - -type DelegatedState struct { - AuthRequestID string `json:"authRequestID"` -} - -func (s DelegatedState) EncodeAsUrlParam() string { - buf := bytes.NewBufferString("") - if err := json.NewEncoder(base64.NewEncoder(base64.URLEncoding, buf)).Encode(s); err != nil { - panic(err) - } - return buf.String() -} - -func DecodeDelegatedState(v string) (*DelegatedState, error) { - ret := &DelegatedState{} - if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(v))).Decode(ret); err != nil { - return nil, err - } - return ret, nil -} diff --git a/ee/auth/pkg/metadata.go b/ee/auth/pkg/metadata.go deleted file mode 100644 index b229f5f298..0000000000 --- a/ee/auth/pkg/metadata.go +++ /dev/null @@ -1,41 +0,0 @@ -package auth - -import ( - "database/sql/driver" - "encoding/json" - "fmt" -) - -type Metadata map[string]string - -// Scan implements the sql.Scanner interface. -func (a *Metadata) Scan(src interface{}) error { - *a = Metadata{} - var err error - switch src := src.(type) { - case []byte: - err = json.Unmarshal(src, a) - case string: - err = json.Unmarshal([]byte(src), a) - case nil: - default: - return fmt.Errorf("type '%T' not handled", src) - } - if err != nil { - return err - } - return nil -} - -// Value implements the driver.Valuer interface. -func (a Metadata) Value() (driver.Value, error) { - if a == nil { - return nil, nil - } - data, err := json.Marshal(a) - if err != nil { - return nil, err - } - - return string(data), nil -} diff --git a/ee/auth/pkg/oidc/authorize_callback.go b/ee/auth/pkg/oidc/authorize_callback.go deleted file mode 100644 index 106aa71449..0000000000 --- a/ee/auth/pkg/oidc/authorize_callback.go +++ /dev/null @@ -1,81 +0,0 @@ -package oidc - -import ( - "embed" - "html/template" - "net/http" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/google/uuid" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" -) - -//go:embed templates -var templateFs embed.FS - -func authorizeErrorHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - authError := r.URL.Query().Get("error") - tpl := template.Must(template.New("error.tmpl"). - ParseFS(templateFs, "templates/error.tmpl")) - if err := tpl.Execute(w, map[string]interface{}{ - "Error": authError, - "ErrorDescription": r.URL.Query().Get("error_description"), - }); err != nil { - panic(err) - } - } -} - -func authorizeCallbackHandler( - provider op.OpenIDProvider, - storage Storage, - relyingParty rp.RelyingParty, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - state, err := delegatedauth.DecodeDelegatedState(r.URL.Query().Get("state")) - if err != nil { - panic(err) - } - - authRequest, err := storage.FindAuthRequest(r.Context(), state.AuthRequestID) - if err != nil { - panic(err) - } - - tokens, err := rp.CodeExchange[*oidc.IDTokenClaims](r.Context(), r.URL.Query().Get("code"), relyingParty) - if err != nil { - panic(err) - } - - userInfos, err := rp.Userinfo(tokens.AccessToken, "Bearer", tokens.IDTokenClaims.GetSubject(), relyingParty) - if err != nil { - panic(err) - } - - user, err := storage.FindUserBySubject(r.Context(), tokens.IDTokenClaims.GetSubject()) - if err != nil { - user = &auth.User{ - ID: uuid.NewString(), - Subject: userInfos.Subject, - Email: userInfos.Email, - } - if err := storage.SaveUser(r.Context(), user); err != nil { - panic(err) - } - } - - authRequest.UserID = user.ID - - if err := storage.UpdateAuthRequest(r.Context(), authRequest); err != nil { - panic(err) - } - - w.Header().Set("Location", op.AuthCallbackURL(provider)(r.Context(), state.AuthRequestID)) - w.WriteHeader(http.StatusFound) - } -} diff --git a/ee/auth/pkg/oidc/authorize_callback_test.go b/ee/auth/pkg/oidc/authorize_callback_test.go deleted file mode 100644 index 490715ae5f..0000000000 --- a/ee/auth/pkg/oidc/authorize_callback_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package oidc - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestAuthorizeError(t *testing.T) { - handler := authorizeErrorHandler() - - req := httptest.NewRequest(http.MethodGet, "/?error=foo&error_description=bar", nil) - rec := httptest.NewRecorder() - - handler.ServeHTTP(rec, req) - - data, err := io.ReadAll(rec.Body) - require.NoError(t, err) - require.Equal(t, string(data), "foo : bar\n") -} diff --git a/ee/auth/pkg/oidc/client.go b/ee/auth/pkg/oidc/client.go deleted file mode 100644 index f41f33b1f9..0000000000 --- a/ee/auth/pkg/oidc/client.go +++ /dev/null @@ -1,143 +0,0 @@ -package oidc - -import ( - "time" - - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" -) - -type Client interface { - GetID() string - GetRedirectURIs() []string - GetPostLogoutRedirectUris() []string - IsPublic() bool - GetScopes() []string - ValidateSecret(secret string) error - IsTrusted() bool -} - -type clientFacade struct { - Client Client - relyingParty rp.RelyingParty -} - -func NewClientFacade(client Client, relyingParty rp.RelyingParty) *clientFacade { - return &clientFacade{ - Client: client, - relyingParty: relyingParty, - } -} - -// GetID must return the client_id -func (c *clientFacade) GetID() string { - if c == nil { - return "" - } - return c.Client.GetID() -} - -// RedirectURIs must return the registered redirect_uris for Code and Implicit Flow -func (c *clientFacade) RedirectURIs() []string { - return c.Client.GetRedirectURIs() -} - -// PostLogoutRedirectURIs must return the registered post_logout_redirect_uris for sign-outs -func (c *clientFacade) PostLogoutRedirectURIs() []string { - return c.Client.GetPostLogoutRedirectUris() -} - -// ApplicationType must return the type of the client (app, native, user agent) -func (c *clientFacade) ApplicationType() op.ApplicationType { - return op.ApplicationTypeWeb -} - -// AuthMethod must return the authentication method (client_secret_basic, client_secret_post, none, private_key_jwt) -func (c *clientFacade) AuthMethod() oidc.AuthMethod { - authMethod := oidc.AuthMethodNone - if !c.Client.IsPublic() { - authMethod = oidc.AuthMethodBasic - } - return authMethod -} - -// ResponseTypes must return all allowed response types (code, id_token token, id_token) -// these must match with the allowed grant types -func (c *clientFacade) ResponseTypes() []oidc.ResponseType { - return []oidc.ResponseType{oidc.ResponseTypeCode} -} - -// GrantTypes must return all allowed grant types (authorization_code, refresh_token, urn:ietf:params:oauth:grant-type:jwt-bearer) -func (c *clientFacade) GrantTypes() []oidc.GrantType { - grantTypes := []oidc.GrantType{ - oidc.GrantTypeCode, - oidc.GrantTypeRefreshToken, - } - if !c.Client.IsPublic() { - grantTypes = append(grantTypes, oidc.GrantTypeClientCredentials) - } - return grantTypes -} - -// LoginURL will be called to redirect the user (agent) to the login UI -// you could implement some logic here to redirect the users to different login UIs depending on the client -func (c *clientFacade) LoginURL(id string) string { - return rp.AuthURL(delegatedauth.DelegatedState{ - AuthRequestID: id, - }.EncodeAsUrlParam(), c.relyingParty) -} - -// AccessTokenType must return the type of access token the client uses (Bearer (opaque) or JWT) -func (c *clientFacade) AccessTokenType() op.AccessTokenType { - return op.AccessTokenTypeJWT -} - -// IDTokenLifetime must return the lifetime of the client's id_tokens -func (c *clientFacade) IDTokenLifetime() time.Duration { - return 1 * time.Hour -} - -// DevMode enables the use of non-compliant configs such as redirect_uris (e.g. http schema for user agent client) -func (c *clientFacade) DevMode() bool { - return false -} - -// RestrictAdditionalIdTokenScopes allows specifying which custom scopes shall be asserted into the id_token -func (c *clientFacade) RestrictAdditionalIdTokenScopes() func(scopes []string) []string { - return func(scopes []string) []string { - return scopes - } -} - -// RestrictAdditionalAccessTokenScopes allows specifying which custom scopes shall be asserted into the JWT access_token -func (c *clientFacade) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string { - return func(scopes []string) []string { - return scopes - } -} - -// IsScopeAllowed enables Client specific custom scopes validation -func (c *clientFacade) IsScopeAllowed(label string) bool { - for _, scope := range c.Client.GetScopes() { - if scope == label { - return true - } - } - return false -} - -// IDTokenUserinfoClaimsAssertion allows specifying if claims of scope profile, email, phone and address are asserted into the id_token -// even if an access token if issued which violates the OIDC Core spec -// (5.4. Requesting Claims using Scope Values: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims) -// some clients though require that e.g. email is always in the id_token when requested even if an access_token is issued -func (c *clientFacade) IDTokenUserinfoClaimsAssertion() bool { - return false -} - -// ClockSkew enables clients to instruct the OP to apply a clock skew on the various times and expirations -// (subtract from issued_at, add to expiration, ...) -func (c *clientFacade) ClockSkew() time.Duration { - return 0 -} diff --git a/ee/auth/pkg/oidc/grant_type_bearer.go b/ee/auth/pkg/oidc/grant_type_bearer.go deleted file mode 100644 index 4ad7dcccb4..0000000000 --- a/ee/auth/pkg/oidc/grant_type_bearer.go +++ /dev/null @@ -1,155 +0,0 @@ -package oidc - -import ( - "context" - "fmt" - "net/http" - "time" - - httphelper "github.com/zitadel/oidc/v2/pkg/http" - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" - "gopkg.in/go-jose/go-jose.v2" -) - -type openIDKeySet struct { - jose.JSONWebKeySet -} - -// VerifySignature implements the oidc.KeySet interface -// providing an implementation for the keys stored in the OP Storage interface -func (o *openIDKeySet) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) { - keyID, alg := oidc.GetKeyIDAndAlg(jws) - - var ( - key jose.JSONWebKey - err error - ) - key, err = oidc.FindMatchingKey(keyID, oidc.KeyUseSignature, alg, o.JSONWebKeySet.Keys...) - if err != nil { - return nil, fmt.Errorf("invalid signature: %w", err) - } - - return jws.Verify(&key) -} - -func VerifyJWTAssertion(ctx context.Context, assertion string, v JWTProfileVerifier) (*oidc.JWTTokenRequest, error) { - request := new(oidc.JWTTokenRequest) - - _, err := oidc.ParseToken(assertion, request) - if err != nil { - return nil, err - } - - if err := oidc.CheckAudience(request, v.Issuer()); err != nil { - return nil, err - } - - if err := oidc.CheckExpiration(request, v.Offset()); err != nil { - return nil, err - } - - accessTokenVerifier := op.NewAccessTokenVerifier(v.DelegatedIssuer(), &openIDKeySet{ - v.JSONWebKeySet(), - }) - if _, err := op.VerifyAccessToken[*oidc.TokenClaims](ctx, assertion, accessTokenVerifier); err != nil { - return nil, err - } - - return request, nil -} - -type JWTProfileVerifier interface { - oidc.Verifier - DelegatedIssuer() string - JSONWebKeySet() jose.JSONWebKeySet -} - -type JWTAuthorizationGrantExchanger interface { - op.Exchanger - JWTProfileVerifier() JWTProfileVerifier -} - -func grantTypeBearer(issuer string, p JWTAuthorizationGrantExchanger) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - profileRequest, err := op.ParseJWTProfileGrantRequest(r, p.Decoder()) - if err != nil { - op.RequestError(w, r, err) - return - } - - clientID, clientSecret, ok := r.BasicAuth() - var client *clientFacade - if ok { - c, err := p.Storage().GetClientByClientID(r.Context(), clientID) - if err != nil { - op.RequestError(w, r, err) - return - } - client = c.(*clientFacade) - if !client.Client.IsPublic() { - if err := client.Client.ValidateSecret(clientSecret); err != nil { - op.RequestError(w, r, err) - return - } - } - } - - tokenRequest, err := VerifyJWTAssertion(r.Context(), profileRequest.Assertion, p.JWTProfileVerifier()) - if err != nil { - op.RequestError(w, r, err) - return - } - - tokenRequest.Scopes, err = p.Storage().ValidateJWTProfileScopes(r.Context(), tokenRequest.Issuer, profileRequest.Scope) - if err != nil { - op.RequestError(w, r, err) - return - } - - tokens, err := ParseAssertion(profileRequest.Assertion) - if err != nil { - op.RequestError(w, r, err) - return - } - - tokenRequest.Scopes = tokens.Scopes - - resp, err := CreateJWTTokenResponse(r.Context(), issuer, tokenRequest, p, client) - if err != nil { - op.RequestError(w, r, err) - return - } - - httphelper.MarshalJSON(w, resp) - } -} - -func ParseAssertion(assertion string) (*oidc.AccessTokenClaims, error) { - var claims = new(oidc.AccessTokenClaims) - - _, err := oidc.ParseToken(assertion, claims) - if err != nil { - return nil, err - } - - return claims, nil -} - -func CreateJWTTokenResponse(ctx context.Context, issuer string, tokenRequest *oidc.JWTTokenRequest, creator op.TokenCreator, client op.Client) (*oidc.AccessTokenResponse, error) { - id, exp, err := creator.Storage().CreateAccessToken(ctx, tokenRequest) - if err != nil { - return nil, err - } - - accessToken, err := op.CreateJWT(ctx, issuer, tokenRequest, exp, id, client, creator.Storage()) - if err != nil { - return nil, err - } - - return &oidc.AccessTokenResponse{ - AccessToken: accessToken, - TokenType: oidc.BearerToken, - ExpiresIn: uint64(exp.Sub(time.Now().UTC()).Seconds()), - }, nil -} diff --git a/ee/auth/pkg/oidc/keyset.go b/ee/auth/pkg/oidc/keyset.go deleted file mode 100644 index 93111fa895..0000000000 --- a/ee/auth/pkg/oidc/keyset.go +++ /dev/null @@ -1,36 +0,0 @@ -package oidc - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/zitadel/oidc/v2/pkg/client" - "gopkg.in/go-jose/go-jose.v2" -) - -func ReadKeySet(httpClient *http.Client, ctx context.Context, configuration delegatedauth.Config) (*jose.JSONWebKeySet, error) { - // TODO: Inefficient, should keep public keys locally and use them instead of calling the network - discoveryConfiguration, err := client.Discover(configuration.Issuer, httpClient) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, discoveryConfiguration.JwksURI, nil) - if err != nil { - return nil, err - } - - rsp, err := httpClient.Do(req) - if err != nil { - return nil, err - } - - keySet := jose.JSONWebKeySet{} - if err := json.NewDecoder(rsp.Body).Decode(&keySet); err != nil { - return nil, err - } - - return &keySet, nil -} diff --git a/ee/auth/pkg/oidc/main_test.go b/ee/auth/pkg/oidc/main_test.go deleted file mode 100644 index e845559504..0000000000 --- a/ee/auth/pkg/oidc/main_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package oidc_test - -import ( - "testing" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -var srv *pgtesting.PostgresServer - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/ee/auth/pkg/oidc/module.go b/ee/auth/pkg/oidc/module.go deleted file mode 100644 index 059ee4d1df..0000000000 --- a/ee/auth/pkg/oidc/module.go +++ /dev/null @@ -1,43 +0,0 @@ -package oidc - -import ( - "context" - "crypto/rsa" - "net/http" - - "github.com/go-chi/chi/v5" - - "gopkg.in/go-jose/go-jose.v2" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/op" - "go.uber.org/fx" -) - -func Module(privateKey *rsa.PrivateKey, issuer string, staticClients ...auth.StaticClient) fx.Option { - return fx.Options( - fx.Invoke(fx.Annotate(func(router chi.Router, provider op.OpenIDProvider, - storage Storage, relyingParty rp.RelyingParty) { - AddRoutes(router, provider, storage, relyingParty) - }, fx.ParamTags(``, ``, ``, `optional:"true"`))), - fx.Provide(fx.Annotate(func(storage Storage, relyingParty rp.RelyingParty) *storageFacade { - return NewStorageFacade(storage, relyingParty, privateKey, staticClients...) - }, fx.As(new(op.Storage)), fx.ParamTags(``, `optional:"true"`))), - fx.Provide(fx.Annotate(func(httpClient *http.Client, storage op.Storage, configuration delegatedauth.Config) (op.OpenIDProvider, error) { - var ( - keySet *jose.JSONWebKeySet - err error - ) - if configuration.Issuer != "" { - keySet, err = ReadKeySet(httpClient, context.TODO(), configuration) - if err != nil { - return nil, err - } - } - - return NewOpenIDProvider(storage, issuer, configuration.Issuer, keySet) - }, fx.ParamTags(``, ``, `optional:"true"`))), - ) -} diff --git a/ee/auth/pkg/oidc/oidc_test.go b/ee/auth/pkg/oidc/oidc_test.go deleted file mode 100644 index 32c5ae2401..0000000000 --- a/ee/auth/pkg/oidc/oidc_test.go +++ /dev/null @@ -1,284 +0,0 @@ -package oidc_test - -import ( - "bytes" - "context" - "crypto/rand" - "crypto/rsa" - "encoding/json" - "fmt" - "net" - "net/http" - "net/http/httptest" - "net/http/httputil" - "net/url" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bundebug" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/logging" - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/bun/bunconnect" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/delegatedauth" - "github.com/formancehq/auth/pkg/oidc" - "github.com/formancehq/auth/pkg/storage/sqlstorage" - "github.com/golang-jwt/jwt" - "github.com/oauth2-proxy/mockoidc" - "github.com/stretchr/testify/require" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/client/rs" - zoidc "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" - "golang.org/x/oauth2/clientcredentials" -) - -type user struct { - *mockoidc.MockUser -} - -func (u *user) Userinfo(scope []string) ([]byte, error) { - encoded, err := u.MockUser.Userinfo(scope) - if err != nil { - return nil, err - } - - m := make(map[string]any) - if err := json.Unmarshal(encoded, &m); err != nil { - return nil, err - } - m["sub"] = u.Subject - return json.Marshal(m) -} - -func withServer(t *testing.T, fn func(m *mockoidc.MockOIDC, storage *sqlstorage.Storage, issuer string, provider op.OpenIDProvider)) { - t.Parallel() - - // Create a mock OIDC server which will always return a default user - mockOIDC, err := mockoidc.Run() - require.NoError(t, err) - defer func() { - require.NoError(t, mockOIDC.Shutdown()) - }() - - // Prepare a tcp connection, listening on :0 to select a random port - l, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - - // Compute server url, it will be the "issuer" of our oidc provider - serverUrl := fmt.Sprintf("http://%s", l.Addr().String()) - - // As our oidc provider, is also a relying party (it delegates authentication), we need to construct a relying party - // with information from the mock - cl := http.DefaultClient - cl.Transport = RoundTripper{http.DefaultTransport} - serverRelyingParty, err := rp.NewRelyingPartyOIDC(mockOIDC.Issuer(), mockOIDC.ClientID, mockOIDC.ClientSecret, - fmt.Sprintf("%s/authorize/callback", serverUrl), []string{"openid", "email"}, rp.WithHTTPClient(cl)) - require.NoError(t, err) - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - postgresDB := srv.NewDatabase(t) - db, err := bunconnect.OpenSQLDB(logging.TestingContext(), bunconnect.ConnectionOptions{ - DatabaseSourceName: postgresDB.ConnString(), - }, hooks...) - require.NoError(t, err) - defer db.Close() - - require.NoError(t, sqlstorage.Migrate(context.TODO(), db)) - - storage := sqlstorage.New(db) - - key, _ := rsa.GenerateKey(rand.Reader, 2048) - storageFacade := oidc.NewStorageFacade(storage, serverRelyingParty, key) - - keySet, err := oidc.ReadKeySet(http.DefaultClient, context.Background(), delegatedauth.Config{ - Issuer: mockOIDC.Issuer(), - ClientID: mockOIDC.ClientID, - ClientSecret: mockOIDC.ClientSecret, - }) - require.NoError(t, err) - - // Construct our oidc provider - provider, err := oidc.NewOpenIDProvider(storageFacade, serverUrl, mockOIDC.Issuer(), keySet) - require.NoError(t, err) - - // Create the router - router := chi.NewRouter() - oidc.AddRoutes(router, provider, storage, serverRelyingParty) - - // Create our http server for our oidc provider - providerHttpServer := &http.Server{ - Handler: router, - ReadHeaderTimeout: 5 * time.Second, - } - go func() { - err := providerHttpServer.Serve(l) - if err != http.ErrServerClosed { - require.Fail(t, err.Error()) - } - }() - defer providerHttpServer.Close() - - fn(mockOIDC, storage, serverUrl, provider) -} - -func Test3LeggedFlow(t *testing.T) { - withServer(t, func(m *mockoidc.MockOIDC, storage *sqlstorage.Storage, issuer string, provider op.OpenIDProvider) { - // Create ou http server for our client (a web application for example) - codeChan := make(chan string, 1) // Just store codes coming from our provider inside a chan - clientHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - codeChan <- r.URL.Query().Get("code") - }) - clientHttpServer := httptest.NewServer(clientHandler) - defer clientHttpServer.Close() - - // Create a OAuth2 client which represent our client application - client := auth.NewClient(auth.ClientOptions{}) - client.RedirectURIs.Append(clientHttpServer.URL) // Need to configure the redirect uri - _, clear := client.GenerateNewSecret(auth.SecretCreate{}) // Need to generate a secret - require.NoError(t, storage.SaveClient(context.TODO(), client)) - - // As our client is a relying party, we can use the library to get some helpers - clientRelyingParty, err := rp.NewRelyingPartyOIDC(issuer, client.Id, clear, client.RedirectURIs[0], []string{"openid", "email"}) - require.NoError(t, err) - - m.QueueUser(&user{ - MockUser: mockoidc.DefaultUser(), - }) - - // Trigger an authentication request - authUrl := rp.AuthURL("", clientRelyingParty) - if testing.Verbose() { - fmt.Printf("URL:%s\n", authUrl) - } - rsp, err := http.Get(authUrl) - require.NoError(t, err) - require.Equal(t, http.StatusOK, rsp.StatusCode) - - select { - // As the mock automatically accept login response, we should have received a code - case code := <-codeChan: - // And this code is used to get a token - tokens, err := rp.CodeExchange[*zoidc.IDTokenClaims](context.TODO(), code, clientRelyingParty) - require.NoError(t, err) - require.Equal(t, time.Until(tokens.Expiry).Round(oidc.ExpirationToken3Legged), oidc.ExpirationToken3Legged) - - // Create a OAuth2 client which represent our client application - secondaryClient := auth.NewClient(auth.ClientOptions{ - Trusted: true, - }) - _, clear = secondaryClient.GenerateNewSecret(auth.SecretCreate{}) // Need to generate a secret - require.NoError(t, storage.SaveClient(context.TODO(), secondaryClient)) - - resourceServer, err := rs.NewResourceServerClientCredentials(issuer, secondaryClient.Id, clear) - require.NoError(t, err) - - introspection, err := rs.Introspect(context.TODO(), resourceServer, tokens.AccessToken) - require.NoError(t, err) - require.True(t, introspection.Active) - - user, err := storage.FindUser(context.TODO(), tokens.IDTokenClaims.GetSubject()) - require.NoError(t, err) - require.NotEmpty(t, user.Email) - default: - require.Fail(t, "code was expected") - } - }) -} - -type RoundTripper struct { - http.RoundTripper -} - -func (r RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - by, err := httputil.DumpRequest(req, true) - if err != nil { - return nil, err - } - if testing.Verbose() { - fmt.Printf("REQ:%s\n", string(by)) - } - resp, err := r.RoundTripper.RoundTrip(req) - if err != nil { - return nil, err - } - by, err = httputil.DumpResponse(resp, true) - if err != nil { - return nil, err - } - if testing.Verbose() { - fmt.Printf("RESP:%s\n", string(by)) - } - return resp, err -} - -var _ http.RoundTripper = RoundTripper{} - -func TestJWTAssertions(t *testing.T) { - withServer(t, func(m *mockoidc.MockOIDC, storage *sqlstorage.Storage, issuer string, provider op.OpenIDProvider) { - - // Create a OAuth2 client which represent our client application - client := auth.NewClient(auth.ClientOptions{}) - _, clear := client.GenerateNewSecret(auth.SecretCreate{}) // Need to generate a secret - require.NoError(t, storage.SaveClient(context.TODO(), client)) - - // As our client is a relying party, we can use the library to get some helpers - clientRelyingParty, err := rp.NewRelyingPartyOIDC(m.Issuer(), client.Id, clear, "", []string{"openid", "email"}) - require.NoError(t, err) - - token, err := m.Keypair.SignJWT(jwt.MapClaims{ - "aud": []string{m.Issuer()}, - "exp": time.Now().Add(5 * time.Minute).Unix(), - "iss": m.Issuer(), - }) - require.NoError(t, err) - - // Create a OAuth2 client which represent our client application - form := url.Values{ - "grant_type": []string{"urn:ietf:params:oauth:grant-type:jwt-bearer"}, - "assertion": []string{token}, - "scope": []string{"openid email"}, - } - req, err := http.NewRequest(http.MethodPost, clientRelyingParty.OAuthConfig().Endpoint.TokenURL, - bytes.NewBufferString(form.Encode())) - require.NoError(t, err) - req.SetBasicAuth(client.Id, clear) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - _, err = http.DefaultClient.Do(req) - require.NoError(t, err) - }) -} - -func TestClientCredentials(t *testing.T) { - withServer(t, func(m *mockoidc.MockOIDC, storage *sqlstorage.Storage, issuer string, provider op.OpenIDProvider) { - - // Create a OAuth2 client which represent our client application - client := auth.NewClient(auth.ClientOptions{}) - _, clear := client.GenerateNewSecret(auth.SecretCreate{}) // Need to generate a secret - require.NoError(t, storage.SaveClient(context.TODO(), client)) - - // As our client is a relying party, we can use the library to get some helpers - clientRelyingParty, err := rp.NewRelyingPartyOIDC(issuer, client.Id, clear, "", []string{"openid", "email"}) - require.NoError(t, err) - - // Create a OAuth2 client which represent our client application - clientCredentialsConfig := clientcredentials.Config{ - ClientID: client.Id, - ClientSecret: clear, - TokenURL: clientRelyingParty.OAuthConfig().Endpoint.TokenURL, - Scopes: []string{}, - } - token, err := clientCredentialsConfig.Token(context.Background()) - require.NoError(t, err) - require.Equal(t, time.Until(token.Expiry).Round(oidc.ExpirationToken2Legged), oidc.ExpirationToken2Legged) - }) -} diff --git a/ee/auth/pkg/oidc/provider.go b/ee/auth/pkg/oidc/provider.go deleted file mode 100644 index 27b429c1ac..0000000000 --- a/ee/auth/pkg/oidc/provider.go +++ /dev/null @@ -1,102 +0,0 @@ -package oidc - -import ( - "crypto/sha256" - "net/http" - "time" - - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" - "golang.org/x/text/language" - "gopkg.in/go-jose/go-jose.v2" -) - -const ( - pathLoggedOut = "/logged-out" -) - -type verifier struct { - issuer string - mat time.Duration - offset time.Duration - jsonWebKeySet jose.JSONWebKeySet - delegatedIssuer string -} - -func (v verifier) DelegatedIssuer() string { - return v.delegatedIssuer -} - -func (v verifier) JSONWebKeySet() jose.JSONWebKeySet { - return v.jsonWebKeySet -} - -func (v verifier) Issuer() string { - return v.issuer -} - -func (v verifier) MaxAgeIAT() time.Duration { - return v.mat -} - -func (v verifier) Offset() time.Duration { - return v.offset -} - -type provider struct { - op.OpenIDProvider - delegatedIssuerJsonWebKeySet jose.JSONWebKeySet - delegatedIssuer string - issuer string -} - -func (p provider) JWTProfileVerifier() JWTProfileVerifier { - return &verifier{ - issuer: p.issuer, - delegatedIssuer: p.delegatedIssuer, - mat: time.Hour, - offset: 0, - jsonWebKeySet: p.delegatedIssuerJsonWebKeySet, - } -} - -var _ JWTAuthorizationGrantExchanger = (*provider)(nil) - -func NewOpenIDProvider(storage op.Storage, issuer, delegatedIssuer string, delegatedIssuerJsonWebKeySet *jose.JSONWebKeySet) (op.OpenIDProvider, error) { - var p op.OpenIDProvider - - interceptors := make([]op.Option, 0) - if delegatedIssuer != "" { - interceptors = append(interceptors, op.WithHttpInterceptors(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Intercept token requests with grant_type of type bearer assertion - // as the library does not implement what we needs - if r.URL.Path == op.DefaultEndpoints.Token.Relative() && - r.FormValue("grant_type") == string(oidc.GrantTypeBearer) { - grantTypeBearer(issuer, &provider{ - issuer: issuer, - OpenIDProvider: p, - delegatedIssuerJsonWebKeySet: *delegatedIssuerJsonWebKeySet, - delegatedIssuer: delegatedIssuer, - }).ServeHTTP(w, r) - return - } - handler.ServeHTTP(w, r) - }) - - })) - } - interceptors = append(interceptors, op.WithAllowInsecure()) - - p, err := op.NewOpenIDProvider(issuer, &op.Config{ - CryptoKey: sha256.Sum256([]byte("test")), - DefaultLogoutRedirectURI: pathLoggedOut, - CodeMethodS256: true, - AuthMethodPost: true, - AuthMethodPrivateKeyJWT: true, - GrantTypeRefreshToken: true, - RequestObjectSupported: true, - SupportedUILocales: []language.Tag{language.English}, - }, storage, interceptors...) - return p, err -} diff --git a/ee/auth/pkg/oidc/router.go b/ee/auth/pkg/oidc/router.go deleted file mode 100644 index 8c7c077b71..0000000000 --- a/ee/auth/pkg/oidc/router.go +++ /dev/null @@ -1,50 +0,0 @@ -package oidc - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/op" -) - -const AuthorizeCallbackPath = "/authorize/callback" - -func AddRoutes(r chi.Router, provider op.OpenIDProvider, storage Storage, relyingParty rp.RelyingParty) { - r.Group(func(r chi.Router) { - if relyingParty != nil { - r.Use(func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == AuthorizeCallbackPath { - if code := r.URL.Query().Get("code"); code != "" { - authorizeCallbackHandler(provider, storage, relyingParty).ServeHTTP(w, r) - return - } else if err := r.URL.Query().Get("error"); err != "" { - authorizeErrorHandler().ServeHTTP(w, r) - return - } - } - h.ServeHTTP(w, r) - }) - }) - } - - r. - With(func(handler http.Handler) http.Handler { - // The otelchi middleware does not see matching route as it is matched in a subrouter - // So the span name terminated with just "/" - // This middleware make the hack - // We can do this because url does not contain any dynamic variables. - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - currentSpan := trace.SpanFromContext(r.Context()) - currentSpan.SetName(r.URL.Path) - currentSpan.SetAttributes(attribute.String("http.route", r.URL.Path)) - handler.ServeHTTP(w, r) - }) - }). - Mount("/", provider.HttpHandler()) - }) -} diff --git a/ee/auth/pkg/oidc/storage.go b/ee/auth/pkg/oidc/storage.go deleted file mode 100644 index bd2d1214f0..0000000000 --- a/ee/auth/pkg/oidc/storage.go +++ /dev/null @@ -1,549 +0,0 @@ -package oidc - -import ( - "context" - "crypto/rsa" - "fmt" - "strings" - "time" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/storage" - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/zitadel/oidc/v2/pkg/client/rp" - "github.com/zitadel/oidc/v2/pkg/oidc" - "github.com/zitadel/oidc/v2/pkg/op" - "golang.org/x/text/language" - "gopkg.in/go-jose/go-jose.v2" -) - -const ( - ExpirationToken2Legged = time.Hour - ExpirationToken3Legged = 5 * time.Minute -) - -type Storage interface { - SaveAuthRequest(ctx context.Context, request *auth.AuthRequest) error - FindAuthRequest(ctx context.Context, id string) (*auth.AuthRequest, error) - FindAuthRequestByCode(ctx context.Context, id string) (*auth.AuthRequest, error) - UpdateAuthRequest(ctx context.Context, request *auth.AuthRequest) error - UpdateAuthRequestCode(ctx context.Context, id string, code string) error - DeleteAuthRequest(ctx context.Context, id string) error - - SaveRefreshToken(ctx context.Context, token *auth.RefreshToken) error - FindRefreshToken(ctx context.Context, token string) (*auth.RefreshToken, error) - DeleteRefreshToken(ctx context.Context, token string) error - - SaveAccessToken(ctx context.Context, token *auth.AccessToken) error - FindAccessToken(ctx context.Context, token string) (*auth.AccessToken, error) - DeleteAccessToken(ctx context.Context, token string) error - DeleteAccessTokensForUserAndClient(ctx context.Context, userID string, clientID string) error - DeleteAccessTokensByRefreshToken(ctx context.Context, token string) error - - FindUser(ctx context.Context, id string) (*auth.User, error) - FindUserBySubject(ctx context.Context, subject string) (*auth.User, error) - SaveUser(ctx context.Context, user *auth.User) error - - FindClient(ctx context.Context, id string) (*auth.Client, error) -} - -type signingKey struct { - id string - algorithm jose.SignatureAlgorithm - key *rsa.PrivateKey -} - -func (s *signingKey) SignatureAlgorithm() jose.SignatureAlgorithm { - return s.algorithm -} - -func (s *signingKey) Key() interface{} { - return s.key -} - -func (s *signingKey) ID() string { - return s.id -} - -type publicKey struct { - signingKey -} - -func (s *publicKey) ID() string { - return s.id -} - -func (s *publicKey) Algorithm() jose.SignatureAlgorithm { - return s.algorithm -} - -func (s *publicKey) Use() string { - return "sig" -} - -func (s *publicKey) Key() interface{} { - return &s.key.PublicKey -} - -type Service struct { - Keys map[string]*rsa.PublicKey -} - -// getInfoFromRequest returns the clientID, authTime and amr depending on the op.TokenRequest type / implementation -func getInfoFromRequest(req op.TokenRequest) (clientID string, authTime time.Time, amr []string) { - authReq, ok := req.(*auth.AuthRequest) //Code Flow (with scope offline_access) - if ok { - return authReq.ApplicationID, authReq.AuthTime, authReq.GetAMR() - } - refreshReq, ok := req.(*auth.RefreshTokenRequest) //Refresh Token Request - if ok { - return refreshReq.ApplicationID, refreshReq.AuthTime, refreshReq.AMR - } - return "", time.Time{}, nil -} - -// Go workspaces force to use the same set of dependencies of other projects -// FCTL use a forked version og the oidc library -// We need to refine this forked version to make these methods optional -type storageFacade struct { - Storage - signingKey signingKey - relyingParty rp.RelyingParty - staticClients []auth.StaticClient -} - -func (s *storageFacade) GetRefreshTokenInfo(ctx context.Context, clientID string, token string) (userID string, tokenID string, err error) { - accessToken, err := s.FindAccessToken(ctx, token) - if err != nil { - return "", "", err - } - - return accessToken.UserID, accessToken.ID, nil -} - -func (s *storageFacade) SigningKey(ctx context.Context) (op.SigningKey, error) { - return &s.signingKey, nil -} - -func (s *storageFacade) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) { - return []jose.SignatureAlgorithm{s.signingKey.algorithm}, nil -} - -func (s *storageFacade) KeySet(ctx context.Context) ([]op.Key, error) { - return []op.Key{&publicKey{s.signingKey}}, nil -} - -func (s *storageFacade) SetUserinfoFromScopes(ctx context.Context, userinfo *oidc.UserInfo, userID, clientID string, scopes []string) error { - return s.setUserinfo(ctx, userinfo, userID, scopes) -} - -func (s *storageFacade) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserInfo, tokenID, subject, origin string) error { - token, err := s.Storage.FindAccessToken(ctx, tokenID) - if err != nil { - return err - } - return s.setUserinfo(ctx, userinfo, token.UserID, token.Scopes) -} - -func (s *storageFacade) SetIntrospectionFromToken(ctx context.Context, introspection *oidc.IntrospectionResponse, tokenID, subject, clientID string) error { - token, err := s.Storage.FindAccessToken(ctx, tokenID) - if err != nil { - return err - } - ok := false - for _, aud := range token.Audience { - if aud == clientID { - ok = true - break - } - } - if !ok { - client, err := s.findClient(ctx, clientID) - if err != nil { - return err - } - ok = client.IsTrusted() - } - if !ok { - return fmt.Errorf("token is not valid for this client") - } - - user, err := s.FindUser(ctx, token.UserID) - if err != nil { - return errors.Wrapf(err, "retrieving user: %s", token.UserID) - } - - for _, scope := range token.Scopes { - switch scope { - case oidc.ScopeOpenID: - introspection.Subject = token.UserID - case oidc.ScopeEmail: - introspection.Email = user.Email - introspection.EmailVerified = true // TODO: Get the information - case oidc.ScopeProfile: - // TODO: Support that - case oidc.ScopePhone: - // TODO: Support that ? - } - } - - introspection.Scope = oidc.SpaceDelimitedArray(token.Scopes) - introspection.ClientID = token.ApplicationID - introspection.Active = time.Now().After(token.Expiration) - return nil -} - -func (s *storageFacade) GetKeyByIDAndClientID(ctx context.Context, keyID, clientID string) (*jose.JSONWebKey, error) { - panic("not implemented") -} - -// CreateAuthRequest implements the op.Storage interface -// it will be called after parsing and validation of the authentication request -func (s *storageFacade) CreateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, userID string) (op.AuthRequest, error) { - request := auth.AuthRequest{ - CreatedAt: time.Now(), - ApplicationID: authReq.ClientID, - CallbackURI: authReq.RedirectURI, - TransferState: authReq.State, - Prompt: auth.PromptToInternal(authReq.Prompt), - UiLocales: auth.Array[language.Tag](authReq.UILocales), - LoginHint: authReq.LoginHint, - MaxAuthAge: auth.MaxAgeToInternal(authReq.MaxAge), - Scopes: auth.Array[string](authReq.Scopes), - ResponseType: authReq.ResponseType, - Nonce: authReq.Nonce, - CodeChallenge: &auth.OIDCCodeChallenge{ - Challenge: authReq.CodeChallenge, - Method: string(authReq.CodeChallengeMethod), - }, - ID: uuid.NewString(), - } - - if err := s.Storage.SaveAuthRequest(ctx, &request); err != nil { - return nil, err - } - - return &request, nil -} - -// AuthRequestByCode implements the op.Storage interface -// it will be called after parsing and validation of the token request (in an authorization code flow) -func (s *storageFacade) AuthRequestByCode(ctx context.Context, code string) (op.AuthRequest, error) { - return s.FindAuthRequestByCode(ctx, code) -} - -// SaveAuthCode implements the op.Storage interface -// it will be called after the authentication has been successful and before redirecting the user agent to the redirect_uri -// (in an authorization code flow) -func (s *storageFacade) SaveAuthCode(ctx context.Context, id string, code string) error { - return s.Storage.UpdateAuthRequestCode(ctx, id, code) -} - -// CreateAccessToken implements the op.Storage interface -// it will be called for all requests able to return an access token (Authorization Code Flow, Implicit Flow, JWT Profile, ...) -func (s *storageFacade) CreateAccessToken(ctx context.Context, request op.TokenRequest) (string, time.Time, error) { - var applicationID string - //if authenticated for an app (auth code / implicit flow) we must save the client_id to the token - authReq, ok := request.(*auth.AuthRequest) - if ok { - applicationID = authReq.ApplicationID - } - token, err := s.saveAccessToken(ctx, nil, applicationID, request.GetSubject(), request.GetAudience(), request.GetScopes()) - if err != nil { - return "", time.Time{}, err - } - return token.ID, token.Expiration, nil -} - -// CreateAccessAndRefreshTokens implements the op.Storage interface -// it will be called for all requests able to return an access and refresh token (Authorization Code Flow, Refresh Token Request) -func (s *storageFacade) CreateAccessAndRefreshTokens(ctx context.Context, request op.TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) { - //get the information depending on the request type / implementation - applicationID, authTime, amr := getInfoFromRequest(request) - - //if currentRefreshToken is empty (Code Flow) we will have to create a new refresh token - if currentRefreshToken == "" { - refreshToken, err := s.createRefreshToken(ctx, applicationID, request.GetSubject(), request.GetAudience(), request.GetScopes(), amr, authTime) - if err != nil { - return "", "", time.Time{}, err - } - accessToken, err := s.saveAccessToken(ctx, refreshToken, applicationID, request.GetSubject(), - request.GetAudience(), request.GetScopes()) - if err != nil { - return "", "", time.Time{}, err - } - return accessToken.ID, refreshToken.ID, accessToken.Expiration, nil - } - - //if we get here, the currentRefreshToken was not empty, so the call is a refresh token request - //we therefore will have to check the currentRefreshToken and renew the refresh token - refreshToken, err := s.renewRefreshToken(ctx, currentRefreshToken) - if err != nil { - return "", "", time.Time{}, err - } - accessToken, err := s.saveAccessToken(ctx, refreshToken, applicationID, request.GetSubject(), request.GetAudience(), request.GetScopes()) - if err != nil { - return "", "", time.Time{}, err - } - return accessToken.ID, refreshToken.ID, accessToken.Expiration, nil -} - -// TokenRequestByRefreshToken implements the op.Storage interface -// it will be called after parsing and validation of the refresh token request -func (s *storageFacade) TokenRequestByRefreshToken(ctx context.Context, refreshToken string) (op.RefreshTokenRequest, error) { - token, err := s.Storage.FindRefreshToken(ctx, refreshToken) - if err != nil { - return nil, fmt.Errorf("invalid refresh_token") - } - return auth.NewRefreshTokenRequest(*token), nil -} - -// TerminateSession implements the op.Storage interface -// it will be called after the user signed out, therefore the access and refresh token of the user of this client must be removed -func (s *storageFacade) TerminateSession(ctx context.Context, userID string, clientID string) error { - return s.Storage.DeleteAccessTokensForUserAndClient(ctx, userID, clientID) -} - -// RevokeToken implements the op.Storage interface -// it will be called after parsing and validation of the token revocation request -func (s *storageFacade) RevokeToken(ctx context.Context, tokenStr string, userID string, clientID string) *oidc.Error { - - accessToken, err := s.Storage.FindAccessToken(ctx, tokenStr) - if storage.IgnoreNotFoundError(err) != nil { - return oidc.ErrServerError().WithDescription(err.Error()) - } - if err == nil { - if accessToken.ApplicationID != clientID { - return oidc.ErrInvalidClient().WithDescription("token was not issued for this client") - } - if err := s.Storage.DeleteAccessToken(ctx, tokenStr); err != nil { - return oidc.ErrServerError().WithDescription(err.Error()) - } - return nil - } - - refreshToken, err := s.Storage.FindRefreshToken(ctx, tokenStr) - if storage.IgnoreNotFoundError(err) != nil { - return oidc.ErrServerError().WithDescription(err.Error()) - } - if err == nil { - if refreshToken.ApplicationID != clientID { - return oidc.ErrInvalidClient().WithDescription("token was not issued for this client") - } - if err := s.Storage.DeleteRefreshToken(ctx, tokenStr); err != nil { - return oidc.ErrServerError().WithDescription(err.Error()) - } - if err := s.Storage.DeleteAccessTokensByRefreshToken(ctx, tokenStr); err != nil { - return oidc.ErrServerError().WithDescription(err.Error()) - } - return nil - } - return nil -} - -func (s *storageFacade) findClient(ctx context.Context, clientID string) (Client, error) { - var client *auth.Client - for _, staticClient := range s.staticClients { - if staticClient.Id == clientID { - return &staticClient, nil - } - } - if client == nil { - var err error - client, err = s.Storage.FindClient(ctx, clientID) - if err != nil { - return nil, err - } - } - return client, nil -} - -// GetClientByClientID implements the op.Storage interface -// it will be called whenever information (type, redirect_uris, ...) about the client behind the client_id is needed -func (s *storageFacade) GetClientByClientID(ctx context.Context, clientID string) (op.Client, error) { - client, err := s.findClient(ctx, clientID) - if err != nil { - return nil, err - } - - return NewClientFacade(client, s.relyingParty), nil -} - -// AuthorizeClientIDSecret implements the op.Storage interface -// it will be called for validating the client_id, client_secret on token or introspection requests -func (s *storageFacade) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error { - client, err := s.findClient(ctx, clientID) - if err != nil { - return err - } - return client.ValidateSecret(clientSecret) -} - -// GetPrivateClaimsFromScopes implements the op.Storage interface -// it will be called for the creation of a JWT access token to assert claims for custom scopes -func (s *storageFacade) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) { - return map[string]interface{}{ - "scope": strings.Join(scopes, " "), - }, nil -} - -// ValidateJWTProfileScopes implements the op.Storage interface -// it will be called to validate the scopes of a JWT Profile Authorization Grant request -func (s *storageFacade) ValidateJWTProfileScopes(ctx context.Context, userID string, scopes []string) ([]string, error) { - return scopes, nil -} - -// Health implements the op.Storage interface -func (s *storageFacade) Health(ctx context.Context) error { - return nil -} - -// createRefreshToken will store a refresh_token in-memory based on the provided information -func (s *storageFacade) createRefreshToken(ctx context.Context, applicationID string, subject string, - audience []string, scopes []string, amr []string, authTime time.Time) (*auth.RefreshToken, error) { - token := auth.RefreshToken{ - ID: uuid.NewString(), - AuthTime: authTime, - AMR: amr, - ApplicationID: applicationID, - UserID: subject, - Audience: audience, - Expiration: time.Now().Add(5 * time.Hour), - Scopes: scopes, - } - if err := s.Storage.SaveRefreshToken(ctx, &token); err != nil { - return nil, err - } - return &token, nil -} - -// renewRefreshToken checks the provided refresh_token and creates a new one based on the current -func (s *storageFacade) renewRefreshToken(ctx context.Context, currentRefreshToken string) (*auth.RefreshToken, error) { - refreshToken, err := s.Storage.FindRefreshToken(ctx, currentRefreshToken) - if err != nil { - return nil, err - } - //deletes the refresh token and all access tokens which were issued based on this refresh token - if err := s.Storage.DeleteRefreshToken(ctx, currentRefreshToken); err != nil { - return nil, err - } - if err := s.Storage.DeleteAccessTokensByRefreshToken(ctx, currentRefreshToken); err != nil { - return nil, err - } - //creates a new refresh token based on the current one - refreshToken.ID = uuid.NewString() - - if err := s.SaveRefreshToken(ctx, refreshToken); err != nil { - return nil, err - } - - return refreshToken, nil -} - -// accessToken will store an access_token in-memory based on the provided information -func (s *storageFacade) saveAccessToken(ctx context.Context, refreshToken *auth.RefreshToken, applicationId, subject string, audience, scopes []string) (*auth.AccessToken, error) { - - expiration := ExpirationToken2Legged - if subject != "" { - expiration = ExpirationToken3Legged - } - - token := auth.AccessToken{ - ID: uuid.NewString(), - ApplicationID: applicationId, - UserID: subject, - Audience: audience, - Expiration: time.Now().Add(expiration), - Scopes: scopes, - RefreshTokenID: func() string { - if refreshToken == nil { - return "" - } - return refreshToken.ID - }(), - } - if err := s.Storage.SaveAccessToken(ctx, &token); err != nil { - return nil, err - } - return &token, nil -} - -// setUserinfo sets the info based on the user, scopes and if necessary the clientID -func (s *storageFacade) setUserinfo(ctx context.Context, userInfo *oidc.UserInfo, userID string, scopes []string) (err error) { - - user, err := s.Storage.FindUser(ctx, userID) - if err != nil { - return err - } - - for _, scope := range scopes { - switch scope { - case oidc.ScopeOpenID: - userInfo.Subject = userID - case oidc.ScopeEmail: - userInfo.Email = user.Email - userInfo.EmailVerified = true // TODO: Get the information - case oidc.ScopeProfile: - // TODO: Support that - case oidc.ScopePhone: - // TODO: Support that ? - } - } - return nil -} - -func (i *storageFacade) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) { - return i.FindAuthRequest(ctx, id) -} - -func (s *storageFacade) ClientCredentials(ctx context.Context, clientID, clientSecret string) (op.Client, error) { - client, err := s.findClient(ctx, clientID) - if err != nil { - return nil, err - } - return NewClientFacade(client, s.relyingParty), client.ValidateSecret(clientSecret) -} - -func (s *storageFacade) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (op.TokenRequest, error) { - - client, err := s.findClient(ctx, clientID) - if err != nil { - return nil, err - } - - allowedScopes := auth.Array[string]{} - -l: - for _, scope := range scopes { - for _, clientScope := range client.GetScopes() { - if clientScope == scope { - allowedScopes.Append(scope) - continue l - } - } - } - - return &auth.AuthRequest{ - ID: uuid.NewString(), - CreatedAt: time.Now(), - ApplicationID: clientID, - Scopes: allowedScopes, - }, nil -} - -var _ op.Storage = (*storageFacade)(nil) -var _ op.ClientCredentialsStorage = (*storageFacade)(nil) - -func NewStorageFacade(storage Storage, rp rp.RelyingParty, privateKey *rsa.PrivateKey, staticClients ...auth.StaticClient) *storageFacade { - return &storageFacade{ - Storage: storage, - signingKey: signingKey{ - id: "id", - algorithm: "RS256", - key: privateKey, - }, - relyingParty: rp, - staticClients: staticClients, - } -} diff --git a/ee/auth/pkg/oidc/templates/error.tmpl b/ee/auth/pkg/oidc/templates/error.tmpl deleted file mode 100644 index c0f3503369..0000000000 --- a/ee/auth/pkg/oidc/templates/error.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{.Error}} : {{.ErrorDescription}} diff --git a/ee/auth/pkg/refresh.go b/ee/auth/pkg/refresh.go deleted file mode 100644 index 1d3b45fb3f..0000000000 --- a/ee/auth/pkg/refresh.go +++ /dev/null @@ -1,59 +0,0 @@ -package auth - -import ( - "time" - - "github.com/uptrace/bun" -) - -type RefreshToken struct { - bun.BaseModel `bun:"table:refresh_tokens"` - - ID string `bun:",pk"` - Token string - AuthTime time.Time - AMR Array[string] `bun:"type:text"` - Audience Array[string] `bun:"type:text"` - UserID string - ApplicationID string - Expiration time.Time - Scopes Array[string] `bun:"type:text"` -} - -type RefreshTokenRequest struct { - RefreshToken -} - -func (r *RefreshTokenRequest) GetAMR() []string { - return r.AMR -} - -func (r *RefreshTokenRequest) GetAudience() []string { - return r.Audience -} - -func (r *RefreshTokenRequest) GetAuthTime() time.Time { - return r.AuthTime -} - -func (r *RefreshTokenRequest) GetClientID() string { - return r.ApplicationID -} - -func (r *RefreshTokenRequest) GetScopes() []string { - return r.Scopes -} - -func (r *RefreshTokenRequest) GetSubject() string { - return r.UserID -} - -func (r *RefreshTokenRequest) SetCurrentScopes(scopes []string) { - r.Scopes = scopes -} - -func NewRefreshTokenRequest(r RefreshToken) *RefreshTokenRequest { - return &RefreshTokenRequest{ - RefreshToken: r, - } -} diff --git a/ee/auth/pkg/request.go b/ee/auth/pkg/request.go deleted file mode 100644 index 011a27e117..0000000000 --- a/ee/auth/pkg/request.go +++ /dev/null @@ -1,113 +0,0 @@ -package auth - -import ( - "time" - - "github.com/uptrace/bun" - - "github.com/zitadel/oidc/v2/pkg/oidc" - "golang.org/x/text/language" -) - -type AuthRequest struct { - bun.BaseModel `bun:"table:auth_requests"` - - ID string `bun:",pk"` - CreatedAt time.Time - ApplicationID string - CallbackURI string - TransferState string - Prompt Array[string] `bun:"type:text"` - UiLocales Array[language.Tag] `bun:"type:text"` - LoginHint string - MaxAuthAge *time.Duration - Scopes Array[string] `bun:"type:text"` - ResponseType oidc.ResponseType - Nonce string - CodeChallenge *OIDCCodeChallenge `bun:"embed:"` - UserID string - AuthTime time.Time - Code string -} - -func (a *AuthRequest) GetID() string { - return a.ID -} - -func (a *AuthRequest) GetACR() string { - return "" //we won't handle acr in this example -} - -func (a *AuthRequest) GetAMR() []string { - return nil -} - -func (a *AuthRequest) GetAudience() []string { - return []string{a.ApplicationID} -} - -func (a *AuthRequest) GetAuthTime() time.Time { - return a.AuthTime -} - -func (a *AuthRequest) GetClientID() string { - return a.ApplicationID -} - -func (a *AuthRequest) GetCodeChallenge() *oidc.CodeChallenge { - return CodeChallengeToOIDC(a.CodeChallenge) -} - -func (a *AuthRequest) GetNonce() string { - return a.Nonce -} - -func (a *AuthRequest) GetRedirectURI() string { - return a.CallbackURI -} - -func (a *AuthRequest) GetResponseType() oidc.ResponseType { - return a.ResponseType -} - -func (a *AuthRequest) GetResponseMode() oidc.ResponseMode { - return "" //we won't handle response mode in this example -} - -func (a *AuthRequest) GetScopes() []string { - return a.Scopes -} - -func (a *AuthRequest) GetState() string { - return a.TransferState -} - -func (a *AuthRequest) GetSubject() string { - return a.UserID -} - -func (a *AuthRequest) Done() bool { - return a.UserID != "" -} - -func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string { - prompts := make([]string, len(oidcPrompt)) - for _, oidcPrompt := range oidcPrompt { - switch oidcPrompt { - case oidc.PromptNone, - oidc.PromptLogin, - oidc.PromptConsent, - oidc.PromptSelectAccount: - prompts = append(prompts, oidcPrompt) - } - } - return prompts -} - -func MaxAgeToInternal(maxAge *uint) *time.Duration { - if maxAge == nil { - return nil - } - dur := time.Duration(*maxAge) * time.Second - return &dur -} diff --git a/ee/auth/pkg/storage/errors.go b/ee/auth/pkg/storage/errors.go deleted file mode 100644 index 64fa848ab2..0000000000 --- a/ee/auth/pkg/storage/errors.go +++ /dev/null @@ -1,16 +0,0 @@ -package storage - -import ( - "errors" -) - -var ( - ErrNotFound = errors.New("not found") -) - -func IgnoreNotFoundError(err error) error { - if err == nil || err == ErrNotFound { - return nil - } - return err -} diff --git a/ee/auth/pkg/storage/sqlstorage/database.go b/ee/auth/pkg/storage/sqlstorage/database.go deleted file mode 100644 index 70b28844e1..0000000000 --- a/ee/auth/pkg/storage/sqlstorage/database.go +++ /dev/null @@ -1,160 +0,0 @@ -package sqlstorage - -import ( - "context" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/go-libs/migrations" - "github.com/uptrace/bun" -) - -const ( - Wallets = "wallets" - Orchestration = "orchestration" - Ledger = "ledger" - Payments = "payments" - Webhooks = "webhooks" - Auth = "auth" - Reconciliation = "reconciliation" - Search = "search" -) - -type Services []string - -var AllServices = Services{ - Wallets, - Orchestration, - Ledger, - Payments, - Webhooks, - Auth, - Reconciliation, - Search, -} - -func Migrate(ctx context.Context, db *bun.DB) error { - migrator := migrations.NewMigrator() - migrator.RegisterMigrations( - migrations.Migration{ - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - script := ` - DROP TABLE IF EXISTS client_scopes; - DROP TABLE IF EXISTS transient_scopes; - - CREATE TABLE IF NOT EXISTS access_tokens ( - id text NOT NULL, - application_id text, - user_id text, - audience text, - expiration timestamp with time zone, - scopes text, - refresh_token_id text - ); - CREATE TABLE IF NOT EXISTS auth_requests ( - id text NOT NULL, - created_at timestamp with time zone, - application_id text, - callback_uri text, - transfer_state text, - prompt text, - ui_locales text, - login_hint text, - max_auth_age bigint, - scopes text, - response_type text, - nonce text, - challenge text, - method text, - user_id text, - auth_time timestamp with time zone, - code text - ); - CREATE TABLE IF NOT EXISTS clients ( - id text NOT NULL, - public boolean, - redirect_uris text, - description text, - name text, - post_logout_redirect_uris text, - metadata text, - trusted boolean, - scopes text, - secrets text - ); - CREATE TABLE IF NOT EXISTS refresh_tokens ( - id text NOT NULL, - token text, - auth_time timestamp with time zone, - amr text, - audience text, - user_id text, - application_id text, - expiration timestamp with time zone, - scopes text - ); - CREATE TABLE IF NOT EXISTS users ( - id text NOT NULL, - subject text, - email text - ); - - ALTER TABLE ONLY users - DROP CONSTRAINT IF EXISTS users_subject_key; - - ALTER TABLE ONLY refresh_tokens - DROP CONSTRAINT IF EXISTS refresh_tokens_pkey; - - ALTER TABLE ONLY access_tokens - DROP CONSTRAINT IF EXISTS access_tokens_pkey; - - ALTER TABLE ONLY auth_requests - DROP CONSTRAINT IF EXISTS auth_requests_pkey; - - ALTER TABLE ONLY clients - DROP CONSTRAINT IF EXISTS clients_pkey; - - ALTER TABLE ONLY users - DROP CONSTRAINT IF EXISTS users_pkey; - - ALTER TABLE ONLY access_tokens - ADD CONSTRAINT access_tokens_pkey PRIMARY KEY (id); - - ALTER TABLE ONLY auth_requests - ADD CONSTRAINT auth_requests_pkey PRIMARY KEY (id); - - ALTER TABLE ONLY clients - ADD CONSTRAINT clients_pkey PRIMARY KEY (id); - - ALTER TABLE ONLY refresh_tokens - ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id); - - ALTER TABLE ONLY users - ADD CONSTRAINT users_pkey PRIMARY KEY (id); - - ALTER TABLE ONLY users - ADD CONSTRAINT users_subject_key UNIQUE (subject); - ` - _, err := tx.Exec(script) - return err - }, - }, - migrations.Migration{ - UpWithContext: func(ctx context.Context, tx bun.Tx) error { - scopes := auth.Array[string]{"openid"} - for _, service := range AllServices { - scopes = append(scopes, service+":read", service+":write") - } - _, err := tx.Exec( - ` - ALTER TABLE clients - ADD COLUMN IF NOT EXISTS scopes TEXT; - - UPDATE clients - SET scopes = ?; - `, scopes) - return err - }, - }, - ) - return migrator.Up(ctx, db) -} diff --git a/ee/auth/pkg/storage/sqlstorage/module.go b/ee/auth/pkg/storage/sqlstorage/module.go deleted file mode 100644 index 72e9d63902..0000000000 --- a/ee/auth/pkg/storage/sqlstorage/module.go +++ /dev/null @@ -1,40 +0,0 @@ -package sqlstorage - -import ( - "context" - "crypto/rsa" - - "github.com/formancehq/go-libs/logging" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/bun/bunconnect" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/oidc" - "github.com/formancehq/go-libs/health" - "github.com/zitadel/oidc/v2/pkg/op" - "go.uber.org/fx" -) - -func Module(connectionOptions bunconnect.ConnectionOptions, key *rsa.PrivateKey, debug bool, staticClients ...auth.StaticClient) fx.Option { - return fx.Options( - bunconnect.Module(connectionOptions, debug), - fx.Invoke(func(lc fx.Lifecycle, db *bun.DB) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - logging.FromContext(ctx).Info("Migrate tables") - - return Migrate(ctx, db) - }, - }) - }), - fx.Supply(key), - fx.Supply(staticClients), - fx.Provide(fx.Annotate(New, - fx.As(new(oidc.Storage)), - )), - health.ProvideHealthCheck(func(storage op.Storage) health.NamedCheck { - return health.NewNamedCheck("Database", health.CheckFn(storage.Health)) - }), - ) -} diff --git a/ee/auth/pkg/storage/sqlstorage/storage.go b/ee/auth/pkg/storage/sqlstorage/storage.go deleted file mode 100644 index 0b91672828..0000000000 --- a/ee/auth/pkg/storage/sqlstorage/storage.go +++ /dev/null @@ -1,246 +0,0 @@ -package sqlstorage - -import ( - "context" - "database/sql" - - "github.com/pkg/errors" - "github.com/uptrace/bun" - - auth "github.com/formancehq/auth/pkg" - "github.com/formancehq/auth/pkg/oidc" - "github.com/formancehq/auth/pkg/storage" - "github.com/zitadel/oidc/v2/pkg/op" -) - -func mapSqlError(err error) error { - if errors.Is(err, sql.ErrNoRows) { - return storage.ErrNotFound - } - return err -} - -var _ oidc.Storage = (*Storage)(nil) - -type Storage struct { - db *bun.DB -} - -func (s *Storage) CreateUser(ctx context.Context, user *auth.User) error { - _, err := s.db.NewInsert().Model(user).Exec(ctx) - return err -} - -func (s *Storage) FindUserByEmail(ctx context.Context, email string) (*auth.User, error) { - user := &auth.User{} - if err := s.db.NewSelect().Model(user).Where("email = ?", email).Scan(ctx); err != nil { - return nil, err - } - return user, nil -} - -func New(db *bun.DB) *Storage { - return &Storage{ - db: db, - } -} - -// AuthRequestByID implements the op.Storage interface -// it will be called after the Login UI redirects back to the OIDC endpoint -func (s *Storage) AuthRequestByID(ctx context.Context, id string) (op.AuthRequest, error) { - request := &auth.AuthRequest{} - if err := s.db.NewSelect().Model(request).Where("id = ?", id).Limit(1).Scan(ctx); err != nil { - return nil, err - } - return request, nil -} - -func (s *Storage) AuthRequestByCode(ctx context.Context, code string) (op.AuthRequest, error) { - request := &auth.AuthRequest{} - if err := s.db.NewSelect().Model(request).Where("code = ?", code).Limit(1).Scan(ctx); err != nil { - return nil, err - } - return request, nil -} - -func (s *Storage) SaveAuthCode(ctx context.Context, id string, code string) error { - _, err := s.db.NewUpdate(). - Model(&auth.AuthRequest{}). - Set("code = ?", code). - Where("id = ?", id). - Exec(ctx) - return err -} - -func (s *Storage) SaveClient(ctx context.Context, client *auth.Client) error { - _, err := s.db.NewInsert().Model(client).Exec(ctx) - return err -} - -func (s *Storage) SaveAuthRequest(ctx context.Context, request *auth.AuthRequest) error { - _, err := s.db.NewInsert().Model(request).Exec(ctx) - return err -} - -func (s *Storage) FindAuthRequest(ctx context.Context, id string) (*auth.AuthRequest, error) { - ret := &auth.AuthRequest{} - err := s.db.NewSelect(). - Model(ret). - Where("id = ?", id). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) FindAuthRequestByCode(ctx context.Context, code string) (*auth.AuthRequest, error) { - ret := &auth.AuthRequest{} - err := s.db.NewSelect(). - Model(ret). - Where("code = ?", code). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) UpdateAuthRequest(ctx context.Context, request *auth.AuthRequest) error { - _, err := s.db.NewUpdate(). - Model(request). - Where("id = ?", request.ID). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) UpdateAuthRequestCode(ctx context.Context, id string, code string) error { - _, err := s.db.NewUpdate(). - Model(&auth.AuthRequest{}). - Where("id = ?", id). - Set("code = ?", code). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) DeleteAuthRequest(ctx context.Context, id string) error { - _, err := s.db.NewDelete(). - Model(&auth.AuthRequest{}). - Where("id = ?", id). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) SaveRefreshToken(ctx context.Context, token *auth.RefreshToken) error { - _, err := s.db.NewInsert().Model(token).Exec(ctx) - return err -} - -func (s *Storage) FindRefreshToken(ctx context.Context, token string) (*auth.RefreshToken, error) { - ret := &auth.RefreshToken{} - err := s.db.NewSelect(). - Model(ret). - Where("id = ?", token). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) DeleteRefreshToken(ctx context.Context, token string) error { - _, err := s.db.NewDelete(). - Model(&auth.RefreshToken{}). - Where("id = ?", token). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) SaveAccessToken(ctx context.Context, token *auth.AccessToken) error { - _, err := s.db.NewInsert().Model(token).Exec(ctx) - return err -} - -func (s *Storage) FindAccessToken(ctx context.Context, token string) (*auth.AccessToken, error) { - ret := &auth.AccessToken{} - err := s.db.NewSelect(). - Model(ret). - Where("id = ?", token). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) DeleteAccessToken(ctx context.Context, token string) error { - _, err := s.db.NewDelete(). - Model(&auth.AccessToken{}). - Where("id = ?", token). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) DeleteAccessTokensForUserAndClient(ctx context.Context, userID string, clientID string) error { - _, err := s.db.NewDelete(). - Model(&auth.AccessToken{}). - Where("user_id = ? and application_id = ?", userID, clientID). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) DeleteAccessTokensByRefreshToken(ctx context.Context, token string) error { - _, err := s.db.NewDelete(). - Model(&auth.AccessToken{}). - Where("refresh_token_id = ?", token). - Exec(ctx) - return mapSqlError(err) -} - -func (s *Storage) FindUser(ctx context.Context, id string) (*auth.User, error) { - ret := &auth.User{} - err := s.db.NewSelect(). - Model(ret). - Where("id = ?", id). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) FindClient(ctx context.Context, id string) (*auth.Client, error) { - ret := &auth.Client{} - err := s.db.NewSelect(). - Model(ret). - Where("id = ?", id). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} - -func (s *Storage) SaveUser(ctx context.Context, user *auth.User) error { - _, err := s.db.NewInsert().Model(user).Exec(ctx) - return err -} - -func (s *Storage) FindUserBySubject(ctx context.Context, subject string) (*auth.User, error) { - ret := &auth.User{} - err := s.db.NewSelect(). - Model(ret). - Where("subject = ?", subject). - Limit(1). - Scan(ctx) - if err != nil { - return nil, mapSqlError(err) - } - return ret, nil -} diff --git a/ee/auth/pkg/token.go b/ee/auth/pkg/token.go deleted file mode 100644 index 57c8822bcd..0000000000 --- a/ee/auth/pkg/token.go +++ /dev/null @@ -1,19 +0,0 @@ -package auth - -import ( - "time" - - "github.com/uptrace/bun" -) - -type AccessToken struct { - bun.BaseModel `bun:"table:access_tokens"` - - ID string `bun:",pk"` - ApplicationID string - UserID string - Audience Array[string] `bun:"type:text"` - Expiration time.Time - Scopes Array[string] `bun:"type:text"` - RefreshTokenID string `json:"refreshTokenID"` -} diff --git a/ee/auth/pkg/user.go b/ee/auth/pkg/user.go deleted file mode 100644 index 81d507e6e4..0000000000 --- a/ee/auth/pkg/user.go +++ /dev/null @@ -1,11 +0,0 @@ -package auth - -import "github.com/uptrace/bun" - -type User struct { - bun.BaseModel `bun:"table:users"` - - ID string `json:"id" bun:",pk"` - Subject string `json:"subject" bun:",unique"` - Email string `json:"email"` -} diff --git a/ee/auth/scratch.Dockerfile b/ee/auth/scratch.Dockerfile deleted file mode 100644 index fdadc465ad..0000000000 --- a/ee/auth/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY auth /usr/bin/auth -ENV OTEL_SERVICE_NAME auth -ENTRYPOINT ["/usr/bin/auth"] -CMD ["serve"] diff --git a/ee/gateway/.gitignore b/ee/gateway/.gitignore deleted file mode 100644 index 41e31f7274..0000000000 --- a/ee/gateway/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.idea -vendor -gateway diff --git a/ee/gateway/.goreleaser.yml b/ee/gateway/.goreleaser.yml deleted file mode 100644 index 64133f3ec6..0000000000 --- a/ee/gateway/.goreleaser.yml +++ /dev/null @@ -1,38 +0,0 @@ -project_name: gateway -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: gateway - id: gateway - ldflags: - - -X github.com/formancehq/stack/components/gateway/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/stack/components/gateway/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/stack/components/gateway/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - gateway - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) \ No newline at end of file diff --git a/ee/gateway/Caddyfile b/ee/gateway/Caddyfile deleted file mode 100644 index e549d75813..0000000000 --- a/ee/gateway/Caddyfile +++ /dev/null @@ -1,179 +0,0 @@ -(cors) { - header { - Access-Control-Allow-Methods "GET,OPTIONS,PUT,POST,DELETE,HEAD,PATCH" - Access-Control-Allow-Headers content-type - Access-Control-Max-Age 100 - Access-Control-Allow-Origin * - } -} - -(handle_route_without_auth) { - # handle does not strips the prefix from the request path - handle {args.0}/* { - reverse_proxy {args.1} - - import cors - - } -} - -(handle_path_route_with_auth) { - # handle_path automatically strips the prefix from the request path - handle_path {args.0}* { - reverse_proxy {args.1} - - import cors - - import auth - - } -} - -(handle_route_with_auth) { - # handle_path automatically strips the prefix from the request path - handle {args.0} { - reverse_proxy {args.2} - uri strip_prefix {args.1} - - import cors - import auth - - } -} - -(handle_path_route_without_auth) { - # handle_path automatically strips the prefix from the request path - handle_path {args.0}* { - reverse_proxy {args.1} - - import cors - - } -} - -(payments) { - @transferinitiationwritermatcher { - path {args.0}/transfer-initiations* - method POST DELETE - } - - @transferinitiationreadermatcher { - path {args.0}/transfer-initiation* - method GET - } - - @bankaccountswritermatcher { - path {args.0}/bank-accounts* - method POST - } - - @bankaccountsreadermatcher { - path {args.0}/bank-accounts* - method GET - } - - @connectorsmatcher { - path {args.0}/connectors* - } - - @configmatcher { - path {args.0}/configs* - } - - @accountsmatcher { - path {args.0}/accounts* - } - - import handle_route_with_auth @transferinitiationreadermatcher {args.0} {args.1} - import handle_route_with_auth @bankaccountsreadermatcher {args.0} {args.1} - import handle_route_with_auth @accountsmatcher {args.0} {args.1} - - import handle_route_with_auth @bankaccountswritermatcher {args.0} {args.2} - import handle_route_with_auth @transferinitiationwritermatcher {args.0} {args.2} - import handle_route_with_auth @connectorsmatcher {args.0} {args.2} - import handle_route_with_auth @configmatcher {args.0} {args.2} - - # All other requests on the api - import handle_path_route_with_auth {args.0} {args.1} -} - -(auth) { - auth { - issuer http://localhost/api/auth - - read_key_set_max_retries 10 - } -} - -(audit) { - audit { - # Kafka publisher - publisher_kafka_broker {$PUBLISHER_KAFKA_BROKER:redpanda:29092} - publisher_kafka_enabled {$PUBLISHER_KAFKA_ENABLED:false} - publisher_kafka_tls_enabled {$PUBLISHER_KAFKA_TLS_ENABLED:false} - publisher_kafka_sasl_enabled {$PUBLISHER_KAFKA_SASL_ENABLED:false} - publisher_kafka_sasl_username {$PUBLISHER_KAFKA_SASL_USERNAME} - publisher_kafka_sasl_password {$PUBLISHER_KAFKA_SASL_PASSWORD} - publisher_kafka_sasl_mechanism {$PUBLISHER_KAFKA_SASL_MECHANISM} - publisher_kafka_sasl_scram_sha_size {$PUBLISHER_KAFKA_SASL_SCRAM_SHA_SIZE} - - # Nats publisher - publisher_nats_enabled {$PUBLISHER_NATS_ENABLED:true} - publisher_nats_url {$PUBLISHER_NATS_URL:nats://nats:4222} - publisher_nats_client_id {$PUBLISHER_NATS_CLIENT_ID:gateway} - } -} - -{ - # Many directives manipulate the HTTP handler chain and the order in which - # those directives are evaluated matters. So the jwtauth directive must be - # ordered. - # c.f. https://caddyserver.com/docs/caddyfile/directives#directive-order - order auth before basicauth - order versions after metrics - order audit after encode - - # Local env dev config - debug -} - -localhost:80 { - tracing { - span gateway - } - - import handle_route_without_auth "/api/auth/dex" "127.0.0.1:5556" - import handle_path_route_without_auth "/api/auth" "127.0.0.1:8083" - import handle_path_route_with_auth "/api/ledger" "127.0.0.1:3068" - import handle_path_route_with_auth "/api/wallets" "127.0.0.1:8081" - import handle_path_route_with_auth "/api/search" "127.0.0.1:8080" - import payments "/api/payments" "127.0.0.1:8082" "127.0.0.1:8087" - import handle_path_route_with_auth "/api/webhooks" "127.0.0.1:8084" - import audit - - handle /versions { - versions { - region "local" - env "local" - endpoints { - auth http://127.0.0.1:8083/_info http://127.0.0.1:8083/_healthcheck - ledger http://127.0.0.1:3068/_info http://127.0.0.1:3068/_healthcheck - wallets http://127.0.0.1:8081/_info http://127.0.0.1:8081/_healthcheck - paymentsapi http://127.0.0.1:8082/_info http://127.0.0.1:8082/_healthcheck - paymentsconnectors http://127.0.0.1:8087/_info http://127.0.0.1:8087/_healthcheck - search http://127.0.0.1:8080/_info http://127.0.0.1:8080/_healthcheck - webhooks http://127.0.0.1:8084/_info http://127.0.0.1:8084/_healthcheck - } - } - } - - handle /api/* { - respond "Bad Gateway" 502 - } - - # handle all other requests - handle { - reverse_proxy 127.0.0.1:3000 - import cors - } -} diff --git a/ee/gateway/Earthfile b/ee/gateway/Earthfile deleted file mode 100644 index e28dbe08da..0000000000 --- a/ee/gateway/Earthfile +++ /dev/null @@ -1,79 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT .. AS ee - -FROM core+base-image - -sources: - WORKDIR src - WORKDIR /src/ee/gateway - COPY go.* . - COPY --dir internal . - COPY --dir pkg . - COPY main.go Caddyfile . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/gateway - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/usr/bin/caddy"] - CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] - COPY Caddyfile /etc/caddy/Caddyfile - COPY (+compile/main) /usr/bin/caddy - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=gateway --REPOSITORY=${REPOSITORY} --TAG=$tag - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"gateway\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=gateway -lint: - FROM core+builder-image - COPY (+sources/*) /src - COPY --pass-args +tidy/go.* . - WORKDIR /src/ee/gateway - DO --pass-args stack+GO_LINT - SAVE ARTIFACT internal AS LOCAL internal - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT main.go AS LOCAL main.go - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/gateway - DO --pass-args core+GO_TESTS - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - -openapi: - COPY ./openapi.yaml . - SAVE ARTIFACT ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/ee/gateway - DO --pass-args stack+GO_TIDY - -release: - BUILD --pass-args stack+goreleaser --path=ee/gateway \ No newline at end of file diff --git a/ee/gateway/build.Dockerfile b/ee/gateway/build.Dockerfile deleted file mode 100644 index 9fb630cf15..0000000000 --- a/ee/gateway/build.Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -ADD https://raw.githubusercontent.com/formancehq/stack/main/ee/gateway/Caddyfile /etc/caddy/Caddyfile -COPY gateway /usr/bin/caddy -ENV OTEL_SERVICE_NAME gateway -ENTRYPOINT ["/usr/bin/caddy"] -CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] diff --git a/ee/gateway/go.mod b/ee/gateway/go.mod deleted file mode 100644 index b485cde7d5..0000000000 --- a/ee/gateway/go.mod +++ /dev/null @@ -1,216 +0,0 @@ -module github.com/formancehq/stack/components/gateway - -go 1.22.0 - -toolchain go1.22.7 - -// caddy use an old version of ristretto while go libs use v1.0.0 -replace github.com/dgraph-io/ristretto v1.0.0 => github.com/dgraph-io/ristretto v0.1.0 - -require ( - github.com/IBM/sarama v1.43.3 - github.com/ThreeDotsLabs/watermill v1.3.7 - github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 - github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 - github.com/caddyserver/caddy/v2 v2.8.4 - github.com/formancehq/go-libs v1.7.1 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.6.0 - github.com/nats-io/nats.go v1.37.0 - github.com/pkg/errors v0.9.1 - github.com/xdg-go/scram v1.1.2 - go.uber.org/zap v1.27.0 - golang.org/x/sync v0.8.0 -) - -require ( - filippo.io/edwards25519 v1.1.0 // indirect - github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 // indirect - github.com/ajg/form v1.5.1 // indirect - github.com/alecthomas/chroma/v2 v2.13.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect - github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/caddyserver/certmagic v0.21.3 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash v1.1.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chzyer/readline v1.5.1 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/dgraph-io/badger v1.6.2 // indirect - github.com/dgraph-io/badger/v2 v2.2007.4 // indirect - github.com/dgraph-io/ristretto v1.0.0 // indirect - github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/dlclark/regexp2 v1.11.0 // indirect - github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect - github.com/eapache/queue v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect - github.com/go-chi/render v1.0.3 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-kit/kit v0.13.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/golang/glog v1.2.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/cel-go v0.20.1 // indirect - github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect - github.com/google/go-tpm v0.9.0 // indirect - github.com/google/go-tspi v0.3.0 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/huandu/xstrings v1.3.3 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.3 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx/v4 v4.18.3 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/libdns/libdns v0.2.2 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/manifoldco/promptui v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mholt/acmez/v2 v2.0.1 // indirect - github.com/miekg/dns v1.1.59 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-ps v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pires/go-proxyproto v0.7.0 // indirect - github.com/prometheus/client_golang v1.20.2 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/quic-go v0.44.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/rs/xid v1.5.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shopspring/decimal v1.4.0 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/slackhq/nebula v1.6.1 // indirect - github.com/smallstep/certificates v0.26.1 // indirect - github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect - github.com/smallstep/nosql v0.6.1 // indirect - github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect - github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect - github.com/smallstep/truststore v0.13.0 // indirect - github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stoewer/go-strcase v1.2.0 // indirect - github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun v1.2.3 // indirect - github.com/uptrace/bun/dialect/pgdialect v1.2.3 // indirect - github.com/uptrace/bun/extra/bunotel v1.2.3 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect - github.com/urfave/cli v1.22.14 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/xo/dburl v0.23.2 // indirect - github.com/yuin/goldmark v1.7.1 // indirect - github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect - github.com/zeebo/blake3 v0.2.3 // indirect - go.etcd.io/bbolt v1.3.9 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 // indirect - go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect - go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.step.sm/cli-utils v0.9.0 // indirect - go.step.sm/crypto v0.45.0 // indirect - go.step.sm/linkedca v0.20.1 // indirect - go.uber.org/automaxprocs v1.5.3 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/fx v1.22.2 // indirect - go.uber.org/mock v0.4.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap/exp v0.2.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.0 // indirect -) diff --git a/ee/gateway/go.sum b/ee/gateway/go.sum deleted file mode 100644 index 8f6c235881..0000000000 --- a/ee/gateway/go.sum +++ /dev/null @@ -1,814 +0,0 @@ -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= -cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= -cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= -cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= -cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= -cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= -cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= -github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 h1:M0iYM5HsGcoxtiQqprRlYZNZnGk3w5LsE9RbC2R8myQ= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1/go.mod h1:RwGHEzGsEEXC/rQNLWQqR83+WPlABgOgnv2kTB56Y4Y= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 h1:ud+4txnRgtr3kZXfXZ5+C7kVQEvsLc5HSNUEa0g+X1Q= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5/go.mod h1:t4o+4A6GB+XC8WL3DandhzPwd265zQuyWMQC/I+WIOU= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 h1:afAkAFzeooBRQvxElR+6xoigXKCukcZXnE9ACxhwlPI= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1/go.mod h1:stjbT+s4u/s5ime5jdIyvPyjBGwGeJewIN7jxH8gp4k= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= -github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= -github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= -github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= -github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= -github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk= -github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw= -github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= -github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= -github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= -github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= -github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= -github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= -github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= -github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= -github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= -github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= -github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= -github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= -github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= -github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= -github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= -github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= -github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= -github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= -github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= -github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw= -github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.20 h1:CXDTYNHeBiAKBTAIP2gjpgbWap2GhATnTLgP8etyvEI= -github.com/nats-io/nats-server/v2 v2.10.20/go.mod h1:hgcPnoUtMfxz1qVOvLZGurVypQ+Cg6GXVXjG53iHk+M= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= -github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= -github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= -github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= -github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= -github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM= -github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI= -github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= -github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= -github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= -github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= -github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= -github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= -github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= -github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= -github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= -github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= -github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= -github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= -github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= -github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU= -github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc= -github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= -github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= -github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= -github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w= -go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y= -go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c= -go.opentelemetry.io/contrib/propagators/aws v1.17.0/go.mod h1:pAlCYRWff4uGqRXOVn3WP8pDZ5E0K56bEoG7a1VSL4k= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y= -go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= -go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0= -go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= -go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= -go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= -go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= -go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= -go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs= -go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= -golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20220310020820-b874c991c1a5/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-20220715151400-c0bba94af5f8/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -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-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= -google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= -google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= -howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/ee/gateway/internal/audit/messages/audit.go b/ee/gateway/internal/audit/messages/audit.go deleted file mode 100644 index d0d82ca9c7..0000000000 --- a/ee/gateway/internal/audit/messages/audit.go +++ /dev/null @@ -1,100 +0,0 @@ -package messages - -import ( - "fmt" - "net/http" - "strings" - "time" - - "github.com/golang-jwt/jwt/v5" - - "github.com/formancehq/go-libs/publish" - "github.com/google/uuid" - "go.uber.org/zap" -) - -const ( - EventVersion = "v1" - EventApp = "gateway" - EventTypeAudit = "AUDIT" -) - -type HttpRequest struct { - Method string `json:"method"` - Path string `json:"path"` - Host string `json:"host"` - Header http.Header `json:"header"` - Body string `json:"body,omitempty"` -} - -type HttpResponse struct { - StatusCode int `json:"status_code"` - Headers http.Header `json:"headers"` - Body string `json:"body,omitempty"` -} - -func NewHttpResponse( - statusCode int, - headers http.Header, - body string, -) HttpResponse { - return HttpResponse{ - StatusCode: statusCode, - Headers: headers, - Body: body, - } -} - -type Payload struct { - ID string `json:"id"` - Identity string `json:"identity"` - Request HttpRequest `json:"request"` - Response HttpResponse `json:"response"` -} - -func NewAuditMessagePayload( - logger *zap.Logger, - request HttpRequest, - response HttpResponse, -) publish.EventMessage { - identity := "" - - if request.Header != nil { - if authorizationHeader := request.Header.Get("Authorization"); authorizationHeader != "" && strings.HasPrefix(strings.ToLower(authorizationHeader), "bearer ") { - - tokenString := strings.Replace(strings.Replace(authorizationHeader, "Bearer ", "", 1), "bearer ", "", 1) - token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) - if err != nil { - logger.Error(fmt.Sprintf("error for Parse %s", err)) - } - if token != nil { - if claims, ok := token.Claims.(jwt.MapClaims); ok { - identity = fmt.Sprint(claims["sub"]) - } else { - logger.Error(fmt.Sprintf("error get claims JWT token: %s", err)) - } - } - } - - request.Header.Del("Authorization") - } - - if request.Path == "/api/auth/oauth/token" { - response.Body = "" - } - - payload := Payload{ - ID: uuid.New().String(), - Identity: identity, - Request: request, - Response: response, - } - - return publish.EventMessage{ - Date: time.Now().UTC(), - App: EventApp, - Version: EventVersion, - Type: EventTypeAudit, - Payload: payload, - } -} diff --git a/ee/gateway/main.go b/ee/gateway/main.go deleted file mode 100644 index a81f24c55c..0000000000 --- a/ee/gateway/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - caddycmd "github.com/caddyserver/caddy/v2/cmd" - // plug in Caddy modules here - _ "github.com/caddyserver/caddy/v2/modules/standard" - _ "github.com/formancehq/stack/components/gateway/pkg/plugins" -) - -func main() { - caddycmd.Main() -} diff --git a/ee/gateway/openapi.yaml b/ee/gateway/openapi.yaml deleted file mode 100644 index db08a547dc..0000000000 --- a/ee/gateway/openapi.yaml +++ /dev/null @@ -1,50 +0,0 @@ -openapi: 3.0.3 -info: - title: Gateway API - version: latest -paths: - /versions: - get: - summary: Show stack version information - operationId: getVersions - security: - - NoAuthorization: [] - - Authorization: [] - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/GetVersionsResponse' - -components: - schemas: - Version: - type: object - properties: - name: - type: string - version: - type: string - health: - type: boolean - required: - - name - - version - - health - GetVersionsResponse: - type: object - properties: - region: - type: string - env: - type: string - versions: - type: array - items: - $ref: '#/components/schemas/Version' - required: - - region - - env - - versions \ No newline at end of file diff --git a/ee/gateway/pkg/plugins/audit.go b/ee/gateway/pkg/plugins/audit.go deleted file mode 100644 index 059f227da6..0000000000 --- a/ee/gateway/pkg/plugins/audit.go +++ /dev/null @@ -1,421 +0,0 @@ -package plugins - -import ( - "bytes" - "fmt" - "io" - "net/http" - "os" - "strconv" - "sync" - "time" - - "github.com/IBM/sarama" - "github.com/ThreeDotsLabs/watermill" - "github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka" - wNats "github.com/ThreeDotsLabs/watermill-nats/v2/pkg/nats" - "github.com/nats-io/nats.go" - "github.com/pkg/errors" - "github.com/xdg-go/scram" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" - "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - "github.com/formancehq/stack/components/gateway/internal/audit/messages" - - "go.uber.org/zap" -) - -func init() { - caddy.RegisterModule(Audit{}) - httpcaddyfile.RegisterHandlerDirective("audit", parseAuditCaddyfile) -} - -type Audit struct { - logger *zap.Logger `json:"-"` - bufPool *sync.Pool `json:"-"` - publisher message.Publisher `json:"-"` - - TopicName string `json:"topic_name,omitempty"` - - PublisherKafkaBroker string `json:"publisher_kafka_broker,omitempty"` - PublisherKafkaEnabled bool `json:"publisher_kafka_enabled,omitempty"` - PublisherKafkaTLSEnabled bool `json:"publisher_kafka_tls_enabled,omitempty"` - PublisherKafkaSASLEnabled bool `json:"publisher_kafka_sasl_enabled,omitempty"` - PublisherKafkaSASLUsername string `json:"publisher_kafka_sasl_username,omitempty"` - PublisherKafkaSASLPassword string `json:"publisher_kafka_sasl_password,omitempty"` - PublisherKafkaSASLMechanism string `json:"publisher_kafka_sasl_mechanism,omitempty"` - PublisherKafkaSASLScramSHASize int `json:"publisher_kafka_sasl_scram_sha_size,omitempty"` - - PublisherNatsEnabled bool `json:"publisher_nats_enabled,omitempty"` - PublisherNatsURL string `json:"publisher_nats_url,omitempty"` - PublisherNatsClientId string `json:"publisher_nats_client_id,omitempty"` - PublisherNatsMaxReconnects int `json:"publisher_nats_max_reconnects,omitempty"` - PublisherNatsMaxReconnectsWait time.Duration `json:"publisher_nats_max_reconnects_wait,omitempty"` -} - -// Implements the caddy.Module interface. -func (Audit) CaddyModule() caddy.ModuleInfo { - return caddy.ModuleInfo{ - ID: "http.handlers.audit", - New: func() caddy.Module { return new(Audit) }, - } -} - -func parseAuditCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { - a := new(Audit) - for h.Next() { - for h.NextBlock(0) { - key := h.Val() - switch key { - case "publisher_kafka_enabled": - var err error - a.PublisherKafkaEnabled, err = parseBool(h.Dispenser) - if err != nil { - return nil, h.Errf("failed to parse publisher_kafka_enabled: %v", err) - } - - case "publisher_kafka_broker": - if !h.AllArgs(&a.PublisherKafkaBroker) { - return nil, h.Errf("expected one string value for kafka_broker") - } - - case "publisher_kafka_tls_enabled": - var err error - a.PublisherKafkaTLSEnabled, err = parseBool(h.Dispenser) - if err != nil { - return nil, h.Errf("failed to parse publisher_kafka_tls_enabled: %v", err) - } - - case "publisher_kafka_sasl_enabled": - var err error - a.PublisherKafkaSASLEnabled, err = parseBool(h.Dispenser) - if err != nil { - return nil, h.Errf("failed to parse publisher_kafka_sasl_enabled: %v", err) - } - - case "publisher_kafka_sasl_username": - if !h.AllArgs(&a.PublisherKafkaSASLUsername) { - return nil, h.Errf("expected one string value for publisher kafka sasl username") - } - - case "publisher_kafka_sasl_password": - if !h.AllArgs(&a.PublisherKafkaSASLPassword) { - return nil, h.Errf("expected one string value for publisher kafka sasl password") - } - - case "publisher_kafka_sasl_mechanism": - if !h.AllArgs(&a.PublisherKafkaSASLMechanism) { - return nil, h.Errf("expected one string value for publisher kafka sasl mechanism") - } - - case "publisher_kafka_sasl_scram_sha_size": - var publisherKafkaSaslScramShaSize string - if !h.AllArgs(&publisherKafkaSaslScramShaSize) { - return nil, h.Errf("expected one boolean value") - } - - res, err := strconv.ParseInt(publisherKafkaSaslScramShaSize, 10, 32) - if err != nil { - return nil, h.Errf("failed to parse publisher_kafka_sasl_scram_sha_size: %v", err) - } - a.PublisherKafkaSASLScramSHASize = int(res) - - case "publisher_nats_enabled": - var err error - a.PublisherNatsEnabled, err = parseBool(h.Dispenser) - if err != nil { - return nil, h.Errf("failed to parse publisher_nats_enabled: %v", err) - } - - case "publisher_nats_url": - if !h.AllArgs(&a.PublisherNatsURL) { - return nil, h.Errf("expected one string value for publisher_nats_url") - } - - case "publisher_nats_client_id": - if !h.AllArgs(&a.PublisherNatsClientId) { - return nil, h.Errf("expected one string value for publisher_nats_client_id") - } - case "publisher_nats_max_reconnects": - var publisherNatsMaxReconnects string - if !h.AllArgs(&publisherNatsMaxReconnects) { - return nil, h.Errf("expected one boolean value") - } - - res, err := strconv.ParseInt(publisherNatsMaxReconnects, 10, 32) - if err != nil { - return nil, h.Errf("failed to parse publisher_nats_max_reconnects: %v", err) - } - a.PublisherNatsMaxReconnects = int(res) - case "publisher_nats_max_reconnects_wait": - var publisherNatsMaxReconnectsWait string - if !h.AllArgs(&publisherNatsMaxReconnectsWait) { - return nil, h.Errf("expected one boolean value") - } - res, err := time.ParseDuration(publisherNatsMaxReconnectsWait) - if err != nil { - return nil, h.Errf("failed to parse publisher_nats_max_reconnects_wait: %v", err) - } - a.PublisherNatsMaxReconnectsWait = res - default: - return nil, h.Errf("unrecognized option: %s", key) - } - } - } - - return a, nil -} - -func parseBool(d *caddyfile.Dispenser) (bool, error) { - var b string - if !d.AllArgs(&b) { - return false, d.Errf("expected one boolean value") - } - - res, err := strconv.ParseBool(b) - if err != nil { - return false, d.Errf("expected boolean value") - } - - return res, nil -} - -// Implements the caddy.Provisioner interface. -func (a *Audit) Provision(ctx caddy.Context) error { - a.logger = ctx.Logger(a) - a.bufPool = &sync.Pool{ - New: func() any { - return new(bytes.Buffer) - }, - } - - // TODO(gfyrag): do not use env var directly! - a.TopicName = os.Getenv("STACK") + "-audit" - - if a.PublisherKafkaEnabled { - return a.provisionKafkaPublisher() - } - - if a.PublisherNatsEnabled { - return a.provisionNatsPublisher() - } - - return nil -} - -func newNatsPublisherWithConn(conn *nats.Conn, logger watermill.LoggerAdapter, config wNats.PublisherConfig) (*wNats.Publisher, error) { - return wNats.NewPublisherWithNatsConn(conn, config.GetPublisherPublishConfig(), logger) -} - -func (a *Audit) provisionNatsPublisher() error { - - jetStreamConfig := wNats.JetStreamConfig{ - AutoProvision: true, - DurablePrefix: "gateway", - } - - natsOptions := []nats.Option{ - nats.Name(a.PublisherNatsClientId), - nats.MaxReconnects(a.PublisherNatsMaxReconnects), - nats.ReconnectWait(a.PublisherNatsMaxReconnectsWait), - nats.ClosedHandler(func(c *nats.Conn) { - a.logger.Info("nats connection closed") - err := caddy.Stop() - if err != nil { - a.logger.Error("failed to stop caddy", zap.Error(err)) - panic(err) - } - os.Exit(1) - }), - } - - publisherConfig := wNats.PublisherConfig{ - URL: a.PublisherNatsURL, - NatsOptions: natsOptions, - JetStream: jetStreamConfig, - Marshaler: &wNats.NATSMarshaler{}, - SubjectCalculator: wNats.DefaultSubjectCalculator, - } - - conn, err := publish.NewNatsConn( - publisherConfig, - ) - if err != nil { - a.logger.Error("failed to create nats connection", zap.Error(err)) - return err - } - - a.publisher, err = newNatsPublisherWithConn( - conn, - logging.NewZapLoggerAdapter( - a.logger, - ), - publisherConfig, - ) - - if err != nil { - a.logger.Error("failed to create nats publisher", zap.Error(err)) - return err - } - - return nil -} - -func (a *Audit) provisionKafkaPublisher() error { - - options := []publish.SaramaOption{ - publish.WithSASLCredentials( - a.PublisherKafkaSASLUsername, - a.PublisherKafkaSASLPassword, - ), - } - - if a.PublisherKafkaTLSEnabled { - options = append(options, publish.WithTLS()) - } - - if a.PublisherKafkaSASLEnabled { - options = append(options, publish.WithSASLMechanism(sarama.SASLMechanism(a.PublisherKafkaSASLMechanism))) - options = append(options, - publish.WithSASLScramClient(func() sarama.SCRAMClient { - var fn scram.HashGeneratorFcn - switch a.PublisherKafkaSASLScramSHASize { - case 512: - fn = publish.SHA512 - case 256: - fn = publish.SHA256 - default: - panic("sha size not handled") - } - return &publish.XDGSCRAMClient{ - HashGeneratorFcn: fn, - } - }), - ) - } - - var err error - a.publisher, err = publish.NewKafkaPublisher( - logging.NewZapLoggerAdapter( - a.logger, - ), - publish.NewSaramaConfig( - "gateway", - sarama.V1_0_0_0, - options...), - kafka.DefaultMarshaler{}, - a.PublisherKafkaBroker, - ) - - if err != nil { - a.logger.Error("failed to create kafka publisher", zap.Error(err)) - return err - } - - return nil -} - -func (a Audit) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - request := messages.HttpRequest{ - Method: r.Method, - Path: r.URL.Path, - Host: r.Host, - Header: r.Header, - Body: "", - } - - body, err := io.ReadAll(r.Body) - if err != nil { - if !errors.Is(err, io.EOF) { - return err - } - } - - if len(body) > 0 { - request.Body = string(body) - r.Body.Close() - // Restore the io.ReadCloser to its original state - r.Body = io.NopCloser(bytes.NewBuffer(body)) - } - - buf := a.bufPool.Get().(*bytes.Buffer) - buf.Reset() - defer a.bufPool.Put(buf) - - rww := NewResponseWriterWrapper(w, buf) - if err := next.ServeHTTP(rww, r); err != nil { - return err - } - - response := messages.NewHttpResponse( - *rww.statusCode, - rww.Header(), - rww.body.String(), - ) - - if err := a.publisher.Publish( - a.TopicName, - publish.NewMessage( - r.Context(), - messages.NewAuditMessagePayload( - a.logger, - request, - response, - ), - ), - ); err != nil { - a.logger.Error(fmt.Errorf("failed to publish audit message: %v", err).Error()) - } - - return nil -} - -// Interface Guards -var ( - _ caddy.Provisioner = (*Audit)(nil) - _ caddy.Module = (*Audit)(nil) - _ caddyhttp.MiddlewareHandler = (*Audit)(nil) -) - -//------------------------------------------------------------------------------ - -// ResponseWriterWrapper is a wrapper for the http.ResponseWriter, it captures -// the response body and status code to be used in the audit log. -type ResponseWriterWrapper struct { - http.ResponseWriter - body *bytes.Buffer - statusCode *int -} - -// NewResponseWriterWrapper static function creates a wrapper for the -// http.ResponseWriter -func NewResponseWriterWrapper(w http.ResponseWriter, buf *bytes.Buffer) ResponseWriterWrapper { - statusCode := 200 - return ResponseWriterWrapper{ - ResponseWriter: w, - body: buf, - statusCode: &statusCode, // Default status code - } -} - -func (rww ResponseWriterWrapper) Write(buf []byte) (int, error) { - rww.body.Write(buf) - return rww.ResponseWriter.Write(buf) -} - -// Header function overwrites the http.ResponseWriter Header() function -func (rww ResponseWriterWrapper) Header() http.Header { - return rww.ResponseWriter.Header() - -} - -// WriteHeader function overwrites the http.ResponseWriter WriteHeader() function -func (rww ResponseWriterWrapper) WriteHeader(statusCode int) { - (*rww.statusCode) = statusCode - rww.ResponseWriter.WriteHeader(statusCode) -} diff --git a/ee/gateway/pkg/plugins/versions.go b/ee/gateway/pkg/plugins/versions.go deleted file mode 100644 index d4067c62eb..0000000000 --- a/ee/gateway/pkg/plugins/versions.go +++ /dev/null @@ -1,351 +0,0 @@ -package plugins - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "time" - - "github.com/caddyserver/caddy/v2" - "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" - "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" - "github.com/caddyserver/caddy/v2/modules/caddyhttp" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -func init() { - caddy.RegisterModule(Versions{}) - httpcaddyfile.RegisterHandlerDirective("versions", parseCaddyfile) -} - -type Service struct { - VersionEndpoint string `json:"endpoint,omitempty"` - HealthEndpoint string `json:"health,omitempty"` -} - -type Endpoint struct { - Name string `json:"name,omitempty"` - Services []Service `json:"services,omitempty"` -} - -// Versions is a module that serves a /versions endpoint. This endpoint will -// gather all sub-services versions and return them in a JSON response. -// This module is configurable by end-users via caddy configuration. -type Versions struct { - logger *zap.Logger `json:"-"` - versionsHandler http.Handler `json:"-"` - - Region string `json:"region,omitempty"` - Environment string `json:"env,omitempty"` - Endpoints []Endpoint `json:"endpoints,omitempty"` -} - -// Implements the caddy.Module interface. -func (Versions) CaddyModule() caddy.ModuleInfo { - return caddy.ModuleInfo{ - // Note: The ID must start by the namespace http.handlers.* in order to be - // loaded by the global handlers. - ID: "http.handlers.versions", - New: func() caddy.Module { return new(Versions) }, - } -} - -// Implements the caddy.Provisioner interface. -func (v *Versions) Provision(ctx caddy.Context) error { - v.logger = ctx.Logger(v) - v.versionsHandler = newVersionsHandler( - v.logger, - newHTTPClient(), - v.Region, - v.Environment, - v.Endpoints, - ) - - return nil -} - -func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { - var v Versions - err := v.UnmarshalCaddyfile(h.Dispenser) - return v, err -} - -// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax: -// -// versions { -// -// } -func (m *Versions) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { - m.Endpoints = make([]Endpoint, 0) - for d.Next() { - for d.NextBlock(0) { - key := d.Val() - switch key { - case "endpoints": - for d.Next() { - for d.NextBlock(0) { - name := d.Val() - services := make([]Service, 0) - for d.NextBlock(1) { - versionEndpoint := d.Val() - if !d.NextArg() { - return d.Errf("invalid number of endpoints' arguments: want ") - } - healthEndpoint := d.Val() - services = append(services, Service{ - VersionEndpoint: versionEndpoint, - HealthEndpoint: healthEndpoint, - }) - } - - m.Endpoints = append(m.Endpoints, Endpoint{ - Name: name, - Services: services, - }) - } - } - - case "region": - if !d.AllArgs(&m.Region) { - return d.Errf("invalid number of region's arguments: want ") - } - - case "env": - if !d.AllArgs(&m.Environment) { - return d.Errf("invalid number of env's arguments: want ") - } - } - } - } - return nil -} - -// Implements the caddy.Validator interface. -// Validate is called after the config initialization is completed and after -// the module is provisioned. -func (v *Versions) Validate() error { - for _, endpoint := range v.Endpoints { - for _, service := range endpoint.Services { - if _, err := url.ParseRequestURI(service.VersionEndpoint); err != nil { - return fmt.Errorf("invalid version endpoint %s: %w", endpoint.Name, err) - } - - if _, err := url.ParseRequestURI(service.HealthEndpoint); err != nil { - return fmt.Errorf("invalid health endpoint %s: %w", endpoint.Name, err) - } - } - } - - return nil -} - -func (v Versions) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { - v.versionsHandler.ServeHTTP(w, r) - return nil -} - -//------------------------------------------------------------------------------ - -type serviceInfo struct { - Name string `json:"name"` - // We do not want to omit empty values in the json response - Version string `json:"version"` - Health bool `json:"health"` -} - -type backendServiceInfo struct { - serviceInfo - // Deprecated: ledger v1 - Data struct { - Version string `json:"version"` - } -} - -func (info backendServiceInfo) GetVersion() string { - if info.Data.Version != "" { - return info.Data.Version - } - if info.Version != "" { - return info.Version - } - return "unknown" -} - -type versionsResponse struct { - Region string `json:"region"` - Env string `json:"env"` - Versions []*serviceInfo `json:"versions"` -} - -type versionsHandler struct { - logger *zap.Logger - httpClient *http.Client - - region string - env string - endpoints []Endpoint -} - -func newVersionsHandler(logger *zap.Logger, httpClient *http.Client, region, env string, endpoints []Endpoint) http.Handler { - return &versionsHandler{ - logger: logger, - httpClient: httpClient, - region: region, - env: env, - endpoints: endpoints, - } -} - -func newHTTPClient() *http.Client { - return &http.Client{ - Timeout: 10 * time.Second, - } -} - -func (v *versionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - eg, ctxGroup := errgroup.WithContext(r.Context()) - - versions := make(chan *serviceInfo, len(v.endpoints)) - - for _, endpoint := range v.endpoints { - edpt := endpoint - eg.Go(func() error { - res := &serviceInfo{ - Name: edpt.Name, - } - - for _, service := range edpt.Services { - if res.Version == "" { - version, err := serviceVersion(ctxGroup, v.httpClient, service.VersionEndpoint) - if err != nil { - // Log and Discard the error if there is any, and provide an - // "unknown" version. - v.logger.Error("failed to query version", zap.Error(err)) - res.Version = "unknown" - } else { - res.Version = version - } - } - - health, err := serviceHealth(ctxGroup, v.httpClient, service.HealthEndpoint) - if err != nil { - // Log and Discard the error if there is any, and provide a - // "false" health. - v.logger.Error("failed to query health", zap.Error(err)) - res.Health = false - break - } else { - res.Health = health - if !health { - break - } - } - } - - versions <- res - - return nil - }) - } - - if err := eg.Wait(); err != nil { - v.logger.Error("failed to query versions", zap.Error(err)) - return - } - - close(versions) - - res := versionsResponse{ - Region: v.region, - Env: v.env, - } - res.Versions = make([]*serviceInfo, 0, len(v.endpoints)) - for version := range versions { - res.Versions = append(res.Versions, version) - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(res); err != nil { - v.logger.Error("failed to encode response", zap.Error(err)) - } -} - -func serviceVersion( - ctx context.Context, - httpClient *http.Client, - versionEndpoint string, -) (string, error) { - sInfo := &backendServiceInfo{} - - resp, err := serviceCall(ctx, httpClient, versionEndpoint) - if err != nil { - return "", fmt.Errorf("failed to get version for %s: %w", versionEndpoint, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to get version for %s: %s", versionEndpoint, resp.Status) - } - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("failed to read response body for %s: %w", versionEndpoint, err) - } - - if err := json.Unmarshal(responseBody, &sInfo); err != nil { - return "", fmt.Errorf("failed to unmarshal response body for %s: %w", versionEndpoint, err) - } - - return sInfo.GetVersion(), nil -} - -func serviceHealth( - ctx context.Context, - httpClient *http.Client, - healthEndpoint string, -) (bool, error) { - resp, err := serviceCall(ctx, httpClient, healthEndpoint) - if err != nil { - return false, fmt.Errorf("failed to get health for %s: %w", healthEndpoint, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("wrong status code for health endpoint %s: %d", healthEndpoint, resp.StatusCode) - } - - return true, nil -} - -func serviceCall( - ctx context.Context, - httpClient *http.Client, - endpoint string, -) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) - if err != nil { - return nil, fmt.Errorf("failed to create version request: %w", err) - } - - resp, err := httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get version for %s: %w", endpoint, err) - } - - return resp, nil -} - -//------------------------------------------------------------------------------ - -// Interface Guards -var ( - _ caddy.Provisioner = (*Versions)(nil) - _ caddy.Module = (*Versions)(nil) - _ caddyhttp.MiddlewareHandler = (*Versions)(nil) - _ caddyfile.Unmarshaler = (*Versions)(nil) - _ caddy.Validator = (*Versions)(nil) -) diff --git a/ee/gateway/scratch.Dockerfile b/ee/gateway/scratch.Dockerfile deleted file mode 100644 index 162908ec7a..0000000000 --- a/ee/gateway/scratch.Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -ADD https://raw.githubusercontent.com/formancehq/stack/main/ee/gateway/Caddyfile /etc/caddy/Caddyfile -COPY gateway /usr/bin/caddy -ENV OTEL_SERVICE_NAME gateway -ENTRYPOINT ["/usr/bin/caddy"] -CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"] \ No newline at end of file diff --git a/ee/search/.dockerignore b/ee/search/.dockerignore deleted file mode 100644 index 782896a431..0000000000 --- a/ee/search/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.git -vendor -.idea diff --git a/ee/search/.gitignore b/ee/search/.gitignore deleted file mode 100644 index bcc2ba0afc..0000000000 --- a/ee/search/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -vendor -.idea -coverage.* -search diff --git a/ee/search/.goreleaser.yml b/ee/search/.goreleaser.yml deleted file mode 100644 index 3302974141..0000000000 --- a/ee/search/.goreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -project_name: search -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: search - id: search - ldflags: - - -X github.com/formancehq/search/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/search/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/search/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - search - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) \ No newline at end of file diff --git a/ee/search/Earthfile b/ee/search/Earthfile deleted file mode 100644 index 68ad5fbd1d..0000000000 --- a/ee/search/Earthfile +++ /dev/null @@ -1,80 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT .. AS ee - -FROM core+base-image - -sources: - WORKDIR src - WORKDIR /src/ee/search - COPY go.* . - COPY --dir pkg cmd . - COPY main.go . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/search - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/search"] - CMD ["serve"] - COPY (+compile/main) /bin/search - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=search --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/search - WITH DOCKER --pull=postgres:15-alpine - DO --pass-args core+GO_TESTS - END - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"search\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=search - -lint: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/search - COPY --pass-args +tidy/go.* . - DO --pass-args stack+GO_LINT - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT main.go AS LOCAL main.go - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - -openapi: - COPY ./openapi.yaml . - SAVE ARTIFACT ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/ee/search - DO --pass-args stack+GO_TIDY - -release: - BUILD --pass-args stack+goreleaser --path=ee/search \ No newline at end of file diff --git a/ee/search/README.md b/ee/search/README.md deleted file mode 100644 index f15f20f219..0000000000 --- a/ee/search/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# search - -## How to run? - -``` -docker compose up -``` - -## How to test messages? - -``` -docker compose exec -ti redpanda bash -redpanda@ad6639f1576b:/$ cat /src/benthos/messages/committed_transactions.json | rpk topic produce ledger -``` - -## Env vars - -### Ingester - -#### Input -- KAFKA_ADDRESS -- KAFKA_TOPIC -- KAFKA_VERSION -- KAFKA_CONSUMER_GROUP - -#### Traces -- JAEGER_COLLECTOR -- SERVICE_NAME - -#### Output -- OPENSEARCH_URL -- OPENSEARCH_INDEX -- OPENSEARCH_TLS_ENABLED -- OPENSEARCH_TLS_SKIP_CERT_VERIFY -- OPENSEARCH_BASIC_AUTH_ENABLED -- OPENSEARCH_AUTH_USERNAME -- OPENSEARCH_AUTH_PASSWORD diff --git a/ee/search/benthos.Dockerfile b/ee/search/benthos.Dockerfile deleted file mode 100644 index 0b9253232a..0000000000 --- a/ee/search/benthos.Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM jeffail/benthos:4.10 -WORKDIR /config -COPY benthos /config -ENV ELASTICSEARCH_BATCHING_COUNT 50 -ENV ELASTICSEARCH_BATCHING_PERIOD 1s -ENV TOPIC_PREFIX="" -CMD ["-c", "config.yml", "-r", "/config/resources/*.yaml", "-t", "/config/templates/*.yaml", "streams", "/config/streams/ledger/*.yaml", "/config/streams/payments/*.yaml"] diff --git a/ee/search/benthos/benthos.go b/ee/search/benthos/benthos.go deleted file mode 100644 index 7ef0cb5090..0000000000 --- a/ee/search/benthos/benthos.go +++ /dev/null @@ -1,14 +0,0 @@ -package benthos - -import ( - "embed" -) - -//go:embed resources -var Resources embed.FS - -//go:embed templates -var Templates embed.FS - -//go:embed streams -var Streams embed.FS diff --git a/ee/search/benthos/config.yml b/ee/search/benthos/config.yml deleted file mode 100644 index 6128c0e85c..0000000000 --- a/ee/search/benthos/config.yml +++ /dev/null @@ -1,3 +0,0 @@ -http: - enabled: true - address: 0.0.0.0:4195 \ No newline at end of file diff --git a/ee/search/benthos/resources/output_elasticsearch.yaml b/ee/search/benthos/resources/output_elasticsearch.yaml deleted file mode 100644 index 772663c597..0000000000 --- a/ee/search/benthos/resources/output_elasticsearch.yaml +++ /dev/null @@ -1,45 +0,0 @@ -output_resources: - - label: elasticsearch - processors: - - mapping: | - root = match { - this.action == "update" => if this.exists("upsert") { this.merge({ - "upsert": { - "stack": env("STACK") - } - }) } else { this }, - _ => this.merge({ - "document": { - "stack": env("STACK") - } - }) - } - root.id = "%s-%s".format(env("STACK"), this.id) - - log: - message: "Will write: ${! this }" - elasticsearch: - urls: - - ${OPENSEARCH_URL} - id: ${! json().id } - sniff: false - healthcheck: false - index: ${OPENSEARCH_INDEX} - action: ${! json().action } - tls: - enabled: true - skip_cert_verify: true - basic_auth: - enabled: ${BASIC_AUTH_ENABLED} - username: ${BASIC_AUTH_USERNAME} - password: ${BASIC_AUTH_PASSWORD} - batching: - count: ${OPENSEARCH_BATCHING_COUNT:10} - period: ${OPENSEARCH_BATCHING_PERIOD:1s} - multipart: true - doc: ${! if json().exists("document") { json("document") } else {""} } - script: ${! if json().exists("script") { json("script") } else {""} } - upsert: ${! if json().exists("upsert") { json("upsert") } else {""} } - params: ${! if json().exists("params") { json("params") } else {""} } - aws: - enabled: ${AWS_IAM_ENABLED:false} - region: ${AWS_REGION:""} diff --git a/ee/search/benthos/streams/ledger/ledger.yaml b/ee/search/benthos/streams/ledger/ledger.yaml deleted file mode 100644 index 13ee4668cf..0000000000 --- a/ee/search/benthos/streams/ledger/ledger.yaml +++ /dev/null @@ -1,309 +0,0 @@ -input: - event_bus: - topic: ledger - consumer_group: search - -pipeline: - processors: - - switch_event_type: - events: - - label: COMMITTED_TRANSACTIONS - version: v2 - processors: - - bloblang: | - map amount { - root = [this.amount] - let hasDecimals = this.asset.split("/").length() > 1 - let decimals = if $hasDecimals { this.asset.split("/").index(1).number() } else { 0 } - root = if $decimals > 0 { - root.append( - this.amount / range(0, $decimals).fold(1, t -> t.tally * 10) # Just a pow... - ) - } - root = root.flatten() - } - - map tx { - root = { - "action": "index", - "id": "TRANSACTION-%s-%s".format(this.ledger, this.transaction.id), - "document": { - "data": { - "postings": this.transaction.postings, - "reference": this.transaction.reference, - "txid": this.transaction.id, - "timestamp": this.transaction.timestamp, - "metadata": if this.transaction.metadata { this.transaction.metadata } else {{}} - }, - "indexed": { - "reference": this.transaction.reference, - "txid": this.transaction.id, - "timestamp": this.transaction.timestamp, - "asset": this.transaction.postings.map_each(p -> p.asset), - "source": this.transaction.postings.map_each(p -> p.source), - "destination": this.transaction.postings.map_each(p -> p.destination), - "amount": this.transaction.postings.map_each(p -> p.apply("amount")) - }, - "kind": "TRANSACTION", - "when": this.date - } - } - } - - map account { - root = { - "action": "upsert", - "id": "ACCOUNT-%s-%s".format(this.ledger, this.account), - "document": { - "data": { - "address": this.account, - "metadata": {} - }, - "indexed": { - "address": this.account - }, - "kind": "ACCOUNT", - "when": this.date - } - } - } - - root = [] - root = root.append( - this.payload.transactions.map_each(transaction -> { - "transaction": transaction, - "ledger": this.payload.ledger, - "date": this.date - }.apply("tx")) - ) - root = root.append( - this.payload.transactions. - map_each(transaction -> transaction.postings.map_each(posting -> [ - posting.source, - posting.destination - ]). - flatten(). - map_each(account -> { - "account": account, - "ledger": this.payload.ledger, - "date": this.date - }.apply("account")) - ). - flatten() - ) - root = root.append( - this.payload.accountMetadata.map_each(item -> item.value.map_each(metadata -> { - "script": "ctx._source.data.metadata[params.key]=params.value", - "params": { - "key": metadata.key, - "value": metadata.value - }, - "action": "update", - "id": "ACCOUNT-%s-%s".format(this.payload.ledger, item.key), - "upsert": { - "data": { - "address": item.key, - "metadata": { metadata.key: metadata.value } - }, - "indexed": { - "address": item.key - }, - "kind": "ACCOUNT", - "when": this.date - } - }).values()).values().flatten() - ) - root = root.flatten() - - let overlay = { - "data": { - "ledger": this.payload.ledger - }, - "indexed": { - "ledger": this.payload.ledger - } - } - - root = root.map_each(cmd -> match cmd.action { - cmd.action == "update" => if cmd.exists("upsert") { cmd.merge({ - "upsert": $overlay - }) } else { cmd }, - _ => cmd.merge({ - "document": $overlay - }) - }) - - log: - message: "Computed: ${! this }" - - unarchive: - format: json_array - - label: SAVED_METADATA - version: v2 - processors: - - bloblang: | - root = this.payload.metadata.map_each(item -> { - "script": "if (ctx._source.data.metadata == null) { ctx._source.data.metadata = [params.key: params.value] } ctx._source.data.metadata[params.key]=params.value", - "params": { - "key": item.key, - "value": item.value - }, - "action": "update", - "id": "%s-%s-%s".format(this.payload.targetType, this.payload.ledger, this.payload.targetId), - "upsert": { - "data": { - "address": this.payload.targetId, - "metadata": { item.key: item.value }, - "ledger": this.payload.ledger - }, - "indexed": { - "address": this.payload.targetId, - "ledger": this.payload.ledger - }, - "kind": "ACCOUNT", - "when": this.date - } - }).values() - - unarchive: - format: json_array - - label: DELETED_METADATA - version: v2 - processors: - - bloblang: | - root = { - "script": "ctx._source.data.metadata.remove(params.key)", - "params": { - "key": this.payload.key - }, - "action": "update", - "id": "%s-%s-%s".format(this.payload.targetType, this.payload.ledger, this.payload.targetId) - } - - label: COMMITTED_TRANSACTIONS - version: v1 - processors: - - bloblang: | - map account { - root = this.map_each(v -> v.value.map_each(v2 -> { - "action": "upsert", - "id": v.key, - "document": { - "data": { - "address": v.key - }, - "indexed": { - "address": v.key - }, - "kind": "ACCOUNT" - } - }).values()).values().flatten() - } - - map tx { - root = { - "action": "index", - "id": "%s".format(this.txid), - "document": { - "data": { - "postings": this.postings, - "reference": this.reference, - "txid": this.txid, - "timestamp": this.timestamp, - "metadata": if this.metadata { this.metadata } else {{}} - }, - "indexed": { - "reference": this.reference, - "txid": this.txid, - "timestamp": this.timestamp, - "asset": this.postings.map_each(p -> p.asset), - "source": this.postings.map_each(p -> p.source), - "destination": this.postings.map_each(p -> p.destination), - "amount": this.postings.map_each(p -> if p.asset.contains("/") { - [ - p.amount, - p.amount / if p.asset.split("/").index(1).number(){ range(0, p.asset.split("/").index(1).number()).fold(1, t -> t.tally * 10) } else { 1 } # amount / pow(10, decimal part of asset) - ] - } else { [ p.amount ] }).flatten().map_each(v -> "%v".format(v)) - }, - "kind": "TRANSACTION" - } - } - } - - map committedTransactions { - root = [ - this.payload.transactions.map_each(t -> t.apply("tx")).map_each(t -> t.assign({ - "id": "TRANSACTION-%s-%s".format(this.payload.ledger, t.id) - })), - this.payload.transactions.map_each(t -> t.postings.map_each(p -> [{ - "action": "upsert", - "id": "ACCOUNT-%s-%s".format(this.payload.ledger, p.source), - "document": { - "data": { - "address": p.source, - "metadata": {} - }, - "indexed": { - "address": p.source - }, - "kind": "ACCOUNT" - } - }, { - "action": "upsert", - "id": "ACCOUNT-%s-%s".format(this.payload.ledger, p.destination), - "document": { - "data": { - "address": p.destination, - "metadata": {} - }, - "indexed": { - "address": p.destination - }, - "kind": "ACCOUNT" - } - }])).flatten().flatten() - ].flatten().map_each(t -> t.merge({ - "document": { - "when": this.date, - "ledger": this.payload.ledger, - "data": { - "ledger": this.payload.ledger - }, - "indexed": { - "ledger": this.payload.ledger - } - }, - })) - } - - root = this.apply("committedTransactions") - - unarchive: - format: json_array - - label: SAVED_METADATA - version: v1 - processors: - - bloblang: | - root = this.payload.metadata.map_each(item -> { - "script": "if (ctx._source.data.metadata == null) { ctx._source.data.metadata = [params.key: params.value] } ctx._source.data.metadata[params.key]=params.value", - "params": { - "key": item.key, - "value": item.value.string() - }, - "action": "update", - "id": "%s-%s-%s".format(this.payload.targetType, this.payload.ledger, this.payload.targetId), - "upsert": { - "data": { - "address": this.payload.targetId, - "metadata": { item.key: item.value.string() }, - "ledger": this.payload.ledger - }, - "indexed": { - "address": this.payload.targetId, - "ledger": this.payload.ledger - }, - "kind": "ACCOUNT", - "when": this.date - } - }).values() - - unarchive: - format: json_array - -output: - resource: elasticsearch diff --git a/ee/search/benthos/streams/payments/payments.yaml b/ee/search/benthos/streams/payments/payments.yaml deleted file mode 100644 index a6396753fa..0000000000 --- a/ee/search/benthos/streams/payments/payments.yaml +++ /dev/null @@ -1,291 +0,0 @@ -input: - event_bus: - topic: payments - consumer_group: search - -pipeline: - processors: - - switch_event_type: - events: - - label: CONNECTOR_RESET - version: v1 - processors: - - bloblang: | - root = { - "query": { - "bool": { - "must": [ - { - "bool": { - "should": [ - { - "match": { - "kind": "PAYMENT" - } - }, - { - "match": { - "kind": "PAYMENT_POOL" - } - }, - { - "match": { - "kind": "PAYMENT_ACCOUNT" - } - }, - { - "match": { - "kind": "PAYMENT_BALANCE" - } - }, - { - "match": { - "kind": "PAYMENT_BANK_ACCOUNT" - } - }, - { - "match": { - "kind": "PAYMENT_TRANSFER_INITIATION" - } - } - ] - } - }, - { - "match": { - "indexed.connectorId": this.payload.connectorId - } - }, - { - "match": { - "stack": env("STACK") - } - } - ] - } - } - } - - - label: DELETED_POOL - version: v1 - processors: - - bloblang: | - root = { - "query": { - "bool": { - "must": [ - { - "match": { - "kind": "PAYMENT_POOL" - } - }, - { - "match": { - "indexed.id": this.payload.id - } - }, - { - "match": { - "stack": env("STACK") - } - } - ] - } - } - } - - - label: DELETED_TRANSFER_INITIATION - version: v1 - processors: - - bloblang: | - root = { - "query": { - "bool": { - "must": [ - { - "match": { - "kind": "PAYMENT_TRANSFER_INITIATION" - } - }, - { - "should": [ - { - "match": { - "indexed.id": this.payload.id - } - }, - { - "match": { - "indexed.provider": this.payload.provider - } - } - ] - }, - { - "match": { - "stack": env("STACK") - } - } - ] - } - } - } - - label: SAVED_PAYMENT - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload.without("rawData"), - "indexed": { - "id": this.payload.id, - "reference": this.payload.reference, - "provider": this.payload.provider, - "createdAt": this.payload.createdAt, - "connectorId": this.payload.connectorId, - "type": this.payload.type, - "status": this.payload.status, - "scheme": this.payload.scheme, - "asset": this.payload.asset, - "initialAmount": this.payload.initialAmount, - "amount": this.payload.amount - }, - "kind": "PAYMENT", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-%s".format(this.payload.id) - } - - label: SAVED_ACCOUNT - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload.without("rawData"), - "indexed": { - "id": this.payload.id, - "provider": this.payload.provider, - "createdAt": this.payload.createdAt, - "reference": this.payload.reference, - "connectorId": this.payload.connectorId, - "defaultAsset": this.payload.defaultAsset, - "accountName": this.payload.accountName, - "type": this.payload.type - }, - "kind": "PAYMENT_ACCOUNT", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-ACCOUNT-%s".format(this.payload.id) - } - - label: SAVED_BALANCE - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload, - "indexed": { - "accountId": this.payload.accountID, - "provider": this.payload.provider, - "connectorId": this.payload.connectorId, - "createdAt": this.payload.createdAt, - "asset": this.payload.asset, - "balance": this.payload.balance - }, - "kind": "PAYMENT_BALANCE", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-BALANCE-%s-%s".format(this.payload.accountID, this.payload.asset) - } - - label: SAVED_BANK_ACCOUNT - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload, - "indexed": { - "id": this.payload.id, - "createdAt": this.payload.createdAt, - "provider": this.payload.provider, - "name": this.payload.name, - "accountNumber": this.payload.accountNumber, - "iban": this.payload.iban, - "swiftBicCode": this.payload.swiftBicCode, - "country": this.payload.country - }, - "kind": "PAYMENT_BANK_ACCOUNT", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-BANK-ACCOUNT-%s".format(this.payload.id) - } - - label: SAVED_POOL - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload, - "indexed": { - "id": this.payload.id, - "createdAt": this.payload.createdAt, - "name": this.payload.name, - "accountIDs": this.payload.accountIDs, - }, - "kind": "PAYMENT_POOL", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-POOL-%s".format(this.payload.id) - } - - label: SAVED_TRANSFER_INITIATION - version: v1 - processors: - - bloblang: | - root = { - "document": { - "data": this.payload, - "indexed": { - "id": this.payload.id, - "createdAt": this.payload.createdAt, - "scheduledAt": this.payload.scheduledAt, - "connectorId": this.payload.connectorId, - "description": this.payload.description, - "type": this.payload.type, - "provider": this.payload.provider, - "sourceAccountId": this.payload.sourceAccountID, - "destinationAccountId": this.payload.destinationAccountID, - "amount": this.payload.amount, - "asset": this.payload.asset, - "attempts": this.payload.attempts, - "status": this.payload.status, - "error": this.payload.error, - "relatedPayment": this.payload.relatedPayment - }, - "kind": "PAYMENT_TRANSFER_INITIATION", - "when": this.date - }, - "action": "index", - "id": "PAYMENT-TRANSFER-INITIATION-%s".format(this.payload.id) - } - -output: - switch: - cases: - - check: this.action != null - output: - resource: elasticsearch - - output: - http_client: - url: ${OPENSEARCH_URL}/${OPENSEARCH_INDEX}/_delete_by_query - verb: POST - headers: - Content-Type: application/json - basic_auth: - enabled: ${BASIC_AUTH_ENABLED} - username: ${BASIC_AUTH_USERNAME} - password: ${BASIC_AUTH_PASSWORD} diff --git a/ee/search/benthos/templates/event_bus.yaml b/ee/search/benthos/templates/event_bus.yaml deleted file mode 100644 index 4f2b5822e8..0000000000 --- a/ee/search/benthos/templates/event_bus.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: event_bus -type: input - -fields: -- name: topic - type: string -- name: consumer_group - type: string - -mapping: | - root = if env("BROKER") == "kafka" {{ - "kafka_franz": { - "seed_brokers": [ env("KAFKA_ADDRESS") ], - "topics": [ env("TOPIC_PREFIX") + this.topic ], - "consumer_group": this.consumer_group, - "checkpoint_limit": 1024, - "sasl": [ - { - "mechanism": env("KAFKA_SASL_MECHANISM"), - "password": env("KAFKA_SASL_PASSWORD"), - "username": env("KAFKA_SASL_USERNAME"), - "aws": { - "region": env("AWS_REGION"), - "credentials": { - "profile": env("AWS_PROFILE"), - "id": env("AWS_ACCESS_KEY_ID"), - "secret": env("AWS_SECRET_ACCESS_KEY"), - "token": env("AWS_SESSION_TOKEN"), - "role": env("AWS_ROLE_ARN") - } - } - } - ], - "tls": { - "enabled": env("KAFKA_TLS_ENABLED") == "true" - } - } - }} else {{ - "nats_jetstream": { - "urls": [env("NATS_URL")], - "queue": this.consumer_group, - "subject": env("TOPIC_PREFIX") + this.topic, - "durable": if env("NATS_BIND") == "true" { this.consumer_group + "_" + this.topic } else { this.consumer_group }, - "bind": env("NATS_BIND") == "true" - } - }} diff --git a/ee/search/benthos/templates/postgres_query.yaml b/ee/search/benthos/templates/postgres_query.yaml deleted file mode 100644 index 44a85a11b0..0000000000 --- a/ee/search/benthos/templates/postgres_query.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: postgres_query -type: processor - -fields: -- name: query - type: string -- name: service - type: string - -mapping: | - root = { - "sql_raw": { - "dsn": env("%s_POSTGRES_URI".format(this.service.uppercase())), - "driver": "postgres", - "query": this.query, - "unsafe_dynamic_query": true - } - } diff --git a/ee/search/benthos/templates/switch_event_type.yaml b/ee/search/benthos/templates/switch_event_type.yaml deleted file mode 100644 index 4937e5f8be..0000000000 --- a/ee/search/benthos/templates/switch_event_type.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: switch_event_type -type: processor - -fields: -- kind: list - type: unknown - name: events - -mapping: | - root = { - "switch": this.events.map_each(eventDefinition -> { - "check": "this.type == \"%s\" && this.version == \"%s\"".format(eventDefinition.label, eventDefinition.version), - "processors": eventDefinition.processors - }).append({ - "processors": [ - { - "log": { - "level": "DEBUG", - "message": "unable to handle message ${! content() }" - } - }, - { - "bloblang": "root = deleted()" - } - ] - }) - } diff --git a/ee/search/build.Dockerfile b/ee/search/build.Dockerfile deleted file mode 100644 index ef34b5ade5..0000000000 --- a/ee/search/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY search /usr/bin/search -ENV OTEL_SERVICE_NAME search -ENTRYPOINT ["/usr/bin/search"] -CMD ["serve"] diff --git a/ee/search/cmd/init_mapping.go b/ee/search/cmd/init_mapping.go deleted file mode 100644 index 1d6d97f7ca..0000000000 --- a/ee/search/cmd/init_mapping.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/search/pkg/searchengine" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func NewInitMapping() *cobra.Command { - cmd := &cobra.Command{ - Use: "init-mapping", - Short: "Init ElasticSearch mapping", - RunE: func(cmd *cobra.Command, args []string) error { - config, err := newConfig(cmd) - if err != nil { - return err - } - - client, err := newOpensearchClient(cmd, config) - if err != nil { - return err - } - - esIndex, _ := cmd.Flags().GetString(esIndicesFlag) - if esIndex == "" { - return errors.New("es index not defined") - } - - return searchengine.CreateIndex(cmd.Context(), client, esIndex) - }, - } - cmd.Flags().Bool(awsIAMEnabledFlag, false, "Enable AWS IAM") - cmd.Flags().String(esIndicesFlag, "", "ES index to look") - cmd.Flags().String(openSearchServiceFlag, "", "Open search service hostname") - cmd.Flags().String(openSearchSchemeFlag, "https", "OpenSearch scheme") - cmd.Flags().String(openSearchUsernameFlag, "", "OpenSearch username") - cmd.Flags().String(openSearchPasswordFlag, "", "OpenSearch password") - cmd.Flags().String(stackFlag, "", "Stack id") - - iam.AddFlags(cmd.Flags()) - service.AddFlags(cmd.Flags()) - - return cmd -} diff --git a/ee/search/cmd/root.go b/ee/search/cmd/root.go deleted file mode 100644 index 63f00fb867..0000000000 --- a/ee/search/cmd/root.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - _ "github.com/bombsimon/logrusr/v3" - "github.com/formancehq/go-libs/service" - "github.com/spf13/cobra" -) - -var ( - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func NewRootCommand() *cobra.Command { - root := &cobra.Command{ - Use: "search", - Short: "search", - DisableAutoGenTag: true, - Version: Version, - } - - serverCmd := NewServer() - root.AddCommand(NewVersion(), serverCmd, NewInitMapping(), NewUpdateMapping()) - - return root -} - -func Execute() { - service.Execute(NewRootCommand()) -} diff --git a/ee/search/cmd/serve.go b/ee/search/cmd/serve.go deleted file mode 100644 index 72defef7be..0000000000 --- a/ee/search/cmd/serve.go +++ /dev/null @@ -1,230 +0,0 @@ -package cmd - -import ( - "context" - "crypto/tls" - "net/http" - "os" - - "github.com/opensearch-project/opensearch-go" - - "github.com/aws/aws-sdk-go-v2/config" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/health" - "github.com/formancehq/go-libs/httpclient" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/licence" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/otlp" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/search/pkg/searchengine" - "github.com/formancehq/search/pkg/searchhttp" - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/opensearch-project/opensearch-go/opensearchtransport" - requestsigner "github.com/opensearch-project/opensearch-go/v2/signer/awsv2" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux" - "go.opentelemetry.io/otel/trace" - "go.uber.org/fx" -) - -const ( - serviceName = "search" - - openSearchServiceFlag = "open-search-service" - openSearchSchemeFlag = "open-search-scheme" - openSearchUsernameFlag = "open-search-username" - openSearchPasswordFlag = "open-search-password" - esIndicesFlag = "es-indices" - esDisableMappingInitFlag = "mapping-init-disabled" - bindFlag = "bind" - stackFlag = "stack" - awsIAMEnabledFlag = "aws-iam-enabled" - - defaultBind = ":8080" - - healthCheckPath = "/_healthcheck" -) - -func NewServer() *cobra.Command { - cmd := &cobra.Command{ - Use: "serve", - Short: "Launch the search server", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - - esIndex, _ := cmd.Flags().GetString(esIndicesFlag) - if esIndex == "" { - return errors.New("es index not defined") - } - - bind, _ := cmd.Flags().GetString(bindFlag) - if bind == "" { - bind = defaultBind - } - - options := make([]fx.Option, 0) - options = append(options, opensearchClientModule(cmd)) - options = append(options, - auth.FXModuleFromFlags(cmd), - health.Module(), - health.ProvideHealthCheck(func(client *opensearch.Client) health.NamedCheck { - return health.NewNamedCheck("elasticsearch connection", health.CheckFn(func(ctx context.Context) error { - _, err := client.Ping() - return err - })) - }), - ) - - options = append(options, otlptraces.FXModuleFromFlags(cmd)) - options = append(options, licence.FXModuleFromFlags(cmd, serviceName)) - options = append(options, apiModule(cmd)) - - return service.New(cmd.OutOrStdout(), options...).Run(cmd) - }, - } - - cmd.Flags().String(bindFlag, defaultBind, "http server address") - cmd.Flags().String(esIndicesFlag, "", "ES index to look") - cmd.Flags().String(openSearchServiceFlag, "", "Open search service hostname") - cmd.Flags().String(openSearchSchemeFlag, "https", "OpenSearch scheme") - cmd.Flags().String(openSearchUsernameFlag, "", "OpenSearch username") - cmd.Flags().String(openSearchPasswordFlag, "", "OpenSearch password") - cmd.Flags().Bool(esDisableMappingInitFlag, false, "Disable mapping initialization") - cmd.Flags().String(stackFlag, "", "Stack id") - cmd.Flags().Bool(awsIAMEnabledFlag, false, "Use AWS IAM for authentication") - - iam.AddFlags(cmd.Flags()) - otlptraces.AddFlags(cmd.Flags()) - service.AddFlags(cmd.Flags()) - licence.AddFlags(cmd.Flags()) - auth.AddFlags(cmd.Flags()) - - return cmd -} - -func exitWithError(ctx context.Context, msg string) { - logging.FromContext(ctx).Error(msg) - os.Exit(1) -} - -func newOpensearchClient(cmd *cobra.Command, config *opensearch.Config) (*opensearch.Client, error) { - httpTransport := http.DefaultTransport - httpTransport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - httpTransport = otlp.NewRoundTripper(httpTransport, service.IsDebug(cmd)) - - if service.IsDebug(cmd) { - httpTransport = httpclient.NewDebugHTTPTransport(httpTransport) - config.Logger = &opensearchtransport.JSONLogger{ - Output: os.Stdout, - EnableRequestBody: true, - EnableResponseBody: true, - } - } else { - config.UseResponseCheckOnly = true - } - - return opensearch.NewClient(*config) -} - -func newConfig(cmd *cobra.Command) (*opensearch.Config, error) { - openSearchServiceHost, _ := cmd.Flags().GetString(openSearchServiceFlag) - if openSearchServiceHost == "" { - exitWithError(cmd.Context(), "missing open search service host") - } - openSearchScheme, _ := cmd.Flags().GetString(openSearchSchemeFlag) - awsIAMEnabled, _ := cmd.Flags().GetBool(awsIAMEnabledFlag) - - cfg := opensearch.Config{ - Addresses: []string{openSearchScheme + "://" + openSearchServiceHost}, - } - if awsIAMEnabled { - awsConfig, err := config.LoadDefaultConfig(context.Background()) - if err != nil { - return nil, err - } - signer, err := requestsigner.NewSigner(awsConfig) - if err != nil { - return nil, err - } - cfg.Signer = signer - } else { - cfg.Username, _ = cmd.Flags().GetString(openSearchUsernameFlag) - cfg.Password, _ = cmd.Flags().GetString(openSearchPasswordFlag) - } - return &cfg, nil -} - -func opensearchClientModule(cmd *cobra.Command) fx.Option { - esDisableMappingInit, _ := cmd.Flags().GetBool(esDisableMappingInitFlag) - openSearchServiceHost, _ := cmd.Flags().GetString(openSearchServiceFlag) - if openSearchServiceHost == "" { - exitWithError(cmd.Context(), "missing open search service host") - } - esIndex, _ := cmd.Flags().GetString(esIndicesFlag) - - options := []fx.Option{ - fx.Provide(func() (*opensearch.Config, error) { - return newConfig(cmd) - }), - fx.Provide(func(config *opensearch.Config) (*opensearch.Client, error) { - return newOpensearchClient(cmd, config) - }), - } - if !esDisableMappingInit { - options = append(options, fx.Invoke(func(lc fx.Lifecycle, client *opensearch.Client) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - return searchengine.CreateIndex(ctx, client, esIndex) - }, - }) - })) - } - return fx.Options(options...) -} - -func apiModule(cmd *cobra.Command) fx.Option { - - serviceInfo := api.ServiceInfo{ - Version: Version, - Debug: service.IsDebug(cmd), - } - stack, _ := cmd.Flags().GetString(stackFlag) - esIndex, _ := cmd.Flags().GetString(esIndicesFlag) - bind, _ := cmd.Flags().GetString(bindFlag) - otelTraces, _ := cmd.Flags().GetBool(otlptraces.OtelTracesFlag) - - return fx.Options( - fx.Provide(fx.Annotate(func(openSearchClient *opensearch.Client, tp trace.TracerProvider, healthController *health.HealthController, a auth.Authenticator) (http.Handler, error) { - router := mux.NewRouter() - - router.Use(handlers.RecoveryHandler()) - router.Handle(healthCheckPath, http.HandlerFunc(healthController.Check)) - router.Path("/_info").Methods(http.MethodGet).Handler(api.InfoHandler(serviceInfo)) - - routerWithTraces := router.PathPrefix("/").Subrouter() - routerWithTraces.Use(auth.Middleware(a)) - if otelTraces { - routerWithTraces.Use(otelmux.Middleware(serviceName, otelmux.WithTracerProvider(tp))) - } - routerWithTraces.PathPrefix("/").Handler(searchhttp.Handler(searchengine.NewDefaultEngine( - openSearchClient, - stack, - searchengine.WithESIndex(esIndex), - ))) - - return router, nil - }, fx.ParamTags(``, `optional:"true"`))), - fx.Invoke(func(lc fx.Lifecycle, handler http.Handler) { - lc.Append(httpserver.NewHook(handler, httpserver.WithAddress(bind))) - }), - ) -} diff --git a/ee/search/cmd/update_mapping.go b/ee/search/cmd/update_mapping.go deleted file mode 100644 index e13dc2e946..0000000000 --- a/ee/search/cmd/update_mapping.go +++ /dev/null @@ -1,41 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/search/pkg/searchengine" - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func NewUpdateMapping() *cobra.Command { - cmd := &cobra.Command{ - Use: "update-mapping", - Short: "Update ElasticSearch mapping", - RunE: func(cmd *cobra.Command, args []string) error { - - config, err := newConfig(cmd) - if err != nil { - return err - } - - client, err := newOpensearchClient(cmd, config) - if err != nil { - return err - } - - esIndex, _ := cmd.Flags().GetString(esIndicesFlag) - if esIndex == "" { - return errors.New("es index not defined") - } - - return searchengine.UpdateMapping(cmd.Context(), client, esIndex) - }, - } - - cmd.Flags().Bool(awsIAMEnabledFlag, false, "Enable AWS IAM") - iam.AddFlags(cmd.Flags()) - service.AddFlags(cmd.Flags()) - - return cmd -} diff --git a/ee/search/cmd/version.go b/ee/search/cmd/version.go deleted file mode 100644 index 5b9e9ff24d..0000000000 --- a/ee/search/cmd/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func PrintVersion(cmd *cobra.Command, args []string) { - fmt.Printf("Version: %s \n", Version) - fmt.Printf("Date: %s \n", BuildDate) - fmt.Printf("Commit: %s \n", Commit) -} - -func NewVersion() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Get version", - Run: PrintVersion, - } -} diff --git a/ee/search/config/redpanda/config.yaml b/ee/search/config/redpanda/config.yaml deleted file mode 100644 index 5ee6333653..0000000000 --- a/ee/search/config/redpanda/config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -auto_create_topics_enabled: true -delete_retention_ms: -1 diff --git a/ee/search/docker-compose.yml b/ee/search/docker-compose.yml deleted file mode 100644 index 59a46f5dd4..0000000000 --- a/ee/search/docker-compose.yml +++ /dev/null @@ -1,110 +0,0 @@ -services: - - jaeger: - image: jaegertracing/opentelemetry-all-in-one - ports: - - "16686:16686/tcp" - - opensearch: - image: opensearchproject/opensearch:2.11.0 - environment: - discovery.type: single-node - ports: - - 9200:9200/tcp - - benthos: - image: jeffail/benthos:4.11 - ports: - - "4195:4195/tcp" - volumes: - - ./benthos:/config - working_dir: /.local - command: - - -w - - --log.level - - trace - - -c - - config.yml - - -r - - /config/resources/*.yaml - - -t - - /config/templates/*.yaml - - streams - - /config/streams/ledger/*.yaml - - /config/streams/payments/*.yaml - environment: - OPENSEARCH_URL: https://opensearch:9200 - OPENSEARCH_BATCHING_COUNT: 1 - OPENSEARCH_BATCHING_PERIOD: 5s - BASIC_AUTH_ENABLED: "true" - BASIC_AUTH_USERNAME: "admin" - BASIC_AUTH_PASSWORD: "admin" - JAEGER_COLLECTOR: "collector:6831" - OPENSEARCH_INDEX: "ledger" - KAFKA_ADDRESS: "redpanda:29092" - TOPIC_PREFIX: "" - depends_on: - - opensearch - - redpanda - - redpanda-config - - jaeger - - search: - command: go run main.go serve - image: golang:1.19-alpine - ports: - - 8080:8080/tcp - depends_on: - - opensearch - - jaeger - environment: - OPEN_SEARCH_SERVICE: "admin:admin@opensearch:9200" - DEBUG: "true" - OTEL_TRACES: "true" - OTEL_TRACES_EXPORTER: jaeger - OTEL_TRACES_EXPORTER_JAEGER_ENDPOINT: http://jaeger:14268/api/traces - OTEL_SERVICE_NAME: "search" - OTEL_RESOURCE_ATTRIBUTES: "env=local engine=docker" - volumes: - - .:/app - working_dir: /app - - redpanda: - image: docker.vectorized.io/vectorized/redpanda:v22.3.24 - volumes: - - .:/src - command: - - redpanda - - start - - --smp - - '1' - - --reserve-memory - - 0M - - --overprovisioned - - --node-id - - '0' - - --kafka-addr - - PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 - - --advertise-kafka-addr - - PLAINTEXT://redpanda:29092,OUTSIDE://localhost:9092 - healthcheck: - test: curl -s -f -k http://127.0.0.1:9644/metrics > /dev/null || exit 1 - interval: 30s - timeout: 10s - retries: 5 - - redpanda-config: - image: docker.vectorized.io/vectorized/redpanda:v22.3.24 - command: - - cluster - - config - - import - - --filename - - /etc/redpanda/redpanda.yaml - - --api-urls - - redpanda:9644 - depends_on: - redpanda: - condition: service_healthy - volumes: - - ./config/redpanda/config.yaml:/etc/redpanda/redpanda.yaml:ro diff --git a/ee/search/go.mod b/ee/search/go.mod deleted file mode 100644 index 2169910b74..0000000000 --- a/ee/search/go.mod +++ /dev/null @@ -1,113 +0,0 @@ -module github.com/formancehq/search - -go 1.22.0 - -toolchain go1.22.7 - -require ( - github.com/aquasecurity/esquery v0.2.0 - github.com/bombsimon/logrusr/v3 v3.1.0 - github.com/formancehq/go-libs v1.7.1 - github.com/gorilla/handlers v1.5.1 - github.com/gorilla/mux v1.8.1 - github.com/opensearch-project/opensearch-go v1.1.0 - github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.8.1 - github.com/stretchr/testify v1.9.0 - github.com/tidwall/gjson v1.14.4 - go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/fx v1.22.2 -) - -require ( - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx v1.2.30 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun v1.2.3 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect -) - -require ( - github.com/ThreeDotsLabs/watermill v1.3.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/elastic/go-elasticsearch/v7 v7.17.10 // indirect - github.com/fatih/structs v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/muhlemmer/gu v0.3.1 // indirect - github.com/muhlemmer/httpforwarded v0.1.0 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/opensearch-project/opensearch-go/v2 v2.3.0 - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rs/cors v1.11.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/zitadel/oidc/v2 v2.12.2 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/formancehq/ledger => ../../components/ledger diff --git a/ee/search/go.sum b/ee/search/go.sum deleted file mode 100644 index 8d65aa5905..0000000000 --- a/ee/search/go.sum +++ /dev/null @@ -1,366 +0,0 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/aquasecurity/esquery v0.2.0 h1:9WWXve95TE8hbm3736WB7nS6Owl8UGDeu+0jiyE9ttA= -github.com/aquasecurity/esquery v0.2.0/go.mod h1:VU+CIFR6C+H142HHZf9RUkp4Eedpo9UrEKeCQHWf9ao= -github.com/aws/aws-sdk-go v1.42.27/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= -github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.18.25/go.mod h1:dZnYpD5wTW/dQF0rRNLVypB396zWCcPiBIvdvSWHEg4= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.13.24/go.mod h1:jYPYi99wUOPIFi0rhiOvXeSEReVOzBqFNOX5bXYoG2o= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.3/go.mod h1:4Q0UFP0YJf0NrsEuEYHpM9fTSEVnD16Z3uyEF7J9JGM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.33/go.mod h1:7i0PF1ME/2eUPFcjkVIwq+DOygHEoK92t5cDqNgYbIw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.27/go.mod h1:UrHnn3QV/d0pBZ6QBAEQcqFLf8FAzLmoUfPVIueOvoM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.10/go.mod h1:AFvkxc8xfBe8XA+5St5XIHHrQQtkxqrRincx4hmMHOk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.19.0/go.mod h1:BgQOMsg8av8jset59jelyPW7NoZcZXLVpDsXunGDrk8= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/bombsimon/logrusr/v3 v3.1.0 h1:zORbLM943D+hDMGgyjMhSAz/iDz86ZV72qaak/CA0zQ= -github.com/bombsimon/logrusr/v3 v3.1.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/elastic/go-elasticsearch/v7 v7.6.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= -github.com/elastic/go-elasticsearch/v7 v7.17.10 h1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo= -github.com/elastic/go-elasticsearch/v7 v7.17.10/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= -github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/jgroeneveld/schema v1.0.0 h1:J0E10CrOkiSEsw6dfb1IfrDJD14pf6QLVJ3tRPl/syI= -github.com/jgroeneveld/schema v1.0.0/go.mod h1:M14lv7sNMtGvo3ops1MwslaSYgDYxrSmbzWIQ0Mr5rs= -github.com/jgroeneveld/trial v2.0.0+incompatible h1:d59ctdgor+VqdZCAiUfVN8K13s0ALDioG5DWwZNtRuQ= -github.com/jgroeneveld/trial v2.0.0+incompatible/go.mod h1:I6INLW96EN8WysNBXUFI3M4RIC8ePg9ntAc/Wy+U/+M= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= -github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= -github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= -github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= -github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opensearch-project/opensearch-go v1.1.0 h1:eG5sh3843bbU1itPRjA9QXbxcg8LaZ+DjEzQH9aLN3M= -github.com/opensearch-project/opensearch-go v1.1.0/go.mod h1:+6/XHCuTH+fwsMJikZEWsucZ4eZMma3zNSeLrTtVGbo= -github.com/opensearch-project/opensearch-go/v2 v2.3.0 h1:nQIEMr+A92CkhHrZgUhcfsrZjibvB3APXf2a1VwCmMQ= -github.com/opensearch-project/opensearch-go/v2 v2.3.0/go.mod h1:8LDr9FCgUTVoT+5ESjc2+iaZuldqE+23Iq0r1XeNue8= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zitadel/oidc/v2 v2.12.2 h1:3kpckg4rurgw7w7aLJrq7yvRxb2pkNOtD08RH42vPEs= -github.com/zitadel/oidc/v2 v2.12.2/go.mod h1:vhP26g1g4YVntcTi0amMYW3tJuid70nxqxf+kb6XKgg= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0 h1:QaNUlLvmettd1vnmFHrgBYQHearxWP3uO4h4F3pVtkM= -go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0/go.mod h1:cJu+5jZwoZfkBOECSFtBZK/O7h/pY5djn0fwnIGnQ4A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -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/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-20210423082822-04245dca01da/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ee/search/main.go b/ee/search/main.go deleted file mode 100644 index 702ce88a05..0000000000 --- a/ee/search/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/formancehq/search/cmd" -) - -func main() { - cmd.Execute() -} diff --git a/ee/search/openapi.yaml b/ee/search/openapi.yaml deleted file mode 100644 index 89b5bdb816..0000000000 --- a/ee/search/openapi.yaml +++ /dev/null @@ -1,135 +0,0 @@ -openapi: 3.0.3 -info: - title: Search API - version: latest -paths: - /_info: - get: - summary: Get server info - operationId: getServerInfo - tags: - - search.v1 - responses: - '200': - description: Server information - content: - application/json: - schema: - $ref: '#/components/schemas/ServerInfo' - security: - - Authorization: - - search:read - /: - post: - summary: search.v1 - tags: - - search.v1 - operationId: search - description: Elasticsearch.v1 query engine - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/Query' - responses: - '200': - description: Success - content: - application/json: - schema: - $ref: '#/components/schemas/Response' - default: - description: Error - content: {} - security: - - Authorization: - - search:write -components: - schemas: - ServerInfo: - type: object - required: - - version - properties: - version: - type: string - Query: - type: object - properties: - ledgers: - type: array - items: - type: string - example: quickstart - after: - type: array - items: - type: string - example: users:002 - pageSize: - type: integer - format: int64 - minimum: 0 - terms: - type: array - items: - type: string - example: destination=central_bank1 - sort: - type: string - example: id:asc - policy: - type: string - example: OR - target: - type: string - cursor: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - raw: - type: object - example: - query: - match_all: {} - Response: - type: object - properties: - data: - type: object - description: The payload - additionalProperties: true - cursor: - title: cursor - type: object - properties: - pageSize: - type: integer - format: int64 - minimum: 0 - hasMore: - type: boolean - total: - title: total - type: object - properties: - value: - type: integer - format: int64 - minimum: 0 - example: 1 - relation: - type: string - example: eq - next: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - previous: - type: string - example: YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol= - data: - type: array - items: - allOf: - - type: object - additionalProperties: true diff --git a/ee/search/pkg/es/response.go b/ee/search/pkg/es/response.go deleted file mode 100644 index e8c49ca454..0000000000 --- a/ee/search/pkg/es/response.go +++ /dev/null @@ -1,41 +0,0 @@ -package es - -import "encoding/json" - -type ResponseShards struct { - Total int `json:"total"` - Successful int `json:"successful"` - Skipped int `json:"skipped"` - Failed int `json:"failed"` -} - -type ResponseHitsTotal struct { - Value int `json:"value"` - Relation string `json:"relation"` -} - -type ResponseHit struct { - Index string `json:"_index"` - Type string `json:"_type"` - ID string `json:"_id"` - Score float64 `json:"_score"` - Source json.RawMessage `json:"_source"` - Fields map[string][]string `json:"fields,omitempty"` - InnerHits map[string]struct { - Hits ResponseHits `json:"hits"` - } `json:"inner_hits,omitempty"` -} - -type ResponseHits struct { - Total ResponseHitsTotal `json:"total"` - MaxScore float64 `json:"max_score"` - Hits []ResponseHit `json:"hits"` -} - -type Response struct { - Took int `json:"took"` - TimedOut bool `json:"timed_out"` - Shards ResponseShards `json:"_shards"` - Hits ResponseHits `json:"hits"` - Aggregations map[string]json.RawMessage `json:"aggregations,omitempty"` -} diff --git a/ee/search/pkg/searchengine/engine.go b/ee/search/pkg/searchengine/engine.go deleted file mode 100644 index 85eee04b38..0000000000 --- a/ee/search/pkg/searchengine/engine.go +++ /dev/null @@ -1,138 +0,0 @@ -package searchengine - -import ( - "bytes" - "context" - "encoding/json" - "io" - - "github.com/formancehq/search/pkg/es" - "github.com/opensearch-project/opensearch-go" - "github.com/pkg/errors" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -type Engine interface { - doRequest(ctx context.Context, m map[string]interface{}) (*es.Response, error) -} - -type EngineFn func(ctx context.Context, m map[string]interface{}) (*es.Response, error) - -func (fn EngineFn) doRequest(ctx context.Context, m map[string]interface{}) (*es.Response, error) { - return fn(ctx, m) -} - -var NotImplementedEngine = EngineFn(func(ctx context.Context, m map[string]interface{}) (*es.Response, error) { - return nil, errors.New("not implemented") -}) - -type DefaultEngineOption interface { - apply(*DefaultEngine) -} -type DefaultEngineOptionFn func(engine *DefaultEngine) - -func (fn DefaultEngineOptionFn) apply(engine *DefaultEngine) { - fn(engine) -} - -func WithESIndex(esIndices ...string) DefaultEngineOptionFn { - return func(engine *DefaultEngine) { - engine.indices = esIndices - } -} - -var DefaultEngineOptions = []DefaultEngineOption{} - -type Response map[string][]interface{} - -type DefaultEngine struct { - openSearchClient *opensearch.Client - indices []string - stack string -} - -func (e *DefaultEngine) doRequest(ctx context.Context, m map[string]interface{}) (*es.Response, error) { - - ctx, span := otel.Tracer("com.formance.search").Start(ctx, "Search") - defer span.End() - - recordFailingSpan := func(err error) error { - if err == nil { - return nil - } - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return err - } - - m["query"] = map[string]any{ - "bool": map[string]any{ - "must": []any{ - map[string]any{"match": map[string]any{"stack": e.stack}}, - m["query"], - }, - }, - } - - data, err := json.Marshal(m) - if err != nil { - return nil, recordFailingSpan(errors.Wrap(err, "marshalling query")) - } - - span.SetAttributes( - attribute.String("query", string(data)), - attribute.StringSlice("indices", e.indices), - ) - - httpResponse, err := e.openSearchClient.Search( - e.openSearchClient.Search.WithBody(bytes.NewReader(data)), - e.openSearchClient.Search.WithStoredFields("_all"), - e.openSearchClient.Search.WithSource("*"), - e.openSearchClient.Search.WithIndex(e.indices...), - e.openSearchClient.Search.WithContext(ctx), - ) - if err != nil { - return nil, recordFailingSpan(errors.Wrap(err, "doing request")) - } - defer httpResponse.Body.Close() - - if httpResponse.IsError() { - switch httpResponse.StatusCode { - case 404: - return &es.Response{}, nil - default: - data, err := io.ReadAll(httpResponse.Body) - if err != nil || len(data) == 0 { - return nil, recordFailingSpan(errors.New(httpResponse.Status())) - } - return nil, recordFailingSpan(errors.New(string(data))) - } - } - - res := &es.Response{} - err = json.NewDecoder(httpResponse.Body).Decode(res) - if err != nil { - return nil, recordFailingSpan(errors.Wrap(err, "decoding result")) - } - - span.SetAttributes(attribute.Int("hits.total.value", res.Hits.Total.Value)) - span.SetAttributes(attribute.String("hits.total.relation", res.Hits.Total.Relation)) - span.SetAttributes(attribute.Int("took", res.Took)) - - return res, nil -} - -func NewDefaultEngine(openSearchClient *opensearch.Client, stack string, opts ...DefaultEngineOption) *DefaultEngine { - - engine := &DefaultEngine{ - openSearchClient: openSearchClient, - stack: stack, - } - opts = append(DefaultEngineOptions, opts...) - for _, opt := range opts { - opt.apply(engine) - } - return engine -} diff --git a/ee/search/pkg/searchengine/indexed_mapping.json b/ee/search/pkg/searchengine/indexed_mapping.json deleted file mode 100644 index fb62d966c2..0000000000 --- a/ee/search/pkg/searchengine/indexed_mapping.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "account" : { - "type": "keyword" - }, - "address" : { - "type": "keyword" - }, - "amount" : { - "type": "float" - }, - "asset" : { - "type": "keyword" - }, - "createdAt" : { - "type" : "date" - }, - "destination" : { - "type": "keyword" - }, - "txid" : { - "type": "long" - }, - "initialAmount" : { - "type" : "long" - }, - "ledger" : { - "type": "keyword" - }, - "name" : { - "type": "keyword" - }, - "provider" : { - "type": "keyword" - }, - "reference" : { - "type": "keyword" - }, - "scheme" : { - "type": "keyword" - }, - "source" : { - "type": "keyword" - }, - "status" : { - "type": "keyword" - }, - "timestamp" : { - "type" : "date" - }, - "id" : { - "type" : "keyword" - }, - "type" : { - "type": "keyword" - }, - "requestPath" : { - "type": "keyword" - }, - "requestMethod" : { - "type": "keyword" - }, - "responseStatusCode" : { - "type": "short" - }, - "identity" : { - "type": "keyword" - } -} diff --git a/ee/search/pkg/searchengine/mapping.go b/ee/search/pkg/searchengine/mapping.go deleted file mode 100644 index b1f2e91fbc..0000000000 --- a/ee/search/pkg/searchengine/mapping.go +++ /dev/null @@ -1,178 +0,0 @@ -package searchengine - -import ( - "bytes" - "context" - _ "embed" - "encoding/json" - "fmt" - - "github.com/opensearch-project/opensearch-go" - "github.com/opensearch-project/opensearch-go/opensearchapi" -) - -//go:embed indexed_mapping.json -var indexedMappingJSON string - -func CreateIndex(ctx context.Context, client *opensearch.Client, index string) error { - indexCreateBody, err := GetIndexDefinition() - if err != nil { - return err - } - - _, err = client.Indices.Create( - index, - client.Indices.Create.WithContext(ctx), - func(request *opensearchapi.IndicesCreateRequest) { - request.Body = bytes.NewReader(indexCreateBody) - }) - return err -} - -func UpdateMapping(ctx context.Context, client *opensearch.Client, index string) error { - updateMapping, err := json.Marshal(getMapping()) - if err != nil { - return err - } - - res, err := client.Indices.PutMapping( - bytes.NewReader(updateMapping), - client.Indices.PutMapping.WithContext(ctx), - client.Indices.PutMapping.WithIndex(index), - ) - - if err != nil { - return err - } - - if res.IsError() { - return fmt.Errorf("request ended with status : %s", res.Status()) - } - - return nil -} - -func GetIndexDefinition() ([]byte, error) { - return json.Marshal(struct { - Mapping Mappings `json:"mappings"` - }{ - Mapping: getMapping(), - }) -} - -func getMapping() Mappings { - indexedMapping := map[string]Property{} - if err := json.Unmarshal([]byte(indexedMappingJSON), &indexedMapping); err != nil { - panic(err) - } - - f := false - return Mappings{ - Properties: map[string]Property{ - "kind": { - Type: "keyword", - }, - "ledger": { - Type: "keyword", - }, - "stack": { - Type: "keyword", - }, - "when": { - Type: "date", - }, - "data": { - Type: "object", - Enabled: &f, - }, - "indexed": { - Type: "object", - Mappings: Mappings{ - Properties: indexedMapping, - }, - }, - }, - } -} - -type Property struct { - Mappings - Type string `json:"type,omitempty"` - Store bool `json:"store,omitempty"` - CopyTo string `json:"copy_to,omitempty"` - Enabled *bool `json:"enabled,omitempty"` -} - -type DynamicTemplate map[string]interface{} - -type Mappings struct { - DynamicTemplates []DynamicTemplate `json:"dynamic_templates,omitempty"` - Properties map[string]Property `json:"properties,omitempty"` -} - -type Template struct { - IndexPatterns []string `json:"index_patterns"` - Mappings Mappings `json:"mappings"` -} - -// -//func DefaultMapping(patterns ...string) Template { -// f := false -// return Template{ -// IndexPatterns: patterns, -// Mappings: Mappings{ -// DynamicTemplates: []DynamicTemplate{ -// { -// "strings": map[string]interface{}{ -// "match_mapping_type": "string", -// "mapping": map[string]interface{}{ -// "type": "keyword", -// }, -// }, -// }, -// }, -// Properties: map[string]Property{ -// "kind": { -// Type: "keyword", -// }, -// "ledger": { -// Type: "keyword", -// }, -// "when": { -// Type: "date", -// }, -// "data": { -// Type: "object", -// Enabled: &f, -// }, -// "indexed": { -// Type: "object", -// }, -// }, -// }, -// } -//} -// -//func LoadMapping(ctx context.Context, client *opensearch.Client, m Template) error { -// data, err := json.Marshal(m) -// if err != nil { -// return err -// } -// -// res, err := opensearchapi.IndicesPutTemplateRequest{ -// Body: bytes.NewReader(data), -// Name: "search_mapping", -// }.Do(ctx, client) -// -// if err != nil { -// return err -// } -// if res.IsError() { -// return errors.New(res.String()) -// } -// return nil -//} -// -//func LoadDefaultMapping(ctx context.Context, client *opensearch.Client, indices ...string) error { -// return LoadMapping(ctx, client, DefaultMapping(indices...)) -//} diff --git a/ee/search/pkg/searchengine/query.go b/ee/search/pkg/searchengine/query.go deleted file mode 100644 index 904b8ea610..0000000000 --- a/ee/search/pkg/searchengine/query.go +++ /dev/null @@ -1,240 +0,0 @@ -package searchengine - -import ( - "context" - "encoding/json" - - "github.com/aquasecurity/esquery" - search "github.com/formancehq/search/pkg" - "github.com/formancehq/search/pkg/es" - "github.com/pkg/errors" -) - -type baseQuery struct { - Ledgers []string `json:"ledgers"` - Terms []string `json:"terms"` - PageSize uint64 `json:"pageSize"` - TermPolicy string `json:"policy"` -} - -func (q *baseQuery) WithLedgers(ledgers ...string) { - q.Ledgers = ledgers -} - -func (q *baseQuery) WithTerms(terms ...string) { - q.Terms = terms -} - -func (q *baseQuery) WithPageSize(pageSize uint64) { - q.PageSize = pageSize -} - -func (q *baseQuery) WithPolicy(policy string) { - q.TermPolicy = policy -} - -func (q *baseQuery) buildList(hits []es.ResponseHit) ([]json.RawMessage, error) { - res := make([]json.RawMessage, 0) - for _, hit := range hits { - src := search.Source{} - err := json.Unmarshal(hit.Source, &src) - if err != nil { - return nil, err - } - res = append(res, src.Data) - } - return res, nil -} - -type Sort struct { - Key string `json:"key"` - Order esquery.Order `json:"order"` -} - -type SingleDocTypeSearchResponse struct { - Items []json.RawMessage - Total es.ResponseHitsTotal -} - -type SingleDocTypeSearch struct { - baseQuery - Sort []Sort `json:"sort"` - Target string `json:"target"` - SearchAfter []interface{} `json:"after"` -} - -func (q *SingleDocTypeSearch) Do(ctx context.Context, e Engine) (*SingleDocTypeSearchResponse, error) { - - filter := esquery.Bool(). - Must(esquery.Match("kind", q.Target)) - - should := make([]esquery.Mappable, 0) - if len(q.Ledgers) > 0 { - should = append(should, esquery.Bool().Should( - func() []esquery.Mappable { - res := make([]esquery.Mappable, 0) - for _, l := range q.Ledgers { - res = append(res, esquery.Match("ledger", l)) - } - res = append(res, esquery.Bool().MustNot(esquery.Exists("ledger"))) - return res - }()..., - )) - filter = filter.MinimumShouldMatch(1).Should(should...) - } - - query := esquery.Bool().Filter(filter) - if len(q.Terms) > 0 { - terms, err := ParseTerms(q.Terms...) - if err != nil { - return nil, errors.Wrap(err, "parsing terms") - } - if q.TermPolicy == TermPolicyOR { - query = query.Should(terms...).MinimumShouldMatch(1) - } else { - query = query.Must(terms...) - } - } - - req := esquery.Search().Query(query) - if len(q.SearchAfter) != 0 { - req = req.SearchAfter(q.SearchAfter...) - } - for _, sort := range q.Sort { - req.Sort("indexed."+sort.Key, sort.Order) - } - req.Size(q.PageSize) - - res, err := e.doRequest(ctx, req.Map()) - if err != nil { - return nil, err - } - - list, err := q.buildList(res.Hits.Hits) - if err != nil { - return nil, err - } - - return &SingleDocTypeSearchResponse{ - Items: list, - Total: res.Hits.Total, - }, nil -} - -func (q *SingleDocTypeSearch) WithSort(field string, order esquery.Order) { - q.Sort = append(q.Sort, struct { - Key string `json:"key"` - Order esquery.Order `json:"order"` - }{Key: field, Order: order}) -} - -func (q *SingleDocTypeSearch) WithSearchAfter(after []interface{}) { - q.SearchAfter = after -} - -func (q *SingleDocTypeSearch) WithTarget(target string) { - q.Target = target -} - -func NewSingleDocTypeSearch(target string) *SingleDocTypeSearch { - return &SingleDocTypeSearch{ - baseQuery: baseQuery{ - PageSize: 5, - }, - Target: target, - } -} - -type MultiDocTypeSearchResponse map[string][]json.RawMessage - -type MultiDocTypeSearch struct { - baseQuery -} - -func (q *MultiDocTypeSearch) Do(ctx context.Context, e Engine) (MultiDocTypeSearchResponse, error) { - - result := MultiDocTypeSearchResponse{} - filter := esquery.Bool() - - should := make([]esquery.Mappable, 0) - if len(q.Ledgers) > 0 { - should = append(should, esquery.Bool().Should( - func() []esquery.Mappable { - res := make([]esquery.Mappable, 0) - for _, l := range q.Ledgers { - res = append(res, esquery.Match("ledger", l)) - } - res = append(res, esquery.Bool().MustNot(esquery.Exists("ledger"))) - return res - }()..., - )) - filter = filter.MinimumShouldMatch(1).Should(should...) - } - - query := esquery.Bool().Filter(filter) - if len(q.Terms) > 0 { - terms, err := ParseTerms(q.Terms...) - if err != nil { - return nil, errors.Wrap(err, "parsing terms") - } - query = query.Must(terms...) - } - - req := esquery.Search().Query(query) - m := req.Map() - m["collapse"] = map[string]interface{}{ - "field": "kind", - "inner_hits": map[string]interface{}{ - "name": "docs", - "size": q.PageSize, - "sort": []map[string]interface{}{ - { - "when": "desc", - }, - }, - }, - } - - res, err := e.doRequest(ctx, m) - if err != nil { - return nil, err - } - - for _, hit := range res.Hits.Hits { - objects, err := q.buildList(hit.InnerHits["docs"].Hits.Hits) - if err != nil { - return nil, err - } - result[hit.Fields["kind"][0]] = objects - } - - return result, nil -} - -func NewMultiDocTypeSearch() *MultiDocTypeSearch { - return &MultiDocTypeSearch{ - baseQuery{PageSize: 5}, - } -} - -type RawQuery struct { - Body json.RawMessage `json:"body"` -} - -type RawQueryResponse *es.Response - -func (q RawQuery) WithPageSize(pageSize uint64) { -} - -func (q RawQuery) Do(ctx context.Context, e Engine) (RawQueryResponse, error) { - var query map[string]interface{} - err := json.Unmarshal(q.Body, &query) - if err != nil { - return nil, errors.Wrap(err, "query.invalid_body") - } - res, err := e.doRequest(ctx, query) - if err != nil { - return nil, err - } - return RawQueryResponse(res), nil -} diff --git a/ee/search/pkg/searchengine/term.go b/ee/search/pkg/searchengine/term.go deleted file mode 100644 index f1f84109ef..0000000000 --- a/ee/search/pkg/searchengine/term.go +++ /dev/null @@ -1,100 +0,0 @@ -package searchengine - -import ( - "fmt" - "regexp" - "strings" - - "github.com/aquasecurity/esquery" -) - -type operatorMapper func(key string, value interface{}) esquery.Mappable - -type operator struct { - symbol string - mapper operatorMapper -} - -const ( - TermPolicyAND = "AND" - TermPolicyOR = "OR" -) - -var ( - operatorGt = &operator{ - symbol: ">", - mapper: func(key string, value interface{}) esquery.Mappable { - return esquery.Range(key).Gt(value) - }, - } - operatorLt = &operator{ - symbol: "<", - mapper: func(key string, value interface{}) esquery.Mappable { - return esquery.Range(key).Lt(value) - }, - } - operatorGte = &operator{ - symbol: ">=", - mapper: func(key string, value interface{}) esquery.Mappable { - return esquery.Range(key).Gte(value) - }, - } - operatorLte = &operator{ - symbol: "<=", - mapper: func(key string, value interface{}) esquery.Mappable { - return esquery.Range(key).Lte(value) - }, - } - operatorEquals = &operator{ - symbol: "=", - mapper: func(key string, value interface{}) esquery.Mappable { - return esquery.Match(key, value) - }, - } - operators = map[string]*operator{ - operatorGt.symbol: operatorGt, - operatorLt.symbol: operatorLt, - operatorGte.symbol: operatorGte, - operatorLte.symbol: operatorLte, - operatorEquals.symbol: operatorEquals, - } - - matchRegexp = regexp.MustCompile("([^<=>:]+)([<=>]+)(.+)") - - fieldMap = func(v string) string { - return "indexed." + v - } - - defaultFieldComputer = func(key, value string, mapper operatorMapper) (esquery.Mappable, error) { - return mapper(fieldMap(key), value), nil - } -) - -func ParseTerm(v string) (esquery.Mappable, error) { - if !matchRegexp.MatchString(v) { - return esquery.CustomQuery(map[string]interface{}{ - "query_string": map[string]interface{}{ - "query": fmt.Sprintf("*%s*", strings.Replace(v, ":", "\\:", 1)), - }, - }), nil - } - matches := matchRegexp.FindStringSubmatch(v) - var ( - key = matches[1] - value = matches[3] - operator = operators[matches[2]] - ) - return defaultFieldComputer(key, value, operator.mapper) -} - -func ParseTerms(terms ...string) ([]esquery.Mappable, error) { - ret := make([]esquery.Mappable, 0) - for _, termStr := range terms { - term, err := ParseTerm(termStr) - if err != nil { - return nil, err - } - ret = append(ret, term) - } - return ret, nil -} diff --git a/ee/search/pkg/searchhttp/cursor.go b/ee/search/pkg/searchhttp/cursor.go deleted file mode 100644 index ff81f7119a..0000000000 --- a/ee/search/pkg/searchhttp/cursor.go +++ /dev/null @@ -1,51 +0,0 @@ -package searchhttp - -import ( - "bytes" - "encoding/base64" - "encoding/json" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/search/pkg/searchengine" -) - -type cursorTokenInfo struct { - Target string `json:"target"` - Sort []searchengine.Sort `json:"sort"` - SearchAfter []interface{} `json:"searchAfter"` - Ledgers []string `json:"ledgers"` - PageSize uint64 `json:"pageSize"` - TermPolicy string `json:"termPolicy"` - Reverse bool `json:"reverse"` - Terms []string `json:"terms"` -} - -func DecodeCursorToken(v string, c *cursorTokenInfo) error { - return json.NewDecoder(base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(v))).Decode(&c) -} - -func EncodeCursorToken(c cursorTokenInfo) string { - buf := bytes.NewBufferString("") - enc := base64.NewEncoder(base64.URLEncoding, buf) - err := json.NewEncoder(enc).Encode(c) - if err != nil { - panic(err) - } - enc.Close() - return buf.String() -} - -type Total struct { - Value uint64 `json:"value,omitempty"` - Rel string `json:"relation,omitempty"` -} - -type Cursor[T any] struct { - bunpaginate.Cursor[T] - Total Total `json:"value"` -} - -type BaseResponse[T any] struct { - Cursor *Cursor[T] `json:"cursor,omitempty"` -} diff --git a/ee/search/pkg/searchhttp/cursor_test.go b/ee/search/pkg/searchhttp/cursor_test.go deleted file mode 100644 index 3f8df61aa3..0000000000 --- a/ee/search/pkg/searchhttp/cursor_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package searchhttp - -import ( - "testing" - - "github.com/aquasecurity/esquery" - "github.com/formancehq/search/pkg/searchengine" - "github.com/stretchr/testify/require" -) - -func TestNextToken(t *testing.T) { - nti := cursorTokenInfo{ - Target: "ACCOUNT", - Sort: []searchengine.Sort{ - { - Key: "slug", - Order: esquery.OrderDesc, - }, - }, - SearchAfter: []interface{}{ - "ACCOUNT-2", - }, - Ledgers: []string{"quickstart"}, - } - tok := EncodeCursorToken(nti) - decoded := cursorTokenInfo{} - require.NoError(t, DecodeCursorToken(tok, &decoded)) - require.EqualValues(t, nti, decoded) -} diff --git a/ee/search/pkg/searchhttp/http.go b/ee/search/pkg/searchhttp/http.go deleted file mode 100644 index b7706b7bdb..0000000000 --- a/ee/search/pkg/searchhttp/http.go +++ /dev/null @@ -1,305 +0,0 @@ -package searchhttp - -import ( - "bytes" - "encoding/json" - "io" - "math" - "net/http" - "strconv" - "strings" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/aquasecurity/esquery" - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/search/pkg/searchengine" - "github.com/pkg/errors" - "github.com/tidwall/gjson" -) - -type BaseQuery interface { - WithPageSize(pageSize uint64) -} - -func resolveQuery(r *http.Request) (*cursorTokenInfo, BaseQuery, error) { - var ( - target string - raw json.RawMessage - cursorToken string - info *cursorTokenInfo - ) - - if r.ContentLength > 0 { - type resolveQuery struct { - Target string `json:"target"` - CursorToken string `json:"cursor"` - Raw json.RawMessage `json:"raw"` - } - rq := &resolveQuery{} - buf := bytes.NewBufferString("") - err := json.NewDecoder(io.TeeReader(r.Body, buf)).Decode(rq) - if err != nil { - return nil, nil, errors.Wrap(err, "first phase decoding") - } - r.Body = io.NopCloser(buf) - target = rq.Target - cursorToken = rq.CursorToken - raw = rq.Raw - } else { - target = r.Form.Get("target") - cursorToken = r.Form.Get("cursor") - } - - if raw != nil { - return nil, &searchengine.RawQuery{ - Body: raw, - }, nil - } - - var searchQuery BaseQuery - if cursorToken == "" { - if target == "" { - sq := searchengine.NewMultiDocTypeSearch() - sq.WithTerms(r.Form["terms"]...) - sq.WithLedgers(r.Form["ledgers"]...) - if termPolicy := r.Form.Get("term-policy"); termPolicy != "" { - sq.WithPolicy(termPolicy) - } - searchQuery = sq - } else { - sq := searchengine.NewSingleDocTypeSearch(target) - if after := r.Form.Get("after"); after != "" { - sq.WithSearchAfter([]interface{}{after}) - } - if sorts := r.Form["sort"]; len(sorts) > 0 { - for _, sort := range sorts { - parts := strings.Split(sort, ":") - sq.WithSort(parts[0], esquery.Order(parts[1])) - } - } - sq.WithTerms(r.Form["terms"]...) - sq.WithLedgers(r.Form["ledgers"]...) - if termPolicy := r.Form.Get("policy"); termPolicy != "" { - sq.WithPolicy(termPolicy) - } - searchQuery = sq - } - if r.ContentLength > 0 { - err := json.NewDecoder(r.Body).Decode(&searchQuery) - if err != nil { - return nil, nil, errors.Wrap(err, "decoding query to target struct") - } - } - } else { - info = &cursorTokenInfo{} - err := DecodeCursorToken(cursorToken, info) - if err != nil { - return nil, nil, err - } - q := searchengine.NewSingleDocTypeSearch(info.Target) - for _, s := range info.Sort { - q.WithSort(s.Key, s.Order) - } - q.WithTarget(info.Target) - q.WithSearchAfter(info.SearchAfter) - q.WithLedgers(info.Ledgers...) - q.WithPageSize(info.PageSize) - q.WithPolicy(info.TermPolicy) - q.WithTerms(info.Terms...) - searchQuery = q - } - - if pageSize := r.Form.Get("pageSize"); pageSize != "" { - pageSize, err := strconv.ParseInt(pageSize, 10, 64) - if err != nil { - return nil, nil, errors.Wrap(err, "parsing pageSize") - } - searchQuery.WithPageSize(uint64(pageSize)) - } - - switch qq := searchQuery.(type) { - case *searchengine.SingleDocTypeSearch: // Default sort - if len(qq.Sort) == 0 { - // TODO: Remove the sort and ask frontend to specify the sort to be agnostic - switch qq.Target { - case "ACCOUNT": - qq.WithSort("address", esquery.OrderDesc) - case "AUDIT": - qq.WithSort("audit", esquery.OrderDesc) - case "TRANSACTION": - qq.WithSort("txid", esquery.OrderDesc) - case "PAYMENT": - qq.WithSort("reference", esquery.OrderDesc) - case "PAYMENT_TRANSFER_INITIATION": - qq.WithSort("id", esquery.OrderDesc) - case "PAYMENT_BALANCE": - qq.WithSort("createdAt", esquery.OrderDesc) - case "PAYMENT_BANK_ACCOUNT": - qq.WithSort("id", esquery.OrderDesc) - } - } - } - - return info, searchQuery, nil -} - -func Handler(engine searchengine.Engine) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - if r.Method != http.MethodPost && r.Method != http.MethodGet { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - cursor, searchQuery, err := resolveQuery(r) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - switch qq := searchQuery.(type) { - case *searchengine.RawQuery: - res, err := qq.Do(r.Context(), engine) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if err := json.NewEncoder(w).Encode(res); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - return - case *searchengine.SingleDocTypeSearch: - qq.PageSize++ - searchResponse, err := qq.Do(r.Context(), engine) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - return - } - - reverseOrder := func(sorts ...searchengine.Sort) []searchengine.Sort { - ret := make([]searchengine.Sort, 0) - for _, aSort := range qq.Sort { // Use of next token, to get previous token, we need to invert the sort - order := aSort.Order - if order == esquery.OrderAsc { - order = esquery.OrderDesc - } else { - order = esquery.OrderAsc - } - ret = append(ret, searchengine.Sort{ - Key: aSort.Key, - Order: order, - }) - } - return ret - } - - items := searchResponse.Items - var ( - hasMore bool - reverse bool - ) - if cursor != nil && cursor.Reverse { - reverse = true - } - if len(items) > int(qq.PageSize)-1 { - hasMore = true - items = items[0 : qq.PageSize-1] - } - if reverse { - for i := 0; i < len(items)/2; i++ { - items[i], items[len(items)-1-i] = items[len(items)-1-i], items[i] - } - } - - next := "" - if hasMore || reverse { - item := items[len(items)-1] - sort := qq.Sort - if reverse { - sort = reverseOrder(sort...) - } - nextTokenInfo := cursorTokenInfo{ - Target: qq.Target, - Sort: sort, - Ledgers: qq.Ledgers, - PageSize: qq.PageSize - 1, - TermPolicy: qq.TermPolicy, - Terms: qq.Terms, - } - for _, s := range qq.Sort { - value := gjson.Get(string(item), s.Key) - nextTokenInfo.SearchAfter = append(nextTokenInfo.SearchAfter, value.Value()) - } - next = EncodeCursorToken(nextTokenInfo) - } - previous := "" - if cursor != nil && (!reverse || (reverse && hasMore)) { - var sort []searchengine.Sort - if cursor.Reverse { - sort = cursor.Sort - } else { - sort = reverseOrder(qq.Sort...) - } - prevTokenInfo := cursorTokenInfo{ - Target: qq.Target, - Sort: sort, - Ledgers: qq.Ledgers, - PageSize: qq.PageSize - 1, - TermPolicy: qq.TermPolicy, - Reverse: true, - Terms: qq.Terms, - } - firstItem := items[0] - for _, s := range qq.Sort { - value := gjson.Get(string(firstItem), s.Key) - prevTokenInfo.SearchAfter = append(prevTokenInfo.SearchAfter, value.Value()) - } - previous = EncodeCursorToken(prevTokenInfo) - } - - resp := BaseResponse[json.RawMessage]{ - Cursor: &Cursor[json.RawMessage]{ - Cursor: bunpaginate.Cursor[json.RawMessage]{ - PageSize: int(math.Min(float64(qq.PageSize-1), float64(len(items)))), - HasMore: next != "", - Previous: previous, - Next: next, - Data: items, - }, - Total: Total{ - Value: uint64(searchResponse.Total.Value), - Rel: searchResponse.Total.Relation, - }, - }, - } - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("error encoding json response: %s", err) - } - - case *searchengine.MultiDocTypeSearch: - searchResponse, err := qq.Do(r.Context(), engine) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - return - } - resp := api.BaseResponse[searchengine.MultiDocTypeSearchResponse]{ - Data: &searchResponse, - } - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("error encoding json response: %s", err) - } - } - } -} diff --git a/ee/search/pkg/searchhttp/http_test.go b/ee/search/pkg/searchhttp/http_test.go deleted file mode 100644 index 411f254671..0000000000 --- a/ee/search/pkg/searchhttp/http_test.go +++ /dev/null @@ -1,451 +0,0 @@ -package searchhttp - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/aquasecurity/esquery" - "github.com/formancehq/go-libs/api" - search "github.com/formancehq/search/pkg" - "github.com/formancehq/search/pkg/es" - "github.com/formancehq/search/pkg/searchengine" - "github.com/stretchr/testify/require" -) - -type queryChecker func(*testing.T, map[string]interface{}) - -func hasPageSize(pageSize int) queryChecker { - return func(t *testing.T, m map[string]interface{}) { - require.EqualValues(t, pageSize, m["size"]) - } -} - -func hasSort(sorts ...searchengine.Sort) queryChecker { - expected := esquery.Sort{} - for _, sort := range sorts { - expected = append(expected, map[string]interface{}{ - "indexed." + sort.Key: map[string]interface{}{ - "order": sort.Order, - }, - }) - } - return func(t *testing.T, m map[string]interface{}) { - require.EqualValues(t, expected, m["sort"]) - } -} - -func hasSearchAfter(searchAfter ...interface{}) queryChecker { - return func(t *testing.T, m map[string]interface{}) { - require.EqualValues(t, searchAfter, m["search_after"]) - } -} - -func TestMultiSearch(t *testing.T) { - type testCase struct { - name string - results map[string][]interface{} - expected interface{} - } - - now := time.Now().Round(time.Second).UTC() - var testCases = []testCase{ - { - name: "nominal", - results: map[string][]interface{}{ - "ACCOUNT": { - map[string]any{ - "address": "user:001", - "metadata": nil, - }, - map[string]any{ - "address": "user:002", - "metadata": map[string]any{ - "foo": "bar", - }, - }, - }, - "TRANSACTION": { - map[string]any{ - "txid": 1, - "postings": []map[string]any{{ - "source": "world", - "destination": "central_bank", - "amount": 100, - "asset": "USD", - }}, - "reference": "tx1", - "timestamp": now, - "metadata": map[string]any{ - "foo": "bar", - }, - }, - }, - }, - expected: api.BaseResponse[map[string]interface{}]{ - Data: &map[string]interface{}{ - "ACCOUNT": []interface{}{ - map[string]interface{}{ - "address": "user:001", - "metadata": nil, - }, - map[string]interface{}{ - "address": "user:002", - "metadata": map[string]interface{}{ - "foo": "bar", - }, - }, - }, - "TRANSACTION": []interface{}{ - map[string]interface{}{ - "txid": float64(1), - "reference": "tx1", - "timestamp": now.Format(time.RFC3339), - "metadata": map[string]interface{}{ - "foo": "bar", - }, - "postings": []interface{}{ - map[string]interface{}{ - "source": "world", - "destination": "central_bank", - "amount": float64(100), - "asset": "USD", - }, - }, - }, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - esResponse := &es.Response{ - Hits: es.ResponseHits{ - Hits: []es.ResponseHit{}, - }, - } - - for key, sources := range tc.results { - esResponse.Hits.Hits = append(esResponse.Hits.Hits, es.ResponseHit{ - Fields: map[string][]string{ - "kind": {key}, - }, - InnerHits: map[string]struct { - Hits es.ResponseHits `json:"hits"` - }{ - "docs": { - Hits: es.ResponseHits{ - Hits: func() []es.ResponseHit { - ret := make([]es.ResponseHit, 0) - for _, source := range sources { - sourceData, err := json.Marshal(source) - require.NoError(t, err) - - data, err := json.Marshal(search.Source{ - Kind: key, - Ledger: "testing", - When: time.Time{}, - Data: sourceData, - }) - require.NoError(t, err) - ret = append(ret, es.ResponseHit{ - Source: data, - }) - } - return ret - }(), - }, - }, - }, - }) - } - - engine := searchengine.EngineFn( - func(ctx context.Context, m map[string]interface{}) (*es.Response, error) { - return esResponse, nil - }) - - r := Handler(engine) - - query := map[string]interface{}{} - data, err := json.Marshal(query) - require.NoError(t, err) - - rec := httptest.NewRecorder() - req := httptest.NewRequest("POST", "/", bytes.NewBuffer(data)) - r.ServeHTTP(rec, req) - require.Equal(t, http.StatusOK, rec.Result().StatusCode) - - response := api.BaseResponse[map[string]interface{}]{} - err = json.NewDecoder(rec.Body).Decode(&response) - require.NoError(t, err) - - require.EqualValues(t, tc.expected, response) - }) - } - -} - -func TestSingleDocTypeSearch(t *testing.T) { - type testCase struct { - name string - query map[string]interface{} - kind string - results []interface{} - expected interface{} - queryChecker []queryChecker - } - - now := time.Now().Round(time.Second).UTC() - var testCases = []testCase{ - { - name: "nominal", - kind: "ACCOUNT", - query: map[string]interface{}{}, - results: []interface{}{ - map[string]any{ - "address": "user:001", - "metadata": nil, - }, - map[string]any{ - "address": "user:002", - "metadata": map[string]any{ - "foo": "bar", - }, - }, - }, - expected: BaseResponse[map[string]interface{}]{ - Cursor: &Cursor[map[string]interface{}]{ - Cursor: bunpaginate.Cursor[map[string]any]{ - PageSize: 2, - Data: []map[string]interface{}{ - { - "address": "user:001", - "metadata": nil, - }, - { - "address": "user:002", - "metadata": map[string]interface{}{ - "foo": "bar", - }, - }, - }, - }, - Total: Total{ - Value: 2, - Rel: "eq", - }, - }, - }, - }, - { - name: "pageSize", - kind: "ACCOUNT", - query: map[string]interface{}{ - "pageSize": 1, - }, - queryChecker: []queryChecker{ - hasPageSize(2), - hasSort(searchengine.Sort{ - Key: "address", - Order: esquery.OrderDesc, - }), - }, - results: []interface{}{ - map[string]any{ - "address": "user:002", - "metadata": map[string]any{ - "foo": "bar", - }, - }, - }, - expected: BaseResponse[map[string]interface{}]{ - Cursor: &Cursor[map[string]interface{}]{ - Cursor: bunpaginate.Cursor[map[string]any]{ - PageSize: 1, - HasMore: false, - Data: []map[string]interface{}{ - { - "address": "user:002", - "metadata": map[string]interface{}{ - "foo": "bar", - }, - }, - }, - }, - Total: Total{ - Value: 1, - Rel: "eq", - }, - }, - }, - }, - { - name: "search-after", - kind: "ACCOUNT", - query: map[string]interface{}{ - "after": []interface{}{ - "user:002", - }, - }, - queryChecker: []queryChecker{ - hasSort(searchengine.Sort{ - Key: "address", - Order: esquery.OrderDesc, - }), - hasSearchAfter("user:002"), - }, - results: []interface{}{ - map[string]any{ - "address": "user:001", - "metadata": nil, - }, - }, - expected: BaseResponse[map[string]interface{}]{ - Cursor: &Cursor[map[string]interface{}]{ - Cursor: bunpaginate.Cursor[map[string]any]{ - PageSize: 1, - HasMore: false, - Data: []map[string]interface{}{ - { - "address": "user:001", - "metadata": nil, - }, - }, - }, - Total: Total{ - Value: 1, - Rel: "eq", - }, - }, - }, - }, - { - name: "next-page", - kind: "ACCOUNT", - query: map[string]interface{}{ - "cursor": EncodeCursorToken(cursorTokenInfo{ - Target: "ACCOUNT", - Sort: []searchengine.Sort{ - { - Key: "address", - Order: esquery.OrderDesc, - }, - }, - SearchAfter: []interface{}{ - "user:002", - }, - PageSize: 5, - }), - }, - queryChecker: []queryChecker{ - hasPageSize(6), - hasSort(searchengine.Sort{ - Key: "address", - Order: esquery.OrderDesc, - }), - hasSearchAfter("user:002"), - }, - results: []interface{}{ - map[string]any{ - "address": "user:001", - "metadata": nil, - }, - }, - expected: BaseResponse[map[string]interface{}]{ - Cursor: &Cursor[map[string]interface{}]{ - Cursor: bunpaginate.Cursor[map[string]any]{ - PageSize: 1, - HasMore: false, - Previous: EncodeCursorToken(cursorTokenInfo{ - Target: "ACCOUNT", - Sort: []searchengine.Sort{ - { - Key: "address", - Order: esquery.OrderAsc, - }, - }, - SearchAfter: []interface{}{ - "user:001", - }, - PageSize: 5, - Reverse: true, - }), - Data: []map[string]interface{}{ - { - "address": "user:001", - "metadata": nil, - }, - }, - }, - Total: Total{ - Value: 1, - Rel: "eq", - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - esResponse := &es.Response{ - Hits: es.ResponseHits{ - Hits: []es.ResponseHit{}, - Total: es.ResponseHitsTotal{ - Value: len(tc.results), - Relation: "eq", - }, - }, - } - - for _, source := range tc.results { - sourceData, err := json.Marshal(source) - require.NoError(t, err) - - data, err := json.Marshal(search.Source{ - Kind: tc.kind, - Ledger: "testing", - When: now, - Data: sourceData, - }) - require.NoError(t, err) - esResponse.Hits.Hits = append(esResponse.Hits.Hits, es.ResponseHit{ - Source: data, - }) - } - - engine := searchengine.EngineFn( - func(ctx context.Context, m map[string]interface{}) (*es.Response, error) { - for _, check := range tc.queryChecker { - check(t, m) - } - return esResponse, nil - }) - - r := Handler(engine) - - tc.query["target"] = tc.kind - data, err := json.Marshal(tc.query) - require.NoError(t, err) - - rec := httptest.NewRecorder() - req := httptest.NewRequest("POST", "/", bytes.NewBuffer(data)) - r.ServeHTTP(rec, req) - require.Equal(t, http.StatusOK, rec.Result().StatusCode) - - response := BaseResponse[map[string]interface{}]{} - err = json.NewDecoder(rec.Body).Decode(&response) - require.NoError(t, err) - require.EqualValues(t, tc.expected, response) - }) - } -} diff --git a/ee/search/pkg/source.go b/ee/search/pkg/source.go deleted file mode 100644 index d5ce543ade..0000000000 --- a/ee/search/pkg/source.go +++ /dev/null @@ -1,14 +0,0 @@ -package search - -import ( - "encoding/json" - "time" -) - -type Source struct { - ID string `json:"_id"` - Kind string `json:"kind"` - Ledger string `json:"ledger"` - When time.Time `json:"when"` - Data json.RawMessage `json:"data"` -} diff --git a/ee/search/scratch.Dockerfile b/ee/search/scratch.Dockerfile deleted file mode 100644 index cd4f59d52b..0000000000 --- a/ee/search/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY search /usr/bin/search -ENV OTEL_SERVICE_NAME search -ENTRYPOINT ["/usr/bin/search"] -CMD ["serve"] diff --git a/ee/search/tests/pipelines/ACCOUNT.json b/ee/search/tests/pipelines/ACCOUNT.json deleted file mode 100644 index f4500da74c..0000000000 --- a/ee/search/tests/pipelines/ACCOUNT.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "Account pipeline", - "processors": [ - { - "set": { - "field": "indexed.address", - "value": "{{data.address}}" - } - } - ] -} diff --git a/ee/search/tests/pipelines/PAYMENT.json b/ee/search/tests/pipelines/PAYMENT.json deleted file mode 100644 index cd797b4833..0000000000 --- a/ee/search/tests/pipelines/PAYMENT.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "description": "Payment pipeline", - "processors": [ - { - "set": { - "field": "indexed.provider", - "value": "{{data.provider}}" - } - }, - { - "set": { - "field": "indexed.reference", - "value": "{{data.reference}}" - } - }, - { - "set": { - "field": "indexed.scheme", - "value": "{{data.scheme}}" - } - }, - { - "set": { - "field": "indexed.type", - "value": "{{data.type}}" - } - }, - { - "script": { - "source": "String asset = ctx.data.value.asset; double amount = ctx.data.value.amount; String[] parts = asset.splitOnToken(\"/\"); int length = parts.length; int decimal = 0; if (length > 1) { decimal = Integer.parseInt(parts[1]); amount = amount * Math.pow(10, -decimal); } ctx.indexed.amount = amount;" - } - } - ] -} diff --git a/ee/search/tests/pipelines/TRANSACTION.json b/ee/search/tests/pipelines/TRANSACTION.json deleted file mode 100644 index 7797774b77..0000000000 --- a/ee/search/tests/pipelines/TRANSACTION.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "description": "Transaction pipeline", - "processors": [ - { - "set": { - "field": "indexed.reference", - "value": "{{data.reference}}" - } - }, - { - "set": { - "field": "indexed.timestamp", - "value": "{{data.timestamp}}" - } - }, - { - "set": { - "field": "indexed.metadata", - "value": "{{data.metadata}}" - } - }, - { - "convert" : { - "field" : "indexed.metadata", - "type": "string" - } - }, - { - "set": { - "field": "indexed.id", - "value": "{{data.id}}" - } - }, - { - "convert": { - "field": "indexed.id", - "type": "long" - } - }, - { - "script": { - "if": "ctx.data.postings != null", - "source": "ArrayList amounts = new ArrayList(); for (def posting: ctx.data.postings) { String asset = posting.asset; String[] parts = asset.splitOnToken(\"/\"); int length = parts.length; int decimal = 0; double amount = posting.amount; if (length > 1) { decimal = Integer.parseInt(parts[1]); amount = amount * Math.pow(10, -decimal); } amounts.add(amount); } ctx.indexed.amount = amounts; " - } - }, - { - "foreach": { - "field": "data.postings", - "if": "ctx.data.postings != null", - "processor": { - "append": { - "field": "indexed.asset", - "value": [ - "{{_ingest._value.asset}}" - ] - } - } - } - }, - { - "foreach": { - "field": "data.postings", - "if": "ctx.data.postings != null", - "processor": { - "append": { - "field": "indexed.source", - "value": [ - "{{_ingest._value.source}}" - ] - } - } - } - }, - { - "foreach": { - "field": "data.postings", - "if": "ctx.data.postings != null", - "processor": { - "append": { - "field": "indexed.destination", - "value": [ - "{{_ingest._value.destination}}" - ] - } - } - } - } - ] -} diff --git a/ee/stargate/.gitignore b/ee/stargate/.gitignore deleted file mode 100644 index 38ad8dde44..0000000000 --- a/ee/stargate/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.idea -vendor -stargate diff --git a/ee/stargate/.goreleaser.yml b/ee/stargate/.goreleaser.yml deleted file mode 100644 index 35d8fdaef1..0000000000 --- a/ee/stargate/.goreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -project_name: stargate -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: stargate - id: stargate - ldflags: - - -X github.com/formancehq/stack/ee/stargate/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/stack/ee/stargate/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/stack/ee/stargate/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - stargate - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) diff --git a/ee/stargate/Earthfile b/ee/stargate/Earthfile deleted file mode 100644 index 11d12792c4..0000000000 --- a/ee/stargate/Earthfile +++ /dev/null @@ -1,86 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT .. AS ee - -FROM core+base-image - -sources: - WORKDIR src - WORKDIR /src/ee/stargate - COPY go.* . - COPY --dir cmd internal . - COPY main.go . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/stargate - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/stargate"] - CMD ["client"] - COPY (+compile/main) /bin/stargate - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=stargate --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/stargate - WITH DOCKER --pull=postgres:15-alpine - DO --pass-args core+GO_TESTS - END - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"stargate\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=stargate - -lint: - FROM core+builder-image - COPY (+sources/*) /src - COPY --pass-args +tidy/go.* . - WORKDIR /src/ee/stargate - DO --pass-args stack+GO_LINT - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT internal AS LOCAL internal - SAVE ARTIFACT main.go AS LOCAL main.go - -pre-commit: - WAIT - BUILD --pass-args +tidy - END - BUILD --pass-args +lint - -openapi: - RUN echo "not implemented" - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/ee/stargate - DO --pass-args stack+GO_TIDY - -grpc-generate: - FROM core+grpc-base - LET protoName=stargate.proto - COPY $protoName . - DO core+GRPC_GEN --protoName=$protoName - SAVE ARTIFACT generated AS LOCAL internal/generated - -release: - BUILD --pass-args stack+goreleaser --path=ee/stargate \ No newline at end of file diff --git a/ee/stargate/build.Dockerfile b/ee/stargate/build.Dockerfile deleted file mode 100644 index 24b9d977a8..0000000000 --- a/ee/stargate/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY stargate /usr/bin/stargate -ENV OTEL_SERVICE_NAME stargate -ENTRYPOINT ["/usr/bin/stargate"] -CMD ["client"] \ No newline at end of file diff --git a/ee/stargate/cmd/client.go b/ee/stargate/cmd/client.go deleted file mode 100644 index 6165b00542..0000000000 --- a/ee/stargate/cmd/client.go +++ /dev/null @@ -1,121 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/licence" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - app "github.com/formancehq/go-libs/service" - "github.com/formancehq/stack/ee/stargate/internal/client" - "github.com/formancehq/stack/ee/stargate/internal/client/controllers" - "github.com/formancehq/stack/ee/stargate/internal/client/interceptors" - "github.com/spf13/cobra" - "go.uber.org/fx" -) - -const ( - serviceName = "stargate" - - organizationIDFlag = "organization-id" - stackIDFlag = "stack-id" - - bindFlag = "bind" - - stargateServerURLFlag = "stargate-server-url" - gatewayURLFlag = "gateway-url" - - workerPoolMaxWorkersFlag = "worker-pool-max-worker" - workerPoolMaxTasksFlag = "worker-pool-max-tasks" - - ClientChanSizeFlag = "client-chan-size" - HTTPClientTimeoutFlag = "http-client-timeout" - HTTPClientMaxIdleConnsFlag = "http-client-max-idle-conns" - HTTPClientMaxIdleConnsPerHostFlag = "http-client-max-idle-conns-per-host" - - AuthRefreshTokenDurationBeforeExpireTimeFlag = "auth-refresh-token-duration-before-expire-time" - StargateAuthClientIDFlag = "stargate-auth-client-id" - StargateAuthClientSecretFlag = "stargate-auth-client-secret" - StargateAuthIssuerURLFlag = "stargate-auth-issuer-url" - TlsEnabledFlag = "tls-enabled" - TlsInsecureSkipVerifyFlag = "tls-insecure-skip-verify" - TlsCACertificateFlag = "tls-ca-cert" -) - -func newClient() *cobra.Command { - return &cobra.Command{ - Use: "client", - Short: "Launch client", - SilenceUsage: true, - RunE: func(cmd *cobra.Command, args []string) error { - return app.New(cmd.OutOrStdout(), resolveClientOptions(cmd)...).Run(cmd) - }, - } -} - -func resolveClientOptions(cmd *cobra.Command) []fx.Option { - options := make([]fx.Option, 0) - options = append(options, fx.NopLogger) - - workerPoolMaxTasks, _ := cmd.Flags().GetInt(workerPoolMaxTasksFlag) - workerPoolMaxWorkers, _ := cmd.Flags().GetInt(workerPoolMaxWorkersFlag) - organizationID, _ := cmd.Flags().GetString(organizationIDFlag) - stackID, _ := cmd.Flags().GetString(stackIDFlag) - clientChanSize, _ := cmd.Flags().GetInt(ClientChanSizeFlag) - gatewayURL, _ := cmd.Flags().GetString(gatewayURLFlag) - httpClientTimeout, _ := cmd.Flags().GetDuration(HTTPClientTimeoutFlag) - httpClientMaxIdleConns, _ := cmd.Flags().GetInt(HTTPClientMaxIdleConnsFlag) - httpClientMaxIdleConnsPerHost, _ := cmd.Flags().GetInt(HTTPClientMaxIdleConnsPerHostFlag) - stargateAuthIssuerURL, _ := cmd.Flags().GetString(StargateAuthIssuerURLFlag) - authRefreshTokenDuration, _ := cmd.Flags().GetDuration(AuthRefreshTokenDurationBeforeExpireTimeFlag) - stargateAuthClientID, _ := cmd.Flags().GetString(StargateAuthClientIDFlag) - stargateAuthClientSecret, _ := cmd.Flags().GetString(StargateAuthClientSecretFlag) - bind, _ := cmd.Flags().GetString(bindFlag) - stargateServerURL, _ := cmd.Flags().GetString(stargateServerURLFlag) - tlsEnabled, _ := cmd.Flags().GetBool(TlsEnabledFlag) - tlsCaCert, _ := cmd.Flags().GetString(TlsCACertificateFlag) - tlsInsecureSkipVerify, _ := cmd.Flags().GetBool(TlsInsecureSkipVerifyFlag) - - options = append(options, - otlptraces.FXModuleFromFlags(cmd), - otlpmetrics.FXModuleFromFlags(cmd), - licence.FXModuleFromFlags(cmd, serviceName), - fx.Provide(func() client.WorkerPoolConfig { - return client.NewWorkerPoolConfig( - workerPoolMaxWorkers, - workerPoolMaxTasks, - ) - }), - fx.Provide(func() client.Config { - return client.NewClientConfig( - organizationID, - stackID, - clientChanSize, - gatewayURL, - httpClientTimeout, - httpClientMaxIdleConns, - httpClientMaxIdleConnsPerHost, - ) - }), - - fx.Provide(func() interceptors.Config { - return interceptors.NewConfig( - stargateAuthIssuerURL, - authRefreshTokenDuration, - stargateAuthClientID, - stargateAuthClientSecret, - ) - }), - fx.Provide(func() controllers.StargateControllerConfig { - return controllers.NewStargateControllerConfig(Version) - }), - client.Module( - bind, - stargateServerURL, - tlsEnabled, - tlsCaCert, - tlsInsecureSkipVerify, - app.IsDebug(cmd), - ), - ) - - return options -} diff --git a/ee/stargate/cmd/root.go b/ee/stargate/cmd/root.go deleted file mode 100644 index bd4be918de..0000000000 --- a/ee/stargate/cmd/root.go +++ /dev/null @@ -1,62 +0,0 @@ -package cmd - -import ( - "time" - - "github.com/formancehq/go-libs/licence" - "github.com/formancehq/go-libs/otlp/otlpmetrics" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/service" - "github.com/spf13/cobra" -) - -var ( - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func NewRootCommand() *cobra.Command { - root := &cobra.Command{ - Use: "stargate", - Short: "stargate", - DisableAutoGenTag: true, - Version: Version, - } - - version := newVersion() - root.AddCommand(version) - - client := newClient() - root.AddCommand(client) - - client.Flags().String(organizationIDFlag, "", "Organization ID") - client.Flags().String(stackIDFlag, "", "Stack ID") - client.Flags().String(bindFlag, "0.0.0.0:8080", "Listen address for http API") - client.Flags().String(stargateServerURLFlag, "", "Stargate server URL") - client.Flags().String(gatewayURLFlag, "", "Gateway URL") - client.Flags().Int(workerPoolMaxWorkersFlag, 100, "Max worker pool size") - client.Flags().Int(workerPoolMaxTasksFlag, 10000, "Max worker pool tasks") - client.Flags().Int(ClientChanSizeFlag, 1024, "Client chan size") - client.Flags().Duration(HTTPClientTimeoutFlag, 10*time.Second, "HTTP client timeout") - client.Flags().Int(HTTPClientMaxIdleConnsFlag, 100, "HTTP client max idle conns") - client.Flags().Int(HTTPClientMaxIdleConnsPerHostFlag, 2, "HTTP client max idle conns per host") - client.Flags().Duration(AuthRefreshTokenDurationBeforeExpireTimeFlag, 30*time.Second, "Auth refresh token duration") - client.Flags().String(StargateAuthClientIDFlag, "", "Stargate auth client ID") - client.Flags().String(StargateAuthClientSecretFlag, "", "Stargate auth client secret") - client.Flags().String(StargateAuthIssuerURLFlag, "", "Stargate auth issuer") - client.Flags().Bool(TlsEnabledFlag, true, "TLS enabled") - client.Flags().String(TlsCACertificateFlag, "", "TLS cert file") - client.Flags().Bool(TlsInsecureSkipVerifyFlag, false, "TLS insecure skip verify") - - service.AddFlags(client.PersistentFlags()) - licence.AddFlags(client.PersistentFlags()) - otlptraces.AddFlags(root.PersistentFlags()) - otlpmetrics.AddFlags(root.PersistentFlags()) - - return root -} - -func Execute() { - service.Execute(NewRootCommand()) -} diff --git a/ee/stargate/cmd/version.go b/ee/stargate/cmd/version.go deleted file mode 100644 index 9cad90866d..0000000000 --- a/ee/stargate/cmd/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "log" - - "github.com/spf13/cobra" -) - -func newVersion() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Get version", - Run: printVersion, - } -} - -func printVersion(cmd *cobra.Command, args []string) { - log.Printf("Version: %s \n", Version) - log.Printf("Date: %s \n", BuildDate) - log.Printf("Commit: %s \n", Commit) -} diff --git a/ee/stargate/go.mod b/ee/stargate/go.mod deleted file mode 100644 index db3191540b..0000000000 --- a/ee/stargate/go.mod +++ /dev/null @@ -1,85 +0,0 @@ -module github.com/formancehq/stack/ee/stargate - -go 1.22.0 - -toolchain go1.22.7 - -require ( - github.com/alitto/pond v1.8.3 - github.com/formancehq/go-libs v1.7.1 - github.com/go-chi/chi/v5 v5.1.0 - github.com/go-chi/cors v1.2.1 - github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.8.1 - github.com/zitadel/oidc v1.13.4 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/metric v1.30.0 - go.uber.org/fx v1.22.2 - golang.org/x/oauth2 v0.23.0 - golang.org/x/sync v0.8.0 - google.golang.org/grpc v1.67.0 - google.golang.org/protobuf v1.34.2 -) - -require ( - github.com/ThreeDotsLabs/watermill v1.3.7 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx v1.2.30 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/shirou/gopsutil/v4 v4.24.8 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/contrib/instrumentation/host v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - gopkg.in/square/go-jose.v2 v2.6.0 // indirect -) diff --git a/ee/stargate/go.sum b/ee/stargate/go.sum deleted file mode 100644 index 289716e09c..0000000000 --- a/ee/stargate/go.sum +++ /dev/null @@ -1,183 +0,0 @@ -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= -github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= -github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= -github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= -github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shirou/gopsutil/v4 v4.24.8 h1:pVQjIenQkIhqO81mwTaXjTzOMT7d3TZkf43PlVFHENI= -github.com/shirou/gopsutil/v4 v4.24.8/go.mod h1:wE0OrJtj4dG+hYkxqDH3QiBICdKSf04/npcvLLc/oRg= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zitadel/oidc v1.13.4 h1:+k2GKqP9Ld9S2MSFlj+KaNsoZ3J9oy+Ezw51EzSFuC8= -github.com/zitadel/oidc v1.13.4/go.mod h1:3h2DhUcP02YV6q/CA/BG4yla0o6rXjK+DkJGK/dwJfw= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0 h1:V/Cy5A2ydwvyED4ewwXJ441R3QllG+U8tXXVOjPeX4Y= -go.opentelemetry.io/contrib/instrumentation/host v0.55.0/go.mod h1:fsY+EfHPwa1bQcxOUPv1FWaQXAwY+RliLRs6B6qgJes= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0 h1:GotCpbh7YkCHdFs+hYMdvAEyGsBZifFognqrOnBwyJM= -go.opentelemetry.io/contrib/instrumentation/runtime v0.55.0/go.mod h1:6b0AS55EEPj7qP44khqF5dqTUq+RkakDMShFaW1EcA4= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0 h1:WypxHH02KX2poqqbaadmkMYalGyy/vil4HE4PM4nRJc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0/go.mod h1:U79SV99vtvGSEBeeHnpgGJfTsnsdkWLpPN/CcHAzBSI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0 h1:VrMAbeJz4gnVDg2zEzjHG4dEH86j4jO6VYB+NgtGD8s= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.30.0/go.mod h1:qqN/uFdpeitTvm+JDqqnjm517pmQRYxTORbETHq5tOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0 h1:IyFlqNsi8VT/nwYlLJfdM0y1gavxGpEvnf6FtVfZ6X4= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.30.0/go.mod h1:bxiX8eUeKoAEQmbq/ecUT8UqZwCjZW52yJrXJUSozsk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.30.0 h1:QJLT8Pe11jyHBHfSAgYH7kEmT24eX792jZO1bo4BXkM= -go.opentelemetry.io/otel/sdk/metric v1.30.0/go.mod h1:waS6P3YqFNzeP01kuo/MBBYqaoBJl7efRQHOaydhy1Y= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ee/stargate/internal/client/client.go b/ee/stargate/internal/client/client.go deleted file mode 100644 index 652665ddc7..0000000000 --- a/ee/stargate/internal/client/client.go +++ /dev/null @@ -1,297 +0,0 @@ -package client - -import ( - "bytes" - "context" - "io" - "net/http" - "strings" - "time" - - "github.com/alitto/pond" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/stack/ee/stargate/internal/generated" - metrics "github.com/formancehq/stack/ee/stargate/internal/grpcmetrics" - "github.com/formancehq/stack/ee/stargate/internal/opentelemetry" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/propagation" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc/metadata" -) - -type WorkerPoolConfig struct { - MaxWorkers int - MaxTasks int -} - -func NewWorkerPoolConfig(maxWorkers, maxTasks int) WorkerPoolConfig { - return WorkerPoolConfig{ - MaxWorkers: maxWorkers, - MaxTasks: maxTasks, - } -} - -type Config struct { - OrganizationID string - StackID string - ChanSize int - GatewayUrl string - HTTPClientTimeout time.Duration - HTTPMaxIdleConns int - HTTPMaxIdleConnsPerHost int -} - -func NewClientConfig( - organizationID string, - stackID string, - chanSize int, - gatewayUrl string, - httpClientTimeout time.Duration, - httpMaxIdleConns int, - httpMaxIdleConnsPerHost int, -) Config { - return Config{ - OrganizationID: organizationID, - StackID: stackID, - ChanSize: chanSize, - GatewayUrl: gatewayUrl, - HTTPClientTimeout: httpClientTimeout, - HTTPMaxIdleConns: httpMaxIdleConns, - HTTPMaxIdleConnsPerHost: httpMaxIdleConnsPerHost, - } -} - -type Client struct { - logger logging.Logger - config Config - stargateClient generated.StargateServiceClient - httpClient *http.Client - - workerPool *pond.WorkerPool - metricsRegistry metrics.MetricsRegistry -} - -func NewClient( - l logging.Logger, - stargateClient generated.StargateServiceClient, - clientConfig Config, - workerPoolConfig WorkerPoolConfig, - metricsRegistry metrics.MetricsRegistry, -) *Client { - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.MaxIdleConns = clientConfig.HTTPMaxIdleConns - transport.MaxIdleConnsPerHost = clientConfig.HTTPMaxIdleConnsPerHost - - clientConfig.GatewayUrl = strings.TrimSuffix(clientConfig.GatewayUrl, "/") - - return &Client{ - logger: l, - stargateClient: stargateClient, - config: clientConfig, - workerPool: pond.New(workerPoolConfig.MaxWorkers, workerPoolConfig.MaxTasks), - metricsRegistry: metricsRegistry, - httpClient: &http.Client{ - Timeout: clientConfig.HTTPClientTimeout, - Transport: transport, - }, - } -} - -type ResponseChanEvent struct { - msg *generated.StargateClientMessage - err error -} - -func (c *Client) Run(ctx context.Context) error { - c.logger.Info("starting client...") - - ctx = metadata.AppendToOutgoingContext( - ctx, - "organization-id", c.config.OrganizationID, - "stack-id", c.config.StackID, - ) - - c.logger.WithFields(map[string]any{ - "organization_id": c.config.OrganizationID, - "stack_id": c.config.StackID, - }).Info("connecting to stargate server...") - - stream, err := c.stargateClient.Stargate(ctx) - if err != nil { - return err - } - - c.logger.WithFields(map[string]any{ - "organization_id": c.config.OrganizationID, - "stack_id": c.config.StackID, - }).Info("connected to stargate server") - defer c.logger.WithFields(map[string]any{ - "organization_id": c.config.OrganizationID, - "stack_id": c.config.StackID, - }).Info("disconnected from stargate server") - - responseChan := make(chan *ResponseChanEvent, c.config.ChanSize) - eg, ctx := errgroup.WithContext(ctx) - eg.Go(func() error { - for { - in, err := stream.Recv() - if err != nil { - if err == io.EOF { - return nil - } - - return err - } - - c.logger.WithFields(map[string]any{ - "event": in, - }).Debug("received message from server") - - c.workerPool.Submit(func() { - out := c.Forward(ctx, in) - select { - case <-ctx.Done(): - return - case responseChan <- out: - } - }) - } - }) - - eg.Go(func() error { - for { - select { - case <-ctx.Done(): - return nil - case response := <-responseChan: - if response.err != nil { - // Note: how should we handle errors here? - return response.err - } - - if response.msg == nil { - continue - } - - c.logger.WithFields(map[string]any{ - "response": response, - }).Debug("sending response message to server") - - err := stream.Send(response.msg) - if err != nil { - return err - } - } - } - }) - - return eg.Wait() -} - -func (c *Client) Forward(ctx context.Context, in *generated.StargateServerMessage) *ResponseChanEvent { - attrs := []attribute.KeyValue{} - - switch ev := in.Event.(type) { - case *generated.StargateServerMessage_ApiCall: - - ctx = opentelemetry.Propagator.Extract(ctx, propagation.MapCarrier(ev.ApiCall.OtlpContext)) - - attrs = append(attrs, attribute.String("message_type", "api_call")) - c.metricsRegistry.ServerMessageReceivedByType().Add(ctx, 1, metric.WithAttributes(attrs...)) - - attrs = append(attrs, attribute.String("path", ev.ApiCall.Path)) - path := strings.TrimPrefix(ev.ApiCall.Path, "/") - - req, err := http.NewRequestWithContext(ctx, ev.ApiCall.Method, c.config.GatewayUrl+"/"+path, bytes.NewReader(ev.ApiCall.Body)) - if err != nil { - return &ResponseChanEvent{ - err: err, - } - } - - opentelemetry.Propagator.Inject(ctx, propagation.HeaderCarrier(req.Header)) - - q := req.URL.Query() - for k, v := range ev.ApiCall.Query { - for _, vv := range v.Values { - q.Add(k, vv) - } - } - req.URL.RawQuery = q.Encode() - - for k, v := range ev.ApiCall.Headers { - for _, vv := range v.Values { - req.Header.Add(k, vv) - } - } - - now := time.Now() - resp, err := c.httpClient.Do(req) - if err != nil { - c.logger.Errorf("error making http request: %v", err) - return &ResponseChanEvent{ - err: nil, - msg: &generated.StargateClientMessage{ - CorrelationId: in.CorrelationId, - Event: &generated.StargateClientMessage_ApiCallResponse{ApiCallResponse: &generated.StargateClientMessage_APICallResponse{ - StatusCode: http.StatusInternalServerError, - Body: []byte{}, - Headers: map[string]*generated.Values{}, - }}, - }, - } - } - latency := time.Since(now) - c.metricsRegistry.HTTPCallLatencies().Record(ctx, latency.Milliseconds(), metric.WithAttributes(attrs...)) - - body, err := io.ReadAll(resp.Body) - if err != nil { - return &ResponseChanEvent{ - err: err, - } - } - - headers := make(map[string]*generated.Values) - for k, v := range resp.Header { - headers[k] = &generated.Values{ - Values: v, - } - } - - attrs = append(attrs, attribute.Int("status_code", resp.StatusCode)) - c.metricsRegistry.HTTPCallStatusCodes().Add(ctx, 1, metric.WithAttributes(attrs...)) - - return &ResponseChanEvent{ - err: nil, - msg: &generated.StargateClientMessage{ - CorrelationId: in.CorrelationId, - Event: &generated.StargateClientMessage_ApiCallResponse{ApiCallResponse: &generated.StargateClientMessage_APICallResponse{ - StatusCode: int32(resp.StatusCode), - Body: body, - Headers: headers, - }}, - }, - } - case *generated.StargateServerMessage_Ping_: - return &ResponseChanEvent{ - err: nil, - msg: &generated.StargateClientMessage{ - CorrelationId: in.CorrelationId, - Event: &generated.StargateClientMessage_Pong_{ - Pong: &generated.StargateClientMessage_Pong{}, - }, - }, - } - } - - return &ResponseChanEvent{ - err: nil, - msg: nil, - } -} - -func (c *Client) Close() error { - c.workerPool.StopAndWait() - return nil -} diff --git a/ee/stargate/internal/client/controllers/info_controller.go b/ee/stargate/internal/client/controllers/info_controller.go deleted file mode 100644 index 477a01ba8f..0000000000 --- a/ee/stargate/internal/client/controllers/info_controller.go +++ /dev/null @@ -1,44 +0,0 @@ -package controllers - -import ( - "encoding/json" - "net/http" -) - -type StargateControllerConfig struct { - version string -} - -func NewStargateControllerConfig( - version string, -) StargateControllerConfig { - return StargateControllerConfig{ - version: version, - } -} - -type StargateController struct { - config StargateControllerConfig -} - -func NewStargateController( - config StargateControllerConfig, -) *StargateController { - return &StargateController{ - config: config, - } -} - -type ServiceInfo struct { - Version string `json:"version"` -} - -func (s *StargateController) GetInfo(w http.ResponseWriter, r *http.Request) { - info := ServiceInfo{ - Version: s.config.version, - } - - if err := json.NewEncoder(w).Encode(info); err != nil { - panic(err) - } -} diff --git a/ee/stargate/internal/client/interceptors/auth_interceptor.go b/ee/stargate/internal/client/interceptors/auth_interceptor.go deleted file mode 100644 index cc797771e6..0000000000 --- a/ee/stargate/internal/client/interceptors/auth_interceptor.go +++ /dev/null @@ -1,135 +0,0 @@ -package interceptors - -import ( - "context" - "net/http" - "time" - - "github.com/pkg/errors" - "github.com/zitadel/oidc/pkg/client" - "golang.org/x/oauth2/clientcredentials" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -const ( - defaultWaitingTime = 10 * time.Second -) - -type Config struct { - refreshTokenDurationBeforeExpireTime time.Duration - - clientID string - clientSecret string - endpoint string -} - -func NewConfig( - endpoint string, - refreshTokenDurationBeforeExpireTime time.Duration, - clientID string, - clientSecret string, -) Config { - return Config{ - refreshTokenDurationBeforeExpireTime: refreshTokenDurationBeforeExpireTime, - clientID: clientID, - clientSecret: clientSecret, - endpoint: endpoint, - } -} - -type AuthInterceptor struct { - config Config - httpClient *http.Client - - accessToken string - closeChan chan struct{} -} - -func NewAuthInterceptor(config Config) (*AuthInterceptor, error) { - i := &AuthInterceptor{ - config: config, - httpClient: &http.Client{}, - closeChan: make(chan struct{}), - } - - return i, nil -} - -func (a *AuthInterceptor) Close() { - close(a.closeChan) -} - -func (a *AuthInterceptor) StreamClientInterceptor() grpc.StreamClientInterceptor { - return func( - ctx context.Context, - desc *grpc.StreamDesc, - cc *grpc.ClientConn, - method string, - streamer grpc.Streamer, - opts ...grpc.CallOption, - ) (grpc.ClientStream, error) { - return streamer( - metadata.AppendToOutgoingContext(ctx, "authorization", a.accessToken), - desc, - cc, - method, - opts..., - ) - } -} - -func (a *AuthInterceptor) ScheduleRefreshToken() error { - expire, err := a.refreshToken() - if err != nil { - return err - } - - go func() { - waitingTime := time.Until(expire.Add(-a.config.refreshTokenDurationBeforeExpireTime)) - if waitingTime < 0 { - waitingTime = defaultWaitingTime - } - for { - select { - case <-a.closeChan: - return - case <-time.After(waitingTime): - expire, err := a.refreshToken() - if err != nil { - // TODO(polo): add metrics + log - waitingTime = time.Second - } else { - waitingTime = time.Until(expire.Add(-a.config.refreshTokenDurationBeforeExpireTime)) - if waitingTime < 0 { - waitingTime = defaultWaitingTime - } - } - } - } - }() - - return nil -} - -func (a *AuthInterceptor) refreshToken() (time.Time, error) { - discoveryConfiguration, err := client.Discover(a.config.endpoint, a.httpClient) - if err != nil { - return time.Time{}, err - } - - config := clientcredentials.Config{ - ClientID: a.config.clientID, - ClientSecret: a.config.clientSecret, - TokenURL: discoveryConfiguration.TokenEndpoint, - } - - token, err := config.Token(context.Background()) - if err != nil { - return time.Time{}, errors.Wrapf(err, "cannot fetch token") - } - - a.accessToken = token.AccessToken - - return token.Expiry, nil -} diff --git a/ee/stargate/internal/client/module.go b/ee/stargate/internal/client/module.go deleted file mode 100644 index ccb53b0bcb..0000000000 --- a/ee/stargate/internal/client/module.go +++ /dev/null @@ -1,138 +0,0 @@ -package client - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/health" - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/stack/ee/stargate/internal/client/controllers" - "github.com/formancehq/stack/ee/stargate/internal/client/interceptors" - "github.com/formancehq/stack/ee/stargate/internal/client/routes" - "github.com/formancehq/stack/ee/stargate/internal/generated" - metrics "github.com/formancehq/stack/ee/stargate/internal/grpcmetrics" - "github.com/formancehq/stack/ee/stargate/internal/middlewares" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - "go.uber.org/fx" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" -) - -func Module( - bind string, - serverURL string, - tlsEnabled bool, - tlsCACertificate string, - tlsInsecureSkipVerify bool, - debug bool, -) fx.Option { - options := make([]fx.Option, 0) - - options = append(options, - fx.Provide(routes.NewRouter), - fx.Provide(controllers.NewStargateController), - health.Module(), - fx.Invoke(func(lc fx.Lifecycle, h chi.Router, l logging.Logger) { - if debug { - wrappedRouter := chi.NewRouter() - wrappedRouter.Use(middlewares.Log()) - wrappedRouter.Mount("/", h) - h = wrappedRouter - } - - l.Infof("HTTP server listening on %s", bind) - lc.Append(httpserver.NewHook(h, httpserver.WithAddress(bind))) - }), - - fx.Provide(interceptors.NewAuthInterceptor), - fx.Provide(func(l logging.Logger, authInterceptor *interceptors.AuthInterceptor) (generated.StargateServiceClient, error) { - return newGrpcClient(l, serverURL, tlsEnabled, tlsCACertificate, tlsInsecureSkipVerify, authInterceptor) - }), - fx.Provide(fx.Annotate(noop.NewMeterProvider, fx.As(new(metric.MeterProvider)))), - fx.Provide(metrics.RegisterMetricsRegistry), - fx.Provide(NewClient), - fx.Invoke(func(lc fx.Lifecycle, client *Client, authInterceptor *interceptors.AuthInterceptor) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - if err := authInterceptor.ScheduleRefreshToken(); err != nil { - return err - } - - go func() { - err := client.Run(context.Background()) - if err != nil && err != context.Canceled { - panic(err) - } - }() - - return nil - }, - OnStop: func(ctx context.Context) error { - authInterceptor.Close() - - return client.Close() - }, - }) - }), - ) - - return fx.Options(options...) -} - -func newGrpcClient( - logger logging.Logger, - serverURL string, - tlsEnabled bool, - tlsCACertificate string, - tlsInsecureSkipVerify bool, - authInterceptors *interceptors.AuthInterceptor, -) (generated.StargateServiceClient, error) { - var credential credentials.TransportCredentials - if !tlsEnabled { - logger.Infof("TLS not enabled") - credential = insecure.NewCredentials() - } else { - var certPool *x509.CertPool - if tlsCACertificate != "" { - certPool := x509.NewCertPool() - logger.Infof("Load server certificate from config") - if !certPool.AppendCertsFromPEM([]byte(tlsCACertificate)) { - return nil, fmt.Errorf("failed to add server CA's certificate") - } - } else { - var err error - certPool, err = x509.SystemCertPool() - if err != nil { - return nil, err - } - } - - if tlsInsecureSkipVerify { - logger.Infof("Disable certificate checks") - } - - credential = credentials.NewTLS(&tls.Config{ - InsecureSkipVerify: tlsInsecureSkipVerify, - RootCAs: certPool, - }) - } - - conn, err := grpc.Dial( - serverURL, - grpc.WithStreamInterceptor(authInterceptors.StreamClientInterceptor()), - grpc.WithTransportCredentials(credential), - ) - if err != nil { - logger.Errorf("failed to connect to stargate server '%s': %s", serverURL, err) - return nil, err - } - - return generated.NewStargateServiceClient(conn), nil -} diff --git a/ee/stargate/internal/client/routes/routes.go b/ee/stargate/internal/client/routes/routes.go deleted file mode 100644 index 283ccf057b..0000000000 --- a/ee/stargate/internal/client/routes/routes.go +++ /dev/null @@ -1,36 +0,0 @@ -package routes - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/health" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/stack/ee/stargate/internal/client/controllers" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" -) - -func NewRouter( - logger logging.Logger, - healthController *health.HealthController, - stargateController *controllers.StargateController, -) chi.Router { - router := chi.NewMux() - - router.Use( - cors.New(cors.Options{ - AllowOriginFunc: func(r *http.Request, origin string) bool { - return true - }, - AllowCredentials: true, - }).Handler, - middleware.Recoverer, - ) - - router.Get("/_healthcheck", healthController.Check) - router.Get("/_info", stargateController.GetInfo) - - return router -} diff --git a/ee/stargate/internal/generated/stargate.pb.go b/ee/stargate/internal/generated/stargate.pb.go deleted file mode 100644 index b379fc024c..0000000000 --- a/ee/stargate/internal/generated/stargate.pb.go +++ /dev/null @@ -1,747 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v4.24.4 -// source: stargate.proto - -package generated - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Values struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` -} - -func (x *Values) Reset() { - *x = Values{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Values) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Values) ProtoMessage() {} - -func (x *Values) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Values.ProtoReflect.Descriptor instead. -func (*Values) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{0} -} - -func (x *Values) GetValues() []string { - if x != nil { - return x.Values - } - return nil -} - -type StargateServerMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CorrelationId string `protobuf:"bytes,1,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` - // Types that are assignable to Event: - // - // *StargateServerMessage_ApiCall - // *StargateServerMessage_Ping_ - Event isStargateServerMessage_Event `protobuf_oneof:"event"` -} - -func (x *StargateServerMessage) Reset() { - *x = StargateServerMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateServerMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateServerMessage) ProtoMessage() {} - -func (x *StargateServerMessage) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateServerMessage.ProtoReflect.Descriptor instead. -func (*StargateServerMessage) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{1} -} - -func (x *StargateServerMessage) GetCorrelationId() string { - if x != nil { - return x.CorrelationId - } - return "" -} - -func (m *StargateServerMessage) GetEvent() isStargateServerMessage_Event { - if m != nil { - return m.Event - } - return nil -} - -func (x *StargateServerMessage) GetApiCall() *StargateServerMessage_APICall { - if x, ok := x.GetEvent().(*StargateServerMessage_ApiCall); ok { - return x.ApiCall - } - return nil -} - -func (x *StargateServerMessage) GetPing() *StargateServerMessage_Ping { - if x, ok := x.GetEvent().(*StargateServerMessage_Ping_); ok { - return x.Ping - } - return nil -} - -type isStargateServerMessage_Event interface { - isStargateServerMessage_Event() -} - -type StargateServerMessage_ApiCall struct { - ApiCall *StargateServerMessage_APICall `protobuf:"bytes,101,opt,name=api_call,json=apiCall,proto3,oneof"` -} - -type StargateServerMessage_Ping_ struct { - Ping *StargateServerMessage_Ping `protobuf:"bytes,102,opt,name=ping,proto3,oneof"` -} - -func (*StargateServerMessage_ApiCall) isStargateServerMessage_Event() {} - -func (*StargateServerMessage_Ping_) isStargateServerMessage_Event() {} - -type StargateClientMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CorrelationId string `protobuf:"bytes,1,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` - // Types that are assignable to Event: - // - // *StargateClientMessage_ApiCallResponse - // *StargateClientMessage_Pong_ - Event isStargateClientMessage_Event `protobuf_oneof:"event"` -} - -func (x *StargateClientMessage) Reset() { - *x = StargateClientMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateClientMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateClientMessage) ProtoMessage() {} - -func (x *StargateClientMessage) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateClientMessage.ProtoReflect.Descriptor instead. -func (*StargateClientMessage) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{2} -} - -func (x *StargateClientMessage) GetCorrelationId() string { - if x != nil { - return x.CorrelationId - } - return "" -} - -func (m *StargateClientMessage) GetEvent() isStargateClientMessage_Event { - if m != nil { - return m.Event - } - return nil -} - -func (x *StargateClientMessage) GetApiCallResponse() *StargateClientMessage_APICallResponse { - if x, ok := x.GetEvent().(*StargateClientMessage_ApiCallResponse); ok { - return x.ApiCallResponse - } - return nil -} - -func (x *StargateClientMessage) GetPong() *StargateClientMessage_Pong { - if x, ok := x.GetEvent().(*StargateClientMessage_Pong_); ok { - return x.Pong - } - return nil -} - -type isStargateClientMessage_Event interface { - isStargateClientMessage_Event() -} - -type StargateClientMessage_ApiCallResponse struct { - ApiCallResponse *StargateClientMessage_APICallResponse `protobuf:"bytes,101,opt,name=api_call_response,json=apiCallResponse,proto3,oneof"` -} - -type StargateClientMessage_Pong_ struct { - Pong *StargateClientMessage_Pong `protobuf:"bytes,102,opt,name=pong,proto3,oneof"` -} - -func (*StargateClientMessage_ApiCallResponse) isStargateClientMessage_Event() {} - -func (*StargateClientMessage_Pong_) isStargateClientMessage_Event() {} - -type StargateServerMessage_APICall struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` - Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` - Query map[string]*Values `protobuf:"bytes,3,rep,name=query,proto3" json:"query,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` - Headers map[string]*Values `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - OtlpContext map[string]string `protobuf:"bytes,6,rep,name=otlpContext,proto3" json:"otlpContext,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` -} - -func (x *StargateServerMessage_APICall) Reset() { - *x = StargateServerMessage_APICall{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateServerMessage_APICall) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateServerMessage_APICall) ProtoMessage() {} - -func (x *StargateServerMessage_APICall) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateServerMessage_APICall.ProtoReflect.Descriptor instead. -func (*StargateServerMessage_APICall) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{1, 0} -} - -func (x *StargateServerMessage_APICall) GetMethod() string { - if x != nil { - return x.Method - } - return "" -} - -func (x *StargateServerMessage_APICall) GetPath() string { - if x != nil { - return x.Path - } - return "" -} - -func (x *StargateServerMessage_APICall) GetQuery() map[string]*Values { - if x != nil { - return x.Query - } - return nil -} - -func (x *StargateServerMessage_APICall) GetBody() []byte { - if x != nil { - return x.Body - } - return nil -} - -func (x *StargateServerMessage_APICall) GetHeaders() map[string]*Values { - if x != nil { - return x.Headers - } - return nil -} - -func (x *StargateServerMessage_APICall) GetOtlpContext() map[string]string { - if x != nil { - return x.OtlpContext - } - return nil -} - -type StargateServerMessage_Ping struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *StargateServerMessage_Ping) Reset() { - *x = StargateServerMessage_Ping{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateServerMessage_Ping) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateServerMessage_Ping) ProtoMessage() {} - -func (x *StargateServerMessage_Ping) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateServerMessage_Ping.ProtoReflect.Descriptor instead. -func (*StargateServerMessage_Ping) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{1, 1} -} - -type StargateClientMessage_APICallResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` - Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` - Headers map[string]*Values `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` -} - -func (x *StargateClientMessage_APICallResponse) Reset() { - *x = StargateClientMessage_APICallResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateClientMessage_APICallResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateClientMessage_APICallResponse) ProtoMessage() {} - -func (x *StargateClientMessage_APICallResponse) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateClientMessage_APICallResponse.ProtoReflect.Descriptor instead. -func (*StargateClientMessage_APICallResponse) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{2, 0} -} - -func (x *StargateClientMessage_APICallResponse) GetStatusCode() int32 { - if x != nil { - return x.StatusCode - } - return 0 -} - -func (x *StargateClientMessage_APICallResponse) GetBody() []byte { - if x != nil { - return x.Body - } - return nil -} - -func (x *StargateClientMessage_APICallResponse) GetHeaders() map[string]*Values { - if x != nil { - return x.Headers - } - return nil -} - -type StargateClientMessage_Pong struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *StargateClientMessage_Pong) Reset() { - *x = StargateClientMessage_Pong{} - if protoimpl.UnsafeEnabled { - mi := &file_stargate_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StargateClientMessage_Pong) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StargateClientMessage_Pong) ProtoMessage() {} - -func (x *StargateClientMessage_Pong) ProtoReflect() protoreflect.Message { - mi := &file_stargate_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StargateClientMessage_Pong.ProtoReflect.Descriptor instead. -func (*StargateClientMessage_Pong) Descriptor() ([]byte, []int) { - return file_stargate_proto_rawDescGZIP(), []int{2, 1} -} - -var File_stargate_proto protoreflect.FileDescriptor - -var file_stargate_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x15, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x22, 0x20, 0x0a, 0x06, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0xc8, 0x06, 0x0a, 0x15, 0x53, 0x74, - 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, - 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x51, 0x0a, 0x08, 0x61, 0x70, - 0x69, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x66, - 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x61, - 0x6c, 0x6c, 0x48, 0x00, 0x52, 0x07, 0x61, 0x70, 0x69, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x47, 0x0a, - 0x04, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x48, 0x00, - 0x52, 0x04, 0x70, 0x69, 0x6e, 0x67, 0x1a, 0xda, 0x04, 0x0a, 0x07, 0x41, 0x50, 0x49, 0x43, 0x61, - 0x6c, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x55, - 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3f, 0x2e, - 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, - 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x43, - 0x61, 0x6c, 0x6c, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x5b, 0x0a, 0x07, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x66, 0x6f, 0x72, - 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x61, 0x6c, 0x6c, - 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x67, 0x0a, 0x0b, 0x6f, 0x74, 0x6c, 0x70, 0x43, 0x6f, - 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x61, 0x6c, - 0x6c, 0x2e, 0x4f, 0x74, 0x6c, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x0b, 0x6f, 0x74, 0x6c, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x1a, - 0x57, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x33, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x66, 0x6f, 0x72, 0x6d, - 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x3e, 0x0a, 0x10, 0x4f, 0x74, 0x6c, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x06, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x22, 0x8d, 0x04, 0x0a, 0x15, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, - 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, - 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x6a, 0x0a, 0x11, 0x61, 0x70, 0x69, 0x5f, 0x63, 0x61, 0x6c, - 0x6c, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x3c, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, - 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x41, - 0x50, 0x49, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x00, - 0x52, 0x0f, 0x61, 0x70, 0x69, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x47, 0x0a, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x31, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x6f, - 0x6e, 0x67, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6f, 0x6e, 0x67, 0x1a, 0x86, 0x02, 0x0a, 0x0f, 0x41, - 0x50, 0x49, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, - 0x6f, 0x64, 0x79, 0x12, 0x63, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, - 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x2e, 0x41, 0x50, 0x49, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x59, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x66, 0x6f, 0x72, 0x6d, - 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x1a, 0x06, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x42, 0x07, 0x0a, 0x05, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x32, 0x7d, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x72, 0x67, - 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, - 0x74, 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, - 0x67, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x1a, 0x2c, 0x2e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x73, 0x74, 0x61, - 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x67, 0x61, - 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, - 0x01, 0x30, 0x01, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x68, 0x71, 0x2f, 0x73, 0x74, 0x61, - 0x63, 0x6b, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x73, 0x74, - 0x61, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var ( - file_stargate_proto_rawDescOnce sync.Once - file_stargate_proto_rawDescData = file_stargate_proto_rawDesc -) - -func file_stargate_proto_rawDescGZIP() []byte { - file_stargate_proto_rawDescOnce.Do(func() { - file_stargate_proto_rawDescData = protoimpl.X.CompressGZIP(file_stargate_proto_rawDescData) - }) - return file_stargate_proto_rawDescData -} - -var file_stargate_proto_msgTypes = make([]protoimpl.MessageInfo, 11) -var file_stargate_proto_goTypes = []interface{}{ - (*Values)(nil), // 0: formance.stargate.api.Values - (*StargateServerMessage)(nil), // 1: formance.stargate.api.StargateServerMessage - (*StargateClientMessage)(nil), // 2: formance.stargate.api.StargateClientMessage - (*StargateServerMessage_APICall)(nil), // 3: formance.stargate.api.StargateServerMessage.APICall - (*StargateServerMessage_Ping)(nil), // 4: formance.stargate.api.StargateServerMessage.Ping - nil, // 5: formance.stargate.api.StargateServerMessage.APICall.QueryEntry - nil, // 6: formance.stargate.api.StargateServerMessage.APICall.HeadersEntry - nil, // 7: formance.stargate.api.StargateServerMessage.APICall.OtlpContextEntry - (*StargateClientMessage_APICallResponse)(nil), // 8: formance.stargate.api.StargateClientMessage.APICallResponse - (*StargateClientMessage_Pong)(nil), // 9: formance.stargate.api.StargateClientMessage.Pong - nil, // 10: formance.stargate.api.StargateClientMessage.APICallResponse.HeadersEntry -} -var file_stargate_proto_depIdxs = []int32{ - 3, // 0: formance.stargate.api.StargateServerMessage.api_call:type_name -> formance.stargate.api.StargateServerMessage.APICall - 4, // 1: formance.stargate.api.StargateServerMessage.ping:type_name -> formance.stargate.api.StargateServerMessage.Ping - 8, // 2: formance.stargate.api.StargateClientMessage.api_call_response:type_name -> formance.stargate.api.StargateClientMessage.APICallResponse - 9, // 3: formance.stargate.api.StargateClientMessage.pong:type_name -> formance.stargate.api.StargateClientMessage.Pong - 5, // 4: formance.stargate.api.StargateServerMessage.APICall.query:type_name -> formance.stargate.api.StargateServerMessage.APICall.QueryEntry - 6, // 5: formance.stargate.api.StargateServerMessage.APICall.headers:type_name -> formance.stargate.api.StargateServerMessage.APICall.HeadersEntry - 7, // 6: formance.stargate.api.StargateServerMessage.APICall.otlpContext:type_name -> formance.stargate.api.StargateServerMessage.APICall.OtlpContextEntry - 0, // 7: formance.stargate.api.StargateServerMessage.APICall.QueryEntry.value:type_name -> formance.stargate.api.Values - 0, // 8: formance.stargate.api.StargateServerMessage.APICall.HeadersEntry.value:type_name -> formance.stargate.api.Values - 10, // 9: formance.stargate.api.StargateClientMessage.APICallResponse.headers:type_name -> formance.stargate.api.StargateClientMessage.APICallResponse.HeadersEntry - 0, // 10: formance.stargate.api.StargateClientMessage.APICallResponse.HeadersEntry.value:type_name -> formance.stargate.api.Values - 2, // 11: formance.stargate.api.StargateService.Stargate:input_type -> formance.stargate.api.StargateClientMessage - 1, // 12: formance.stargate.api.StargateService.Stargate:output_type -> formance.stargate.api.StargateServerMessage - 12, // [12:13] is the sub-list for method output_type - 11, // [11:12] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name -} - -func init() { file_stargate_proto_init() } -func file_stargate_proto_init() { - if File_stargate_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_stargate_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Values); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateServerMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateClientMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateServerMessage_APICall); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateServerMessage_Ping); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateClientMessage_APICallResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_stargate_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StargateClientMessage_Pong); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_stargate_proto_msgTypes[1].OneofWrappers = []interface{}{ - (*StargateServerMessage_ApiCall)(nil), - (*StargateServerMessage_Ping_)(nil), - } - file_stargate_proto_msgTypes[2].OneofWrappers = []interface{}{ - (*StargateClientMessage_ApiCallResponse)(nil), - (*StargateClientMessage_Pong_)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_stargate_proto_rawDesc, - NumEnums: 0, - NumMessages: 11, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_stargate_proto_goTypes, - DependencyIndexes: file_stargate_proto_depIdxs, - MessageInfos: file_stargate_proto_msgTypes, - }.Build() - File_stargate_proto = out.File - file_stargate_proto_rawDesc = nil - file_stargate_proto_goTypes = nil - file_stargate_proto_depIdxs = nil -} diff --git a/ee/stargate/internal/generated/stargate_grpc.pb.go b/ee/stargate/internal/generated/stargate_grpc.pb.go deleted file mode 100644 index 19ef391dd2..0000000000 --- a/ee/stargate/internal/generated/stargate_grpc.pb.go +++ /dev/null @@ -1,137 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v4.24.4 -// source: stargate.proto - -package generated - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// StargateServiceClient is the client API for StargateService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type StargateServiceClient interface { - Stargate(ctx context.Context, opts ...grpc.CallOption) (StargateService_StargateClient, error) -} - -type stargateServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewStargateServiceClient(cc grpc.ClientConnInterface) StargateServiceClient { - return &stargateServiceClient{cc} -} - -func (c *stargateServiceClient) Stargate(ctx context.Context, opts ...grpc.CallOption) (StargateService_StargateClient, error) { - stream, err := c.cc.NewStream(ctx, &StargateService_ServiceDesc.Streams[0], "/formance.stargate.api.StargateService/Stargate", opts...) - if err != nil { - return nil, err - } - x := &stargateServiceStargateClient{stream} - return x, nil -} - -type StargateService_StargateClient interface { - Send(*StargateClientMessage) error - Recv() (*StargateServerMessage, error) - grpc.ClientStream -} - -type stargateServiceStargateClient struct { - grpc.ClientStream -} - -func (x *stargateServiceStargateClient) Send(m *StargateClientMessage) error { - return x.ClientStream.SendMsg(m) -} - -func (x *stargateServiceStargateClient) Recv() (*StargateServerMessage, error) { - m := new(StargateServerMessage) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// StargateServiceServer is the server API for StargateService service. -// All implementations must embed UnimplementedStargateServiceServer -// for forward compatibility -type StargateServiceServer interface { - Stargate(StargateService_StargateServer) error - mustEmbedUnimplementedStargateServiceServer() -} - -// UnimplementedStargateServiceServer must be embedded to have forward compatible implementations. -type UnimplementedStargateServiceServer struct { -} - -func (UnimplementedStargateServiceServer) Stargate(StargateService_StargateServer) error { - return status.Errorf(codes.Unimplemented, "method Stargate not implemented") -} -func (UnimplementedStargateServiceServer) mustEmbedUnimplementedStargateServiceServer() {} - -// UnsafeStargateServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to StargateServiceServer will -// result in compilation errors. -type UnsafeStargateServiceServer interface { - mustEmbedUnimplementedStargateServiceServer() -} - -func RegisterStargateServiceServer(s grpc.ServiceRegistrar, srv StargateServiceServer) { - s.RegisterService(&StargateService_ServiceDesc, srv) -} - -func _StargateService_Stargate_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(StargateServiceServer).Stargate(&stargateServiceStargateServer{stream}) -} - -type StargateService_StargateServer interface { - Send(*StargateServerMessage) error - Recv() (*StargateClientMessage, error) - grpc.ServerStream -} - -type stargateServiceStargateServer struct { - grpc.ServerStream -} - -func (x *stargateServiceStargateServer) Send(m *StargateServerMessage) error { - return x.ServerStream.SendMsg(m) -} - -func (x *stargateServiceStargateServer) Recv() (*StargateClientMessage, error) { - m := new(StargateClientMessage) - if err := x.ServerStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// StargateService_ServiceDesc is the grpc.ServiceDesc for StargateService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var StargateService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "formance.stargate.api.StargateService", - HandlerType: (*StargateServiceServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{ - { - StreamName: "Stargate", - Handler: _StargateService_Stargate_Handler, - ServerStreams: true, - ClientStreams: true, - }, - }, - Metadata: "stargate.proto", -} diff --git a/ee/stargate/internal/grpcmetrics/metrics.go b/ee/stargate/internal/grpcmetrics/metrics.go deleted file mode 100644 index 721449f5dd..0000000000 --- a/ee/stargate/internal/grpcmetrics/metrics.go +++ /dev/null @@ -1,88 +0,0 @@ -package grpcmetrics - -import ( - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/metric" -) - -type MetricsRegistry interface { - HTTPCallLatencies() metric.Int64Histogram - HTTPCallStatusCodes() metric.Int64Counter - ServerMessageReceivedByType() metric.Int64Counter -} - -type metricsRegistry struct { - httpCallLatencies metric.Int64Histogram - httpCallStatusCodes metric.Int64Counter - serverMessageReceivedByType metric.Int64Counter -} - -func RegisterMetricsRegistry(meterProvider metric.MeterProvider) (MetricsRegistry, error) { - meter := meterProvider.Meter("client") - - httpCallLatencies, err := meter.Int64Histogram( - "http_call_latencies", - metric.WithUnit("ms"), - metric.WithDescription("Latency of HTTP calls"), - ) - if err != nil { - return nil, err - } - - httpCallStatusCodes, err := meter.Int64Counter( - "http_call_status_codes", - metric.WithUnit("1"), - metric.WithDescription("HTTP status codes of HTTP calls"), - ) - if err != nil { - return nil, err - } - - serverMessageReceivedByType, err := meter.Int64Counter( - "server_message_received_by_type", - metric.WithUnit("1"), - metric.WithDescription("Server message received by type"), - ) - if err != nil { - return nil, err - } - - return &metricsRegistry{ - httpCallLatencies: httpCallLatencies, - httpCallStatusCodes: httpCallStatusCodes, - serverMessageReceivedByType: serverMessageReceivedByType, - }, nil -} - -func (m *metricsRegistry) HTTPCallLatencies() metric.Int64Histogram { - return m.httpCallLatencies -} - -func (m *metricsRegistry) HTTPCallStatusCodes() metric.Int64Counter { - return m.httpCallStatusCodes -} - -func (m *metricsRegistry) ServerMessageReceivedByType() metric.Int64Counter { - return m.serverMessageReceivedByType -} - -type NoOpMetricsRegistry struct{} - -func NewNoOpMetricsRegistry() *NoOpMetricsRegistry { - return &NoOpMetricsRegistry{} -} - -func (m *NoOpMetricsRegistry) HTTPCallLatencies() metric.Int64Histogram { - histogram, _ := otel.GetMeterProvider().Meter("client").Int64Histogram("http_call_latencies") - return histogram -} - -func (m *NoOpMetricsRegistry) HTTPCallStatusCodes() metric.Int64Counter { - counter, _ := otel.GetMeterProvider().Meter("client").Int64Counter("http_call_status_codes") - return counter -} - -func (m *NoOpMetricsRegistry) ServerMessageReceivedByType() metric.Int64Counter { - counter, _ := otel.GetMeterProvider().Meter("client").Int64Counter("server_message_received_by_type") - return counter -} diff --git a/ee/stargate/internal/middlewares/log_middleware.go b/ee/stargate/internal/middlewares/log_middleware.go deleted file mode 100644 index 87685d24d3..0000000000 --- a/ee/stargate/internal/middlewares/log_middleware.go +++ /dev/null @@ -1,24 +0,0 @@ -package middlewares - -import ( - "net/http" - "time" - - "github.com/formancehq/go-libs/logging" -) - -func Log() func(h http.Handler) http.Handler { - return func(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - h.ServeHTTP(w, r) - latency := time.Since(start) - logging.FromContext(r.Context()).WithFields(map[string]interface{}{ - "method": r.Method, - "path": r.URL.Path, - "latency": latency, - "user_agent": r.UserAgent(), - }).Info("Request") - }) - } -} diff --git a/ee/stargate/internal/opentelemetry/context.go b/ee/stargate/internal/opentelemetry/context.go deleted file mode 100644 index 873b8df57b..0000000000 --- a/ee/stargate/internal/opentelemetry/context.go +++ /dev/null @@ -1,9 +0,0 @@ -package opentelemetry - -import ( - "go.opentelemetry.io/otel/propagation" -) - -var ( - Propagator = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) -) diff --git a/ee/stargate/internal/utils/utils.go b/ee/stargate/internal/utils/utils.go deleted file mode 100644 index b7f816a822..0000000000 --- a/ee/stargate/internal/utils/utils.go +++ /dev/null @@ -1,5 +0,0 @@ -package utils - -func GetNatsSubject(organizationID, stackID string) string { - return organizationID + "." + stackID -} diff --git a/ee/stargate/main.go b/ee/stargate/main.go deleted file mode 100644 index 34ba0c36b1..0000000000 --- a/ee/stargate/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/formancehq/stack/ee/stargate/cmd" - -func main() { - cmd.Execute() -} diff --git a/ee/stargate/scratch.Dockerfile b/ee/stargate/scratch.Dockerfile deleted file mode 100644 index a180143ef9..0000000000 --- a/ee/stargate/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY stargate /usr/bin/stargate -ENV OTEL_SERVICE_NAME stargate -ENTRYPOINT ["/usr/bin/stargate"] -CMD ["client"] diff --git a/ee/stargate/stargate.proto b/ee/stargate/stargate.proto deleted file mode 100644 index ff562d08c8..0000000000 --- a/ee/stargate/stargate.proto +++ /dev/null @@ -1,49 +0,0 @@ -syntax = "proto3"; - -package formance.stargate.api; - -option go_package = "github.com/formancehq/stack/components/stargate/internal/generated"; - -message Values { repeated string values = 1; } - -message StargateServerMessage { - message APICall { - string method = 1; - string path = 2; - map query = 3; - bytes body = 4; - map headers = 5; - map otlpContext = 6; - } - - message Ping {} - - string correlation_id = 1; - - oneof event { - APICall api_call = 101; - Ping ping = 102; - } -} - -message StargateClientMessage { - message APICallResponse { - int32 status_code = 1; - bytes body = 2; - map headers = 3; - } - - message Pong {} - - string correlation_id = 1; - - oneof event { - APICallResponse api_call_response = 101; - Pong pong = 102; - } -} - -service StargateService { - rpc Stargate(stream StargateClientMessage) - returns (stream StargateServerMessage); -} diff --git a/ee/wallets/Earthfile b/ee/wallets/Earthfile index fcc3516de0..402bb653ac 100644 --- a/ee/wallets/Earthfile +++ b/ee/wallets/Earthfile @@ -9,7 +9,6 @@ FROM core+base-image sources: WORKDIR src - COPY (stack+sources/out --LOCATION=components/ledger) components/ledger COPY --pass-args (releases+sdk-generate/go) /src/releases/sdks/go WORKDIR /src/ee/wallets COPY go.* . diff --git a/ee/wallets/go.mod b/ee/wallets/go.mod index 4736ca3c27..e8f9661e59 100644 --- a/ee/wallets/go.mod +++ b/ee/wallets/go.mod @@ -91,4 +91,4 @@ require ( replace github.com/formancehq/formance-sdk-go/v2 => ../../releases/sdks/go -replace github.com/formancehq/ledger => ../../components/ledger +replace github.com/formancehq/ledger => github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02 diff --git a/ee/wallets/go.sum b/ee/wallets/go.sum index 46c5e53f6f..5c4891b7fe 100644 --- a/ee/wallets/go.sum +++ b/ee/wallets/go.sum @@ -67,6 +67,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= +github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02 h1:0jB2JrN653A25DxUh4THjujWFZseMQqg5VMwGQxv7gc= +github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02/go.mod h1:sGscj1S3S2ndAzOPVFQFBuC72YF8t+OAm/hcqDcxpxI= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= diff --git a/ee/webhooks/.gitignore b/ee/webhooks/.gitignore deleted file mode 100644 index a861d3e295..0000000000 --- a/ee/webhooks/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.DS_Store -.env -.idea -webhooks -coverage.out -coverage.html -vendor diff --git a/ee/webhooks/.goreleaser.yml b/ee/webhooks/.goreleaser.yml deleted file mode 100644 index 263b698024..0000000000 --- a/ee/webhooks/.goreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -project_name: webhooks -includes: - - from_file: - path: ./../../.goreleaser.default.yaml -monorepo: - tag_prefix: v - dir: ./ - -builds: - - binary: webhooks - id: webhooks - ldflags: - - -X github.com/formancehq/webhooks/cmd.BuildDate={{ .Date }} - - -X github.com/formancehq/webhooks/cmd.Version=v{{ .Version }} - - -X github.com/formancehq/webhooks/cmd.Commit={{ .ShortCommit }} - - -extldflags "-static" - env: - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - - arm64 - -archives: - - id: "{{.ProjectName}}" - builds: - - webhooks - format: tar.gz - name_template: "{{.ProjectName}}_{{.Os}}-{{.Arch}}" - -release: - prerelease: auto - footer: | - ## What to do next? - - Read the [documentation](https://docs.formance.com/) - - Join our [Slack server](https://formance.com/slack) \ No newline at end of file diff --git a/ee/webhooks/Earthfile b/ee/webhooks/Earthfile deleted file mode 100644 index deaf08f2cd..0000000000 --- a/ee/webhooks/Earthfile +++ /dev/null @@ -1,78 +0,0 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core -IMPORT ../.. AS stack -IMPORT .. AS ee - -FROM core+base-image - -sources: - WORKDIR src - WORKDIR /src/ee/webhooks - COPY go.* . - COPY --dir pkg cmd . - COPY main.go . - SAVE ARTIFACT /src - -compile: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/webhooks - ARG VERSION=latest - DO --pass-args core+GO_COMPILE --VERSION=$VERSION - -build-image: - FROM core+final-image - ENTRYPOINT ["/bin/webhooks"] - CMD ["serve"] - COPY (+compile/main) /bin/webhooks - ARG REPOSITORY=ghcr.io - ARG tag=latest - DO core+SAVE_IMAGE --COMPONENT=webhooks --REPOSITORY=${REPOSITORY} --TAG=$tag - -tests: - FROM core+builder-image - COPY (+sources/*) /src - WORKDIR /src/ee/webhooks - WITH DOCKER --pull=postgres:15-alpine - DO --pass-args core+GO_TESTS - END - -deploy: - COPY (+sources/*) /src - LET tag=$(tar cf - /src | sha1sum | awk '{print $1}') - WAIT - BUILD --pass-args +build-image --tag=$tag - END - FROM --pass-args core+vcluster-deployer-image - RUN kubectl patch Versions.formance.com default -p "{\"spec\":{\"webhooks\": \"${tag}\"}}" --type=merge - -deploy-staging: - BUILD --pass-args stack+deployer-module --MODULE=webhooks - -lint: - FROM core+builder-image - COPY (+sources/*) /src - COPY --pass-args +tidy/go.* . - WORKDIR /src/ee/webhooks - DO --pass-args stack+GO_LINT - SAVE ARTIFACT cmd AS LOCAL cmd - SAVE ARTIFACT pkg AS LOCAL pkg - SAVE ARTIFACT main.go AS LOCAL main.go - -pre-commit: - BUILD --pass-args +tidy - BUILD --pass-args +lint - -openapi: - COPY ./openapi.yaml . - SAVE ARTIFACT ./openapi.yaml - -tidy: - FROM core+builder-image - COPY --pass-args (+sources/src) /src - WORKDIR /src/ee/webhooks - DO --pass-args stack+GO_TIDY - -release: - BUILD --pass-args stack+goreleaser --path=ee/webhooks \ No newline at end of file diff --git a/ee/webhooks/README.md b/ee/webhooks/README.md deleted file mode 100644 index 24b6152b2f..0000000000 --- a/ee/webhooks/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# Webhooks - -Webhooks is a service used to manage user configs and send webhooks to endpoints. -A user config is made of the following information: -- Endpoint: a single URL where messages are sent to. -- EventTypes: an array of string identifiers denoting the type of message being sent and are the primary way for webhook consumers to configure what events they are interested in receiving. Are stored in lower-case format. -- Secret: a string used to verify received webhooks. Every webhook and its metadata is signed with a unique key for each endpoint. This signature can then be used to verify the webhook indeed comes from this service. - The format is a random string of bytes of size 24, base64 encoded. (larger size after encoding) - -The service has 3 starting modes, split into 3 separate commands: - -- `server`: REST web service API managing webhooks configs for users. -- `worker`: background service consuming kafka events on selected topics to send webhooks based on user configs and periodically finding failed webhooks requests to retry and sending new attempts. - -## Run linters and tests - -Run the linters: -``` -earthly +lint -``` - -Run the tests: -``` -earthly -P +tests -``` - -## Usage -``` -Usage: - webhooks [command] - -Available Commands: - completion Generate the autocompletion script for the specified shell - help Help about any command - serve Run webhooks server - version Get webhooks version - worker Run webhooks worker - -Flags: - --abort-after duration consider a webhook as failed after retrying it for this duration. (default 720h0m0s) - --debug Debug mode - -h, --help help for webhooks - --kafka-topics strings Kafka topics (default [default]) - --listen string server HTTP bind address (default ":8080") - --log-level string Log level (default "info") - --max-backoff-delay duration maximum backoff delay (default 1h0m0s) - --min-backoff-delay duration minimum backoff delay (default 1m0s) - --otel-resource-attributes strings Additional OTLP resource attributes - --otel-service-name string OpenTelemetry service name - --otel-traces Enable OpenTelemetry traces support - --otel-traces-batch Use OpenTelemetry batching - --otel-traces-exporter string OpenTelemetry traces exporter (default "stdout") - --otel-traces-exporter-jaeger-endpoint string OpenTelemetry traces Jaeger exporter endpoint - --otel-traces-exporter-jaeger-password string OpenTelemetry traces Jaeger exporter password - --otel-traces-exporter-jaeger-user string OpenTelemetry traces Jaeger exporter user - --otel-traces-exporter-otlp-endpoint string OpenTelemetry traces grpc endpoint - --otel-traces-exporter-otlp-insecure OpenTelemetry traces grpc insecure - --otel-traces-exporter-otlp-mode string OpenTelemetry traces OTLP exporter mode (grpc|http) (default "grpc") - --publisher-http-enabled Sent write event to http endpoint - --publisher-kafka-broker strings Kafka address is kafka enabled (default [localhost:9092]) - --publisher-kafka-enabled Publish write events to kafka - --publisher-kafka-sasl-enabled Enable SASL authentication on kafka publisher - --publisher-kafka-sasl-mechanism string SASL authentication mechanism - --publisher-kafka-sasl-password string SASL password - --publisher-kafka-sasl-scram-sha-size int SASL SCRAM SHA size (default 512) - --publisher-kafka-sasl-username string SASL username - --publisher-kafka-tls-enabled Enable TLS to connect on kafka - --publisher-nats-client-id string Nats client ID - --publisher-nats-enabled Publish write events to nats - --publisher-nats-max-reconnect int Nats: set the maximum number of reconnect attempts. (default 30) - --publisher-nats-reconnect-wait duration Nats: the wait time between reconnect attempts. (default 2s) - --publisher-nats-url string Nats url - --publisher-topic-mapping strings Define mapping between internal event types and topics - --retry-period duration worker retry period (default 1m0s) - --storage-postgres-conn-string string Postgres connection string (default "postgresql://webhooks:webhooks@127.0.0.1/webhooks?sslmode=disable") - --worker Enable worker on server -``` diff --git a/ee/webhooks/build.Dockerfile b/ee/webhooks/build.Dockerfile deleted file mode 100644 index b6bbee528f..0000000000 --- a/ee/webhooks/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:22.04 -COPY webhooks /usr/bin/webhooks -ENV OTEL_SERVICE_NAME webhooks -ENTRYPOINT ["/usr/bin/webhooks"] -CMD ["server"] diff --git a/ee/webhooks/cmd/flag/flags.go b/ee/webhooks/cmd/flag/flags.go deleted file mode 100644 index fe2abea6d9..0000000000 --- a/ee/webhooks/cmd/flag/flags.go +++ /dev/null @@ -1,49 +0,0 @@ -package flag - -import ( - "time" - - "github.com/sirupsen/logrus" - "github.com/spf13/pflag" -) - -const ( - LogLevel = "log-level" - Listen = "listen" - Worker = "worker" - - RetryPeriod = "retry-period" - AbortAfter = "abort-after" - MinBackoffDelay = "min-backoff-delay" - MaxBackoffDelay = "max-backoff-delay" - - KafkaTopics = "kafka-topics" - AutoMigrate = "auto-migrate" -) - -const ( - DefaultBindAddressServer = ":8080" - - DefaultPostgresConnString = "postgresql://webhooks:webhooks@127.0.0.1/webhooks?sslmode=disable" - - DefaultKafkaTopic = "default" -) - -var ( - DefaultRetryPeriod = time.Minute -) - -func Init(flagSet *pflag.FlagSet) { - flagSet.String(LogLevel, logrus.InfoLevel.String(), "Log level") - - flagSet.String(Listen, DefaultBindAddressServer, "server HTTP bind address") - flagSet.Duration(RetryPeriod, DefaultRetryPeriod, "worker retry period") - flagSet.Bool(Worker, false, "Enable worker on server") - - flagSet.StringSlice(KafkaTopics, []string{DefaultKafkaTopic}, "Kafka topics") - - flagSet.Duration(AbortAfter, 30*24*time.Hour, "consider a webhook as failed after retrying it for this duration.") - flagSet.Duration(MinBackoffDelay, time.Minute, "minimum backoff delay") - flagSet.Duration(MaxBackoffDelay, time.Hour, "maximum backoff delay") - flagSet.Bool(AutoMigrate, false, "auto migrate database") -} diff --git a/ee/webhooks/cmd/migrate.go b/ee/webhooks/cmd/migrate.go deleted file mode 100644 index c7c2603376..0000000000 --- a/ee/webhooks/cmd/migrate.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/bun/bunmigrate" - "github.com/formancehq/go-libs/logging" - "github.com/uptrace/bun" - - "github.com/formancehq/webhooks/cmd/flag" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/spf13/cobra" -) - -func newMigrateCommand() *cobra.Command { - return bunmigrate.NewDefaultCommand(func(cmd *cobra.Command, args []string, db *bun.DB) error { - return storage.Migrate(cmd.Context(), db) - }) -} - -func handleAutoMigrate(cmd *cobra.Command, args []string) error { - autoMigrate, _ := cmd.Flags().GetBool(flag.AutoMigrate) - if autoMigrate { - logging.FromContext(cmd.Context()).Info("Automatically migrating database...") - defer func() { - logging.FromContext(cmd.Context()).Info("Database migrated.") - }() - return bunmigrate.Run(cmd, args, func(cmd *cobra.Command, args []string, db *bun.DB) error { - return storage.Migrate(cmd.Context(), db) - }) - } - return nil -} diff --git a/ee/webhooks/cmd/root.go b/ee/webhooks/cmd/root.go deleted file mode 100644 index fe54af0490..0000000000 --- a/ee/webhooks/cmd/root.go +++ /dev/null @@ -1,24 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/service" - - "github.com/spf13/cobra" -) - -func NewRootCommand() *cobra.Command { - root := &cobra.Command{ - Use: "webhooks", - } - - root.AddCommand(newServeCommand()) - root.AddCommand(newWorkerCommand()) - root.AddCommand(newVersionCommand()) - root.AddCommand(newMigrateCommand()) - - return root -} - -func Execute() { - service.Execute(NewRootCommand()) -} diff --git a/ee/webhooks/cmd/serve.go b/ee/webhooks/cmd/serve.go deleted file mode 100644 index 02d7fa6f8c..0000000000 --- a/ee/webhooks/cmd/serve.go +++ /dev/null @@ -1,83 +0,0 @@ -package cmd - -import ( - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/otlp/otlptraces" - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/licence" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/webhooks/cmd/flag" - "github.com/formancehq/webhooks/pkg/backoff" - "github.com/formancehq/webhooks/pkg/otlp" - "github.com/formancehq/webhooks/pkg/server" - "github.com/formancehq/webhooks/pkg/storage/postgres" - "github.com/formancehq/webhooks/pkg/worker" - "github.com/spf13/cobra" - "go.uber.org/fx" -) - -func newServeCommand() *cobra.Command { - ret := &cobra.Command{ - Use: "serve", - Aliases: []string{"server"}, - Short: "Run webhooks server", - RunE: serve, - PreRunE: handleAutoMigrate, - } - otlptraces.AddFlags(ret.Flags()) - publish.AddFlags(ServiceName, ret.Flags()) - auth.AddFlags(ret.Flags()) - flag.Init(ret.Flags()) - bunconnect.AddFlags(ret.Flags()) - iam.AddFlags(ret.Flags()) - service.AddFlags(ret.Flags()) - licence.AddFlags(ret.Flags()) - - return ret -} - -func serve(cmd *cobra.Command, _ []string) error { - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return err - } - - listen, _ := cmd.Flags().GetString(flag.Listen) - options := []fx.Option{ - fx.Provide(func() server.ServiceInfo { - return server.ServiceInfo{ - Version: Version, - } - }), - auth.FXModuleFromFlags(cmd), - postgres.NewModule(*connectionOptions, service.IsDebug(cmd)), - otlp.HttpClientModule(), - server.FXModuleFromFlags(cmd, listen, service.IsDebug(cmd)), - licence.FXModuleFromFlags(cmd, ServiceName), - } - isWorker, _ := cmd.Flags().GetBool(flag.Worker) - if isWorker { - retryPeriod, _ := cmd.Flags().GetDuration(flag.RetryPeriod) - minBackOffDelay, _ := cmd.Flags().GetDuration(flag.MinBackoffDelay) - maxBackOffDelay, _ := cmd.Flags().GetDuration(flag.MaxBackoffDelay) - abortAfter, _ := cmd.Flags().GetDuration(flag.AbortAfter) - topics, _ := cmd.Flags().GetStringSlice(flag.KafkaTopics) - - options = append(options, worker.StartModule( - cmd, - retryPeriod, - backoff.NewExponential( - minBackOffDelay, - maxBackOffDelay, - abortAfter, - ), - service.IsDebug(cmd), - topics, - )) - } - - return service.New(cmd.OutOrStdout(), options...).Run(cmd) -} diff --git a/ee/webhooks/cmd/version.go b/ee/webhooks/cmd/version.go deleted file mode 100644 index 55c6da74f4..0000000000 --- a/ee/webhooks/cmd/version.go +++ /dev/null @@ -1,26 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -var ( - ServiceName = "webhooks" - Version = "develop" - BuildDate = "-" - Commit = "-" -) - -func newVersionCommand() *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: fmt.Sprintf("Get %s version", ServiceName), - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Version: %s \n", Version) - fmt.Printf("Date: %s \n", BuildDate) - fmt.Printf("Commit: %s \n", Commit) - }, - } -} diff --git a/ee/webhooks/cmd/worker.go b/ee/webhooks/cmd/worker.go deleted file mode 100644 index 01a6a08812..0000000000 --- a/ee/webhooks/cmd/worker.go +++ /dev/null @@ -1,81 +0,0 @@ -package cmd - -import ( - "net/http" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/aws/iam" - "github.com/formancehq/go-libs/publish" - - "github.com/formancehq/webhooks/pkg/storage/postgres" - - "github.com/formancehq/go-libs/bun/bunconnect" - "github.com/formancehq/go-libs/licence" - - "github.com/formancehq/go-libs/otlp/otlptraces" - - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/service" - "github.com/formancehq/webhooks/cmd/flag" - "github.com/formancehq/webhooks/pkg/backoff" - "github.com/formancehq/webhooks/pkg/otlp" - "github.com/formancehq/webhooks/pkg/worker" - "github.com/spf13/cobra" - "go.uber.org/fx" -) - -func newWorkerCommand() *cobra.Command { - ret := &cobra.Command{ - Use: "worker", - Short: "Run webhooks worker", - RunE: runWorker, - PreRunE: handleAutoMigrate, - } - otlptraces.AddFlags(ret.Flags()) - publish.AddFlags(ServiceName, ret.Flags()) - auth.AddFlags(ret.Flags()) - flag.Init(ret.Flags()) - bunconnect.AddFlags(ret.Flags()) - iam.AddFlags(ret.Flags()) - service.AddFlags(ret.Flags()) - licence.AddFlags(ret.Flags()) - - return ret -} - -func runWorker(cmd *cobra.Command, _ []string) error { - connectionOptions, err := bunconnect.ConnectionOptionsFromFlags(cmd) - if err != nil { - return err - } - - retryPeriod, _ := cmd.Flags().GetDuration(flag.RetryPeriod) - minBackOffDelay, _ := cmd.Flags().GetDuration(flag.MinBackoffDelay) - maxBackOffDelay, _ := cmd.Flags().GetDuration(flag.MaxBackoffDelay) - abortAfter, _ := cmd.Flags().GetDuration(flag.AbortAfter) - topics, _ := cmd.Flags().GetStringSlice(flag.KafkaTopics) - listen, _ := cmd.Flags().GetString(flag.Listen) - - return service.New( - cmd.OutOrStdout(), - otlp.HttpClientModule(), - licence.FXModuleFromFlags(cmd, ServiceName), - postgres.NewModule(*connectionOptions, service.IsDebug(cmd)), - fx.Provide(worker.NewWorkerHandler), - fx.Invoke(func(lc fx.Lifecycle, h http.Handler) { - lc.Append(httpserver.NewHook(h, httpserver.WithAddress(listen))) - }), - otlptraces.FXModuleFromFlags(cmd), - worker.StartModule( - cmd, - retryPeriod, - backoff.NewExponential( - minBackOffDelay, - maxBackOffDelay, - abortAfter, - ), - service.IsDebug(cmd), - topics, - ), - ).Run(cmd) -} diff --git a/ee/webhooks/config/redpanda/config.yaml b/ee/webhooks/config/redpanda/config.yaml deleted file mode 100644 index 5ee6333653..0000000000 --- a/ee/webhooks/config/redpanda/config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -auto_create_topics_enabled: true -delete_retention_ms: -1 diff --git a/ee/webhooks/go.mod b/ee/webhooks/go.mod deleted file mode 100644 index 6c3d9b7586..0000000000 --- a/ee/webhooks/go.mod +++ /dev/null @@ -1,163 +0,0 @@ -module github.com/formancehq/webhooks - -go 1.22.0 - -toolchain go1.22.7 - -require ( - github.com/ThreeDotsLabs/watermill v1.3.7 - github.com/alitto/pond v1.8.3 - github.com/formancehq/go-libs v1.7.1 - github.com/go-chi/chi/v5 v5.1.0 - github.com/google/uuid v1.6.0 - github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.9.0 - github.com/uptrace/bun v1.2.3 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/fx v1.22.2 -) - -require ( - dario.cat/mergo v1.0.1 // indirect - filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/IBM/sarama v1.43.3 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 // indirect - github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 // indirect - github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 // indirect - github.com/ajg/form v1.5.1 // indirect - github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.36 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect - github.com/aws/smithy-go v1.21.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/containerd/continuity v0.4.3 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 // indirect - github.com/docker/cli v27.3.1+incompatible // indirect - github.com/docker/docker v27.3.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect - github.com/eapache/queue v1.1.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-chi/render v1.0.3 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.8.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/goccy/go-json v0.10.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/gorilla/mux v1.8.1 // indirect - github.com/gorilla/schema v1.4.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect - github.com/lestrrat-go/blackmagic v1.0.2 // indirect - github.com/lestrrat-go/httpcc v1.0.1 // indirect - github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx v1.2.30 // indirect - github.com/lestrrat-go/option v1.0.1 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/lithammer/shortuuid/v3 v3.0.7 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/muhlemmer/gu v0.3.1 // indirect - github.com/muhlemmer/httpforwarded v0.1.0 // indirect - github.com/nats-io/nats.go v1.37.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/ory/dockertest/v3 v3.11.0 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/riandyrn/otelchi v0.10.0 // indirect - github.com/rs/cors v1.11.1 // indirect - github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun/dialect/pgdialect v1.2.3 // indirect - github.com/uptrace/bun/extra/bunotel v1.2.3 // indirect - github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect - github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect - github.com/xdg-go/stringprep v1.0.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xo/dburl v0.23.2 // indirect - github.com/zitadel/oidc/v2 v2.12.2 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 // indirect - go.opentelemetry.io/otel/log v0.6.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.30.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/dig v1.18.0 // indirect - go.uber.org/mock v0.4.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/ee/webhooks/go.sum b/ee/webhooks/go.sum deleted file mode 100644 index 075f3320e9..0000000000 --- a/ee/webhooks/go.sum +++ /dev/null @@ -1,439 +0,0 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= -github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= -github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/ThreeDotsLabs/watermill v1.3.7 h1:NV0PSTmuACVEOV4dMxRnmGXrmbz8U83LENOvpHekN7o= -github.com/ThreeDotsLabs/watermill v1.3.7/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1 h1:M0iYM5HsGcoxtiQqprRlYZNZnGk3w5LsE9RbC2R8myQ= -github.com/ThreeDotsLabs/watermill-http/v2 v2.3.1/go.mod h1:RwGHEzGsEEXC/rQNLWQqR83+WPlABgOgnv2kTB56Y4Y= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5 h1:ud+4txnRgtr3kZXfXZ5+C7kVQEvsLc5HSNUEa0g+X1Q= -github.com/ThreeDotsLabs/watermill-kafka/v3 v3.0.5/go.mod h1:t4o+4A6GB+XC8WL3DandhzPwd265zQuyWMQC/I+WIOU= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1 h1:afAkAFzeooBRQvxElR+6xoigXKCukcZXnE9ACxhwlPI= -github.com/ThreeDotsLabs/watermill-nats/v2 v2.1.1/go.mod h1:stjbT+s4u/s5ime5jdIyvPyjBGwGeJewIN7jxH8gp4k= -github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs= -github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0 h1:UyjtGmO0Uwl/K+zpzPwLoXzMhcN9xmnR2nrqJoBrg3c= -github.com/aws/aws-msk-iam-sasl-signer-go v1.0.0/go.mod h1:TJAXuFs2HcMib3sN5L0gUC+Q01Qvy3DemvA55WuC+iA= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18 h1:k51348zRERIvv01FflXAOQj50NeUiZUGOEedT4Vg+UE= -github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.18/go.mod h1:uybY6ESdxsT2dpzwSmpDgZJ3ekCYwVe/ZFYfAaXUbtU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8= -github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0 h1:R2zQhFwSCyyd7L43igYjDrH0wkC/i+QBPELuY0HOu84= -github.com/dnwe/otelsarama v0.0.0-20240308230250-9388d9d40bc0/go.mod h1:2MqLKYJfjs3UriXXF9Fd0Qmh/lhxi/6tHXkqtXxyIHc= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= -github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= -github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= -github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= -github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= -github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= -github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= -github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= -github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= -github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= -github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= -github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= -github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= -github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA= -github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ= -github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= -github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8= -github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM= -github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM= -github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/vWUetyzY= -github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= -github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw= -github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= -github.com/nats-io/nats-server/v2 v2.10.20 h1:CXDTYNHeBiAKBTAIP2gjpgbWap2GhATnTLgP8etyvEI= -github.com/nats-io/nats-server/v2 v2.10.20/go.mod h1:hgcPnoUtMfxz1qVOvLZGurVypQ+Cg6GXVXjG53iHk+M= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA= -github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= -github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/riandyrn/otelchi v0.10.0 h1:QMbR/FMDWBOkej6dfyWteYefUKqIFxnyrpaoWRJ9RPQ= -github.com/riandyrn/otelchi v0.10.0/go.mod h1:zBaX2FavWMlsvq4GqHit+QXxF1c5wIMZZFaYyW4+7FA= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= -github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= -github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= -github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc= -github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc= -github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw= -github.com/uptrace/bun/extra/bunotel v1.2.3 h1:G19QpDE68TXw97x6NciB6nKVDuK0Wb2KgtyMqNIyqBI= -github.com/uptrace/bun/extra/bunotel v1.2.3/go.mod h1:jHRgTqLlX/Zj1KIDokCMDat6JwZHJyErOx0PQ10UFgQ= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c= -github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/dburl v0.23.2 h1:Fl88cvayrgE56JA/sqhNMLljCW/b7RmG1mMkKMZUFgA= -github.com/xo/dburl v0.23.2/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zitadel/oidc/v2 v2.12.2 h1:3kpckg4rurgw7w7aLJrq7yvRxb2pkNOtD08RH42vPEs= -github.com/zitadel/oidc/v2 v2.12.2/go.mod h1:vhP26g1g4YVntcTi0amMYW3tJuid70nxqxf+kb6XKgg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0 h1:vumy4r1KMyaoQRltX7cJ37p3nluzALX9nugCjNNefuY= -go.opentelemetry.io/contrib/propagators/b3 v1.30.0/go.mod h1:fRbvRsaeVZ82LIl3u0rIvusIel2UUf+JcaaIpy5taho= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= -go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= -go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= -go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.22.2 h1:iPW+OPxv0G8w75OemJ1RAnTUrF55zOJlXlo1TbJ0Buw= -go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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-20200930185726-fdedc70b468f/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/ee/webhooks/main.go b/ee/webhooks/main.go deleted file mode 100644 index d4d8c5e3fc..0000000000 --- a/ee/webhooks/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/formancehq/webhooks/cmd" - -func main() { - cmd.Execute() -} diff --git a/ee/webhooks/openapi.yaml b/ee/webhooks/openapi.yaml deleted file mode 100644 index ea4b40ba6f..0000000000 --- a/ee/webhooks/openapi.yaml +++ /dev/null @@ -1,428 +0,0 @@ -openapi: 3.0.3 -info: - title: Webhooks - version: WEBHOOKS_VERSION -paths: - /configs: - get: - summary: Get many configs - description: Sorted by updated date descending - operationId: getManyConfigs - tags: - - webhooks.v1 - parameters: - - name: id - in: query - description: Optional filter by Config ID - required: false - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - - name: endpoint - in: query - description: Optional filter by endpoint URL - required: false - schema: - type: string - example: https://example.com - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigsResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:read - post: - summary: Insert a new config - description: > - Insert a new webhooks config. - - - The endpoint should be a valid https URL and be unique. - - - The secret is the endpoint's verification secret. - - If not passed or empty, a secret is automatically generated. - - The format is a random string of bytes of size 24, base64 encoded. - (larger size after encoding) - - - All eventTypes are converted to lower-case when inserted. - operationId: insertConfig - tags: - - webhooks.v1 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigUser' - required: true - responses: - '200': - description: Config created successfully. - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:write - /configs/{id}: - delete: - summary: Delete one config - description: Delete a webhooks config by ID. - operationId: deleteConfig - tags: - - webhooks.v1 - parameters: - - name: id - in: path - description: Config ID - required: true - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - responses: - '200': - description: Config successfully deleted. - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:write - /configs/{id}/test: - get: - summary: Test one config - description: Test a config by sending a webhook to its endpoint. - operationId: testConfig - tags: - - webhooks.v1 - parameters: - - name: id - in: path - description: Config ID - required: true - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/AttemptResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:read - /configs/{id}/activate: - put: - summary: Activate one config - description: >- - Activate a webhooks config by ID, to start receiving webhooks to its - endpoint. - operationId: activateConfig - tags: - - webhooks.v1 - parameters: - - name: id - in: path - description: Config ID - required: true - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - responses: - '200': - description: Config successfully activated. - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:write - /configs/{id}/deactivate: - put: - summary: Deactivate one config - description: >- - Deactivate a webhooks config by ID, to stop receiving webhooks to its - endpoint. - operationId: deactivateConfig - tags: - - webhooks.v1 - parameters: - - name: id - in: path - description: Config ID - required: true - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - responses: - '200': - description: Config successfully deactivated. - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:write - /configs/{id}/secret/change: - put: - summary: Change the signing secret of a config - description: > - Change the signing secret of the endpoint of a webhooks config. - - - If not passed or empty, a secret is automatically generated. - - The format is a random string of bytes of size 24, base64 encoded. - (larger size after encoding) - operationId: changeConfigSecret - tags: - - webhooks.v1 - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigChangeSecret' - parameters: - - name: id - in: path - description: Config ID - required: true - schema: - type: string - example: 4997257d-dfb6-445b-929c-cbe2ab182818 - responses: - '200': - description: Secret successfully changed. - content: - application/json: - schema: - $ref: '#/components/schemas/ConfigResponse' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - security: - - Authorization: - - webhooks:write -components: - schemas: - ConfigUser: - type: object - required: - - endpoint - - eventTypes - properties: - name: - type: string - example: customer_payment - endpoint: - type: string - example: https://example.com - secret: - type: string - example: V0bivxRWveaoz08afqjU6Ko/jwO0Cb+3 - eventTypes: - type: array - items: - type: string - example: TYPE1 - example: - - TYPE1 - - TYPE2 - ConfigsResponse: - type: object - required: - - cursor - properties: - cursor: - allOf: - - $ref: '#/components/schemas/Cursor' - - properties: - data: - items: - $ref: '#/components/schemas/WebhooksConfig' - type: array - type: object - required: - - data - Cursor: - type: object - required: - - hasMore - - data - properties: - hasMore: - type: boolean - example: false - data: - type: array - items: - $ref: '#/components/schemas/WebhooksConfig' - ConfigResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/WebhooksConfig' - WebhooksConfig: - properties: - id: - type: string - format: uuid - endpoint: - type: string - example: https://example.com - secret: - type: string - example: V0bivxRWveaoz08afqjU6Ko/jwO0Cb+3 - eventTypes: - type: array - items: - type: string - example: TYPE1 - example: - - TYPE1 - - TYPE2 - active: - type: boolean - example: true - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - required: - - id - - endpoint - - secret - - eventTypes - - active - - createdAt - - updatedAt - ConfigChangeSecret: - type: object - properties: - secret: - type: string - example: V0bivxRWveaoz08afqjU6Ko/jwO0Cb+3 - required: - - secret - AttemptResponse: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/Attempt' - Attempt: - properties: - id: - type: string - format: uuid - webhookID: - type: string - format: uuid - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - config: - $ref: '#/components/schemas/WebhooksConfig' - payload: - type: string - example: '{"data":"test"}' - statusCode: - type: integer - example: 200 - retryAttempt: - type: integer - example: 1 - status: - type: string - example: success - nextRetryAfter: - type: string - format: date-time - required: - - id - - webhookID - - createdAt - - updatedAt - - config - - payload - - statusCode - - retryAttempt - - status - ErrorResponse: - type: object - required: - - errorCode - - errorMessage - properties: - errorCode: - $ref: '#/components/schemas/ErrorsEnum' - errorMessage: - type: string - example: '[VALIDATION] invalid ''cursor'' query param' - details: - type: string - example: >- - https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9 - ErrorsEnum: - type: string - enum: - - INTERNAL - - VALIDATION - - NOT_FOUND - example: VALIDATION diff --git a/ee/webhooks/pkg/attempt.go b/ee/webhooks/pkg/attempt.go deleted file mode 100644 index 79d2aeb960..0000000000 --- a/ee/webhooks/pkg/attempt.go +++ /dev/null @@ -1,99 +0,0 @@ -package webhooks - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "time" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/webhooks/pkg/security" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -const ( - StatusAttemptSuccess = "success" - StatusAttemptToRetry = "to retry" - StatusAttemptFailed = "failed" -) - -type Attempt struct { - bun.BaseModel `bun:"table:attempts"` - - ID string `json:"id" bun:",pk"` - WebhookID string `json:"webhookID" bun:"webhook_id"` - CreatedAt time.Time `json:"createdAt" bun:"created_at,nullzero,notnull,default:current_timestamp"` - UpdatedAt time.Time `json:"updatedAt" bun:"updated_at,nullzero,notnull,default:current_timestamp"` - Config Config `json:"config" bun:"type:jsonb"` - Payload string `json:"payload"` - StatusCode int `json:"statusCode" bun:"status_code"` - RetryAttempt int `json:"retryAttempt" bun:"retry_attempt"` - Status string `json:"status"` - NextRetryAfter time.Time `json:"nextRetryAfter,omitempty" bun:"next_retry_after,nullzero"` -} - -func MakeAttempt(ctx context.Context, httpClient *http.Client, retryPolicy BackoffPolicy, id, webhookID string, attemptNb int, cfg Config, payload []byte, isTest bool) (Attempt, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodPost, cfg.Endpoint, bytes.NewBuffer(payload)) - if err != nil { - return Attempt{}, errors.Wrap(err, "http.NewRequestWithContext") - } - - ts := time.Now().UTC() - timestamp := ts.Unix() - signature, err := security.Sign(webhookID, timestamp, cfg.Secret, payload) - if err != nil { - return Attempt{}, errors.Wrap(err, "security.Sign") - } - - req.Header.Set("content-type", "application/json") - req.Header.Set("user-agent", "formance-webhooks/v0") - req.Header.Set("formance-webhook-id", webhookID) - req.Header.Set("formance-webhook-timestamp", fmt.Sprintf("%d", timestamp)) - req.Header.Set("formance-webhook-signature", signature) - req.Header.Set("formance-webhook-test", fmt.Sprintf("%v", isTest)) - - resp, err := httpClient.Do(req) - if err != nil { - return Attempt{}, errors.Wrap(err, "http.Client.Do") - } - - defer func() { - if err := resp.Body.Close(); err != nil { - logging.FromContext(ctx).Error( - errors.Wrap(err, "http.Response.Body.Close")) - } - }() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return Attempt{}, errors.Wrap(err, "io.ReadAll") - } - logging.FromContext(ctx).Debugf("webhooks.MakeAttempt: server response body: %s", string(body)) - - attempt := Attempt{ - ID: id, - WebhookID: webhookID, - Config: cfg, - Payload: string(payload), - StatusCode: resp.StatusCode, - RetryAttempt: attemptNb, - } - - if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices { - attempt.Status = StatusAttemptSuccess - return attempt, nil - } - - delay, err := retryPolicy.GetRetryDelay(attemptNb) - if err != nil { - attempt.Status = StatusAttemptFailed - return attempt, nil - } - - attempt.Status = StatusAttemptToRetry - attempt.NextRetryAfter = ts.Add(delay) - return attempt, nil -} diff --git a/ee/webhooks/pkg/backoff.go b/ee/webhooks/pkg/backoff.go deleted file mode 100644 index 07d983e5b9..0000000000 --- a/ee/webhooks/pkg/backoff.go +++ /dev/null @@ -1,7 +0,0 @@ -package webhooks - -import "time" - -type BackoffPolicy interface { - GetRetryDelay(attemptNumber int) (time.Duration, error) -} diff --git a/ee/webhooks/pkg/backoff/exponential.go b/ee/webhooks/pkg/backoff/exponential.go deleted file mode 100644 index dd81d0e3bd..0000000000 --- a/ee/webhooks/pkg/backoff/exponential.go +++ /dev/null @@ -1,40 +0,0 @@ -package backoff - -import ( - "errors" - "time" - - webhooks "github.com/formancehq/webhooks/pkg" -) - -var ErrMaxAttemptsReached = errors.New("max attempts reached") - -func NewExponential(minRetryDelay, maxRetryDelay, abortAfterDelay time.Duration) webhooks.BackoffPolicy { - return &exponential{ - minRetryDelay, - maxRetryDelay, - abortAfterDelay, - } -} - -type exponential struct { - minRetryDelay time.Duration - maxRetryDelay time.Duration - abortAfterDelay time.Duration -} - -func (e *exponential) GetRetryDelay(attemptNumber int) (time.Duration, error) { - delay := e.minRetryDelay - sinceFirstAttempt := delay - for i := 0; i < attemptNumber; i++ { - delay <<= 1 - if delay > e.maxRetryDelay { - delay = e.maxRetryDelay - } - sinceFirstAttempt += delay - } - if sinceFirstAttempt > e.abortAfterDelay { - return 0, ErrMaxAttemptsReached - } - return delay, nil -} diff --git a/ee/webhooks/pkg/backoff/exponential_test.go b/ee/webhooks/pkg/backoff/exponential_test.go deleted file mode 100644 index e71cd4cf98..0000000000 --- a/ee/webhooks/pkg/backoff/exponential_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package backoff - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestExponential_Nominal(t *testing.T) { - policy := NewExponential(time.Minute, time.Hour, 24*time.Hour) - wantDurations := []time.Duration{ - time.Minute, - 2 * time.Minute, - 4 * time.Minute, - 8 * time.Minute, - 16 * time.Minute, - 32 * time.Minute, - time.Hour, - time.Hour, - time.Hour, - } - for attemptNumber, wantDelay := range wantDurations { - t.Run(fmt.Sprint("attempt #", attemptNumber), func(t *testing.T) { - t.Parallel() - gotDelay, err := policy.GetRetryDelay(attemptNumber) - - assert.NoError(t, err) - assert.Equal(t, wantDelay, gotDelay) - }) - } -} - -func TestExponential_Limit(t *testing.T) { - // Attempt: 0 1 2 - // Delay: 1m 2m X - // sinceFirstAttempt: 1m 3m X - policy := NewExponential(time.Minute, 5*time.Minute, 3*time.Minute) - - delay, err := policy.GetRetryDelay(1) - assert.NoError(t, err) - assert.Equal(t, 2*time.Minute, delay) - - _, err = policy.GetRetryDelay(2) - assert.ErrorIs(t, err, ErrMaxAttemptsReached) -} diff --git a/ee/webhooks/pkg/backoff/noretry.go b/ee/webhooks/pkg/backoff/noretry.go deleted file mode 100644 index de58aaefb7..0000000000 --- a/ee/webhooks/pkg/backoff/noretry.go +++ /dev/null @@ -1,17 +0,0 @@ -package backoff - -import ( - "time" - - webhooks "github.com/formancehq/webhooks/pkg" -) - -func NewNoRetry() webhooks.BackoffPolicy { - return new(NoRetry) -} - -type NoRetry struct{} - -func (n *NoRetry) GetRetryDelay(attemptNumber int) (time.Duration, error) { - return 0, ErrMaxAttemptsReached -} diff --git a/ee/webhooks/pkg/backoff/noretry_test.go b/ee/webhooks/pkg/backoff/noretry_test.go deleted file mode 100644 index 0f57b79488..0000000000 --- a/ee/webhooks/pkg/backoff/noretry_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package backoff - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNoRetry(t *testing.T) { - policy := NewNoRetry() - _, err := policy.GetRetryDelay(0) - assert.ErrorIs(t, err, ErrMaxAttemptsReached) -} diff --git a/ee/webhooks/pkg/config.go b/ee/webhooks/pkg/config.go deleted file mode 100644 index 39f2e715ab..0000000000 --- a/ee/webhooks/pkg/config.go +++ /dev/null @@ -1,76 +0,0 @@ -package webhooks - -import ( - "encoding/base64" - "fmt" - "net/url" - "strings" - "time" - - "github.com/google/uuid" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type Config struct { - bun.BaseModel `bun:"table:configs"` - - ConfigUser - - ID string `json:"id" bun:",pk"` - Active bool `json:"active"` - Name string `json:"name" bun:"name,nullzero"` - CreatedAt time.Time `json:"createdAt" bun:"created_at,nullzero,notnull,default:current_timestamp"` - UpdatedAt time.Time `json:"updatedAt" bun:"updated_at,nullzero,notnull,default:current_timestamp"` -} - -type ConfigUser struct { - Endpoint string `json:"endpoint"` - Secret string `json:"secret"` - EventTypes []string `json:"eventTypes" bun:"event_types,array"` -} - -func NewConfig(cfgUser ConfigUser) Config { - return Config{ - ConfigUser: cfgUser, - ID: uuid.NewString(), - Active: true, - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), - } -} - -var ( - ErrInvalidEndpoint = errors.New("endpoint should be a valid url") - ErrInvalidEventTypes = errors.New("eventTypes should be filled") - ErrInvalidSecret = errors.New("decoded secret should be of size 24") -) - -func (c *ConfigUser) Validate() error { - if u, err := url.Parse(c.Endpoint); err != nil || len(u.String()) == 0 { - return ErrInvalidEndpoint - } - - if c.Secret == "" { - c.Secret = NewSecret() - } else { - if decoded, err := base64.StdEncoding.DecodeString(c.Secret); err != nil { - return fmt.Errorf("secret should be base64 encoded: %w", err) - } else if len(decoded) != 24 { - return ErrInvalidSecret - } - } - - if len(c.EventTypes) == 0 { - return ErrInvalidEventTypes - } - - for i, t := range c.EventTypes { - if len(t) == 0 { - return ErrInvalidEventTypes - } - c.EventTypes[i] = strings.ToLower(t) - } - - return nil -} diff --git a/ee/webhooks/pkg/config_test.go b/ee/webhooks/pkg/config_test.go deleted file mode 100644 index 5013690793..0000000000 --- a/ee/webhooks/pkg/config_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package webhooks - -import ( - "encoding/base64" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestConfig_Validate(t *testing.T) { - cfg := ConfigUser{ - Endpoint: "https://example.com", - EventTypes: []string{"TYPE1", "TYPE2"}, - } - assert.NoError(t, cfg.Validate()) - - cfg = ConfigUser{ - Endpoint: "https://example.com", - Secret: NewSecret(), - EventTypes: []string{"TYPE1", "TYPE2"}, - } - assert.NoError(t, cfg.Validate()) - - cfg = ConfigUser{ - Endpoint: " http://invalid", - EventTypes: []string{"TYPE1", "TYPE2"}, - } - assert.Error(t, cfg.Validate()) - - cfg = ConfigUser{ - Endpoint: "https://example.com", - EventTypes: []string{"TYPE1", ""}, - } - assert.Error(t, cfg.Validate()) - - cfg = ConfigUser{ - Endpoint: "https://example.com", - Secret: base64.StdEncoding.EncodeToString([]byte(`invalid`)), - EventTypes: []string{"TYPE1", "TYPE2"}, - } - assert.Error(t, cfg.Validate()) -} diff --git a/ee/webhooks/pkg/otlp/module.go b/ee/webhooks/pkg/otlp/module.go deleted file mode 100644 index ff834ece28..0000000000 --- a/ee/webhooks/pkg/otlp/module.go +++ /dev/null @@ -1,24 +0,0 @@ -package otlp - -import ( - "fmt" - "net/http" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.uber.org/fx" -) - -func HttpClientModule() fx.Option { - return fx.Provide(func() *http.Client { - return &http.Client{ - Transport: otelhttp.NewTransport(http.DefaultTransport, otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { - str := fmt.Sprintf("%s %s", r.Method, r.URL.Path) - if len(r.URL.Query()) == 0 { - return str - } - - return fmt.Sprintf("%s?%s", str, r.URL.Query().Encode()) - })), - } - }) -} diff --git a/ee/webhooks/pkg/secret.go b/ee/webhooks/pkg/secret.go deleted file mode 100644 index 9eb600c9c6..0000000000 --- a/ee/webhooks/pkg/secret.go +++ /dev/null @@ -1,37 +0,0 @@ -package webhooks - -import ( - "crypto/rand" - "encoding/base64" - "fmt" -) - -type Secret struct { - Secret string `json:"secret" bson:"secret"` -} - -func (s *Secret) Validate() error { - if s.Secret == "" { - s.Secret = NewSecret() - } else { - var decoded []byte - var err error - if decoded, err = base64.StdEncoding.DecodeString(s.Secret); err != nil { - return fmt.Errorf("secret should be base64 encoded: %w", err) - } - if len(decoded) != 24 { - return ErrInvalidSecret - } - } - - return nil -} - -func NewSecret() string { - token := make([]byte, 24) - _, err := rand.Read(token) - if err != nil { - panic(err) - } - return base64.StdEncoding.EncodeToString(token) -} diff --git a/ee/webhooks/pkg/secret_test.go b/ee/webhooks/pkg/secret_test.go deleted file mode 100644 index 98c0fc464a..0000000000 --- a/ee/webhooks/pkg/secret_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package webhooks - -import ( - "crypto/rand" - "encoding/base64" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestSecret_Validate(t *testing.T) { - sec := Secret{Secret: NewSecret()} - assert.NoError(t, sec.Validate()) - - sec = Secret{} - assert.NoError(t, sec.Validate()) - - sec = Secret{Secret: "invalid"} - assert.Error(t, sec.Validate()) - - sec = Secret{Secret: base64.StdEncoding.EncodeToString([]byte(`invalid`))} - assert.Error(t, sec.Validate()) - - token := make([]byte, 23) - _, err := rand.Read(token) - require.NoError(t, err) - tooShort := base64.StdEncoding.EncodeToString(token) - sec = Secret{Secret: tooShort} - assert.Error(t, sec.Validate()) - - token = make([]byte, 25) - _, err = rand.Read(token) - require.NoError(t, err) - tooLong := base64.StdEncoding.EncodeToString(token) - sec = Secret{Secret: tooLong} - assert.Error(t, sec.Validate()) -} diff --git a/ee/webhooks/pkg/security/security.go b/ee/webhooks/pkg/security/security.go deleted file mode 100644 index dc76d2ee40..0000000000 --- a/ee/webhooks/pkg/security/security.go +++ /dev/null @@ -1,54 +0,0 @@ -package security - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "fmt" - "strings" -) - -func Sign(id string, timestamp int64, secret string, payload []byte) (string, error) { - toSign := fmt.Sprintf("%s.%d.%s", id, timestamp, payload) - - hash := hmac.New(sha256.New, []byte(secret)) - if _, err := hash.Write([]byte(toSign)); err != nil { - return "", fmt.Errorf("hash.Hash.Write: %w", err) - } - - signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size())) - - base64.StdEncoding.Encode(signature, hash.Sum(nil)) - - return fmt.Sprintf("v1,%s", signature), nil -} - -func Verify(signatures, id string, timestamp int64, secret string, payload []byte) (bool, error) { - computedSignature, err := Sign(id, timestamp, secret, payload) - if err != nil { - return false, err - } - - expectedSignature := []byte(strings.Split(computedSignature, ",")[1]) - - signatureSlice := strings.Split(signatures, " ") - for _, versionedSignature := range signatureSlice { - sigParts := strings.Split(versionedSignature, ",") - if len(sigParts) < 2 { - continue - } - - version := sigParts[0] - signature := []byte(sigParts[1]) - - if version != "v1" { - continue - } - - if hmac.Equal(signature, expectedSignature) { - return true, nil - } - } - - return false, nil -} diff --git a/ee/webhooks/pkg/server/activation.go b/ee/webhooks/pkg/server/activation.go deleted file mode 100644 index 1a45b8071a..0000000000 --- a/ee/webhooks/pkg/server/activation.go +++ /dev/null @@ -1,59 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/server/apierrors" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/pkg/errors" -) - -func (h *serverHandler) activateOneConfigHandle(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, PathParamId) - c, err := h.store.UpdateOneConfigActivation(r.Context(), id, true) - if err == nil || errors.Is(err, storage.ErrConfigNotModified) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s", PathConfigs, id, PathActivate) - resp := api.BaseResponse[webhooks.Config]{ - Data: &c, - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - } else if errors.Is(err, storage.ErrConfigNotFound) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s: %s", PathConfigs, id, PathActivate, storage.ErrConfigNotFound) - apierrors.ResponseError(w, r, apierrors.NewNotFoundError(storage.ErrConfigNotFound.Error())) - } else { - logging.FromContext(r.Context()).Errorf("PUT %s/%s%s: %s", PathConfigs, id, PathActivate, err) - apierrors.ResponseError(w, r, err) - } -} - -func (h *serverHandler) deactivateOneConfigHandle(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, PathParamId) - c, err := h.store.UpdateOneConfigActivation(r.Context(), id, false) - if err == nil || errors.Is(err, storage.ErrConfigNotModified) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s", PathConfigs, id, PathDeactivate) - resp := api.BaseResponse[webhooks.Config]{ - Data: &c, - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - } else if errors.Is(err, storage.ErrConfigNotFound) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s: %s", PathConfigs, id, PathDeactivate, storage.ErrConfigNotFound) - apierrors.ResponseError(w, r, apierrors.NewNotFoundError(storage.ErrConfigNotFound.Error())) - } else { - logging.FromContext(r.Context()).Errorf("PUT %s/%s%s: %s", PathConfigs, id, PathDeactivate, err) - apierrors.ResponseError(w, r, err) - } -} diff --git a/ee/webhooks/pkg/server/apierrors/errors.go b/ee/webhooks/pkg/server/apierrors/errors.go deleted file mode 100644 index 6cb49b2d0f..0000000000 --- a/ee/webhooks/pkg/server/apierrors/errors.go +++ /dev/null @@ -1,93 +0,0 @@ -package apierrors - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - "github.com/pkg/errors" -) - -const ( - ErrInternal = "INTERNAL" - ErrValidation = "VALIDATION" - ErrContextCancelled = "CONTEXT_CANCELLED" - ErrNotFound = "NOT_FOUND" -) - -func ResponseError(w http.ResponseWriter, r *http.Request, err error) { - status, code := coreErrorToErrorCode(err) - w.WriteHeader(status) - if status < 500 { - err := json.NewEncoder(w).Encode(api.ErrorResponse{ - ErrorCode: code, - ErrorMessage: err.Error(), - }) - if err != nil { - panic(err) - } - } else { - logging.FromContext(r.Context()).Errorf("internal server error: %s", err) - } -} - -func coreErrorToErrorCode(err error) (int, string) { - switch { - case IsValidationError(err): - return http.StatusBadRequest, ErrValidation - case IsNotFoundError(err): - return http.StatusNotFound, ErrNotFound - case errors.Is(err, context.Canceled): - return http.StatusInternalServerError, ErrContextCancelled - default: - return http.StatusInternalServerError, ErrInternal - } -} - -type ValidationError struct { - Msg string -} - -func (v ValidationError) Error() string { - return v.Msg -} - -func (v ValidationError) Is(err error) bool { - _, ok := err.(*ValidationError) - return ok -} - -func NewValidationError(msg string) *ValidationError { - return &ValidationError{ - Msg: msg, - } -} - -func IsValidationError(err error) bool { - return errors.Is(err, &ValidationError{}) -} - -type NotFoundError struct { - Msg string -} - -func (v NotFoundError) Error() string { - return v.Msg -} - -func (v NotFoundError) Is(err error) bool { - _, ok := err.(*NotFoundError) - return ok -} - -func NewNotFoundError(msg string) *NotFoundError { - return &NotFoundError{ - Msg: msg, - } -} - -func IsNotFoundError(err error) bool { - return errors.Is(err, &NotFoundError{}) -} diff --git a/ee/webhooks/pkg/server/delete.go b/ee/webhooks/pkg/server/delete.go deleted file mode 100644 index 6ac98103e6..0000000000 --- a/ee/webhooks/pkg/server/delete.go +++ /dev/null @@ -1,26 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/webhooks/pkg/server/apierrors" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/pkg/errors" -) - -func (h *serverHandler) deleteOneConfigHandle(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, PathParamId) - err := h.store.DeleteOneConfig(r.Context(), id) - if err == nil { - logging.FromContext(r.Context()).Debugf("DELETE %s/%s", PathConfigs, id) - } else if errors.Is(err, storage.ErrConfigNotFound) { - logging.FromContext(r.Context()).Debugf("DELETE %s/%s: %s", PathConfigs, id, storage.ErrConfigNotFound) - apierrors.ResponseError(w, r, apierrors.NewNotFoundError(storage.ErrConfigNotFound.Error())) - } else { - logging.FromContext(r.Context()).Errorf("DELETE %s/%s: %s", PathConfigs, id, err) - apierrors.ResponseError(w, r, err) - } -} diff --git a/ee/webhooks/pkg/server/get.go b/ee/webhooks/pkg/server/get.go deleted file mode 100644 index 7df515ecc9..0000000000 --- a/ee/webhooks/pkg/server/get.go +++ /dev/null @@ -1,70 +0,0 @@ -package server - -import ( - "encoding/json" - "errors" - "net/http" - "net/url" - - "github.com/formancehq/go-libs/bun/bunpaginate" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/server/apierrors" -) - -func (h *serverHandler) getManyConfigsHandle(w http.ResponseWriter, r *http.Request) { - filter, err := buildQueryFilter(r.URL.Query()) - if err != nil { - apierrors.ResponseError(w, r, apierrors.NewValidationError(err.Error())) - return - } - - cfgs, err := h.store.FindManyConfigs(r.Context(), filter) - if err != nil { - logging.FromContext(r.Context()).Errorf("storage.store.FindManyConfigs: %s", err) - apierrors.ResponseError(w, r, err) - return - } - - resp := api.BaseResponse[webhooks.Config]{ - Cursor: &bunpaginate.Cursor[webhooks.Config]{ - Data: cfgs, - }, - } - - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - - logging.FromContext(r.Context()).Debugf("GET /configs: %d results", len(resp.Cursor.Data)) -} - -var ErrInvalidParams = errors.New("invalid params: only 'id' and 'endpoint' with a valid URL are accepted") - -func buildQueryFilter(values url.Values) (map[string]any, error) { - filter := map[string]any{} - - for key, value := range values { - if len(value) != 1 { - return nil, ErrInvalidParams - } - switch key { - case "id": - filter["id"] = value[0] - case "endpoint": - if u, err := url.Parse(value[0]); err != nil { - return nil, ErrInvalidParams - } else { - filter["endpoint"] = u.String() - } - default: - return nil, ErrInvalidParams - } - } - - return filter, nil -} diff --git a/ee/webhooks/pkg/server/handler.go b/ee/webhooks/pkg/server/handler.go deleted file mode 100644 index 65f56f16c4..0000000000 --- a/ee/webhooks/pkg/server/handler.go +++ /dev/null @@ -1,76 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/webhooks/pkg/storage" -) - -const ( - PathHealthCheck = "/_healthcheck" - PathInfo = "/_info" - PathConfigs = "/configs" - PathTest = "/test" - PathActivate = "/activate" - PathDeactivate = "/deactivate" - PathChangeSecret = "/secret/change" - PathId = "/{" + PathParamId + "}" - PathParamId = "id" -) - -type serverHandler struct { - *chi.Mux - - store storage.Store - httpClient *http.Client -} - -func newServerHandler( - store storage.Store, - httpClient *http.Client, - logger logging.Logger, - info ServiceInfo, - authenticator auth.Authenticator, - debug bool, -) http.Handler { - h := &serverHandler{ - Mux: chi.NewRouter(), - store: store, - httpClient: httpClient, - } - - h.Mux.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - handler.ServeHTTP(w, r) - }) - }) - h.Mux.Use(func(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handler.ServeHTTP(w, r.WithContext(logging.ContextWithLogger(r.Context(), logger))) - }) - }) - h.Mux.Get(PathHealthCheck, h.HealthCheckHandle) - h.Mux.Get(PathInfo, h.getInfo(info)) - - h.Mux.Group(func(r chi.Router) { - r.Use(auth.Middleware(authenticator)) - r.Use(service.OTLPMiddleware("webhooks", debug)) - - r.Get(PathConfigs, h.getManyConfigsHandle) - r.Post(PathConfigs, h.insertOneConfigHandle) - r.Delete(PathConfigs+PathId, h.deleteOneConfigHandle) - r.Get(PathConfigs+PathId+PathTest, h.testOneConfigHandle) - r.Put(PathConfigs+PathId+PathActivate, h.activateOneConfigHandle) - r.Put(PathConfigs+PathId+PathDeactivate, h.deactivateOneConfigHandle) - r.Put(PathConfigs+PathId+PathChangeSecret, h.changeSecretHandle) - }) - - return h -} diff --git a/ee/webhooks/pkg/server/health.go b/ee/webhooks/pkg/server/health.go deleted file mode 100644 index b97a8c3e3d..0000000000 --- a/ee/webhooks/pkg/server/health.go +++ /dev/null @@ -1,7 +0,0 @@ -package server - -import ( - "net/http" -) - -func (h *serverHandler) HealthCheckHandle(_ http.ResponseWriter, _ *http.Request) {} diff --git a/ee/webhooks/pkg/server/helpers.go b/ee/webhooks/pkg/server/helpers.go deleted file mode 100644 index 82f6577748..0000000000 --- a/ee/webhooks/pkg/server/helpers.go +++ /dev/null @@ -1,63 +0,0 @@ -package server - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" - - "github.com/formancehq/webhooks/pkg/server/apierrors" -) - -func decodeJSONBody(r *http.Request, dst interface{}, allowEmpty bool) error { - dec := json.NewDecoder(r.Body) - dec.DisallowUnknownFields() - - if err := dec.Decode(&dst); err != nil { - var syntaxError *json.SyntaxError - var unmarshalTypeError *json.UnmarshalTypeError - - switch { - case errors.As(err, &syntaxError): - msg := fmt.Sprintf("Request body contains badly-formed JSON (at position %d)", syntaxError.Offset) - return &apierrors.ValidationError{Msg: msg} - - case errors.Is(err, io.ErrUnexpectedEOF): - msg := "Request body contains badly-formed JSON" - return &apierrors.ValidationError{Msg: msg} - - case errors.As(err, &unmarshalTypeError): - msg := fmt.Sprintf("Request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset) - return &apierrors.ValidationError{Msg: msg} - - case strings.HasPrefix(err.Error(), "json: unknown field "): - fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") - msg := fmt.Sprintf("Request body contains unknown field %s", fieldName) - return &apierrors.ValidationError{Msg: msg} - - case errors.Is(err, io.EOF): - if allowEmpty { - return nil - } - msg := "Request body must not be empty" - return &apierrors.ValidationError{Msg: msg} - - default: - return fmt.Errorf("json.Decoder.Decode: %w", err) - } - } - - if err := dec.Decode(&struct{}{}); !errors.Is(err, io.EOF) { - msg := "Request body must only contain a single JSON object" - return &apierrors.ValidationError{Msg: msg} - } - - if r.Header.Get("Content-Type") != "application/json" { - msg := "Content-Type header should be application/json" - return &apierrors.ValidationError{Msg: msg} - } - - return nil -} diff --git a/ee/webhooks/pkg/server/info.go b/ee/webhooks/pkg/server/info.go deleted file mode 100644 index c7ad56a231..0000000000 --- a/ee/webhooks/pkg/server/info.go +++ /dev/null @@ -1,17 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/formancehq/go-libs/api" -) - -type ServiceInfo struct { - Version string `json:"version"` -} - -func (h *serverHandler) getInfo(info ServiceInfo) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - api.RawOk(w, info) - } -} diff --git a/ee/webhooks/pkg/server/insert.go b/ee/webhooks/pkg/server/insert.go deleted file mode 100644 index 04d1893363..0000000000 --- a/ee/webhooks/pkg/server/insert.go +++ /dev/null @@ -1,45 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/server/apierrors" - "github.com/pkg/errors" -) - -func (h *serverHandler) insertOneConfigHandle(w http.ResponseWriter, r *http.Request) { - cfg := webhooks.ConfigUser{} - if err := decodeJSONBody(r, &cfg, false); err != nil { - logging.FromContext(r.Context()).Errorf("decodeJSONBody: %s", err) - apierrors.ResponseError(w, r, apierrors.NewValidationError(err.Error())) - return - } - - if err := cfg.Validate(); err != nil { - err := errors.Wrap(err, "invalid config") - logging.FromContext(r.Context()).Errorf(err.Error()) - apierrors.ResponseError(w, r, apierrors.NewValidationError(err.Error())) - return - } - - c, err := h.store.InsertOneConfig(r.Context(), cfg) - if err == nil { - logging.FromContext(r.Context()).Debugf("POST %s: inserted id %s", PathConfigs, c.ID) - resp := api.BaseResponse[webhooks.Config]{ - Data: &c, - } - - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - } else { - logging.FromContext(r.Context()).Errorf("POST %s: %s", PathConfigs, err) - apierrors.ResponseError(w, r, err) - } -} diff --git a/ee/webhooks/pkg/server/module.go b/ee/webhooks/pkg/server/module.go deleted file mode 100644 index 4cfdd9f6c9..0000000000 --- a/ee/webhooks/pkg/server/module.go +++ /dev/null @@ -1,43 +0,0 @@ -package server - -import ( - "net/http" - "os" - - "github.com/spf13/cobra" - - "github.com/formancehq/go-libs/auth" - "github.com/formancehq/webhooks/pkg/storage" - - "github.com/formancehq/go-libs/httpserver" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/otlp/otlptraces" - "go.uber.org/fx" -) - -func FXModuleFromFlags(cmd *cobra.Command, addr string, debug bool) fx.Option { - var options []fx.Option - - options = append(options, otlptraces.FXModuleFromFlags(cmd)) - - options = append(options, fx.Provide( - func( - store storage.Store, - httpClient *http.Client, - logger logging.Logger, - info ServiceInfo, - authenticator auth.Authenticator, - ) http.Handler { - return newServerHandler(store, httpClient, logger, info, authenticator, debug) - }, - ), fx.Invoke(func(lc fx.Lifecycle, handler http.Handler) { - lc.Append(httpserver.NewHook(handler, httpserver.WithAddress(addr))) - })) - - logging.Debugf("starting server with env:") - for _, e := range os.Environ() { - logging.Debugf("%s", e) - } - - return fx.Module("webhooks server", options...) -} diff --git a/ee/webhooks/pkg/server/secret.go b/ee/webhooks/pkg/server/secret.go deleted file mode 100644 index 3321afcd3a..0000000000 --- a/ee/webhooks/pkg/server/secret.go +++ /dev/null @@ -1,50 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/server/apierrors" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/pkg/errors" -) - -func (h *serverHandler) changeSecretHandle(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, PathParamId) - sec := webhooks.Secret{} - if err := decodeJSONBody(r, &sec, true); err != nil { - logging.FromContext(r.Context()).Errorf("decodeJSONBody: %s", err) - apierrors.ResponseError(w, r, apierrors.NewValidationError(err.Error())) - return - } - - if err := sec.Validate(); err != nil { - logging.FromContext(r.Context()).Errorf("invalid secret: %s", err) - apierrors.ResponseError(w, r, apierrors.NewValidationError(err.Error())) - return - } - - c, err := h.store.UpdateOneConfigSecret(r.Context(), id, sec.Secret) - if err == nil || errors.Is(err, storage.ErrConfigNotModified) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s", PathConfigs, id, PathChangeSecret) - resp := api.BaseResponse[webhooks.Config]{ - Data: &c, - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - } else if errors.Is(err, storage.ErrConfigNotFound) { - logging.FromContext(r.Context()).Debugf("PUT %s/%s%s: %s", PathConfigs, id, PathChangeSecret, storage.ErrConfigNotFound) - apierrors.ResponseError(w, r, apierrors.NewNotFoundError(storage.ErrConfigNotFound.Error())) - } else { - logging.FromContext(r.Context()).Errorf("PUT %s/%s%s: %s", PathConfigs, id, PathChangeSecret, err) - apierrors.ResponseError(w, r, err) - } -} diff --git a/ee/webhooks/pkg/server/test.go b/ee/webhooks/pkg/server/test.go deleted file mode 100644 index d525ebb02f..0000000000 --- a/ee/webhooks/pkg/server/test.go +++ /dev/null @@ -1,49 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/api" - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/backoff" - "github.com/formancehq/webhooks/pkg/server/apierrors" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/google/uuid" -) - -func (h *serverHandler) testOneConfigHandle(w http.ResponseWriter, r *http.Request) { - id := chi.URLParam(r, PathParamId) - cfgs, err := h.store.FindManyConfigs(r.Context(), map[string]any{"id": id}) - if err == nil { - if len(cfgs) == 0 { - logging.FromContext(r.Context()).Errorf("GET %s/%s%s: %s", PathConfigs, id, PathTest, storage.ErrConfigNotFound) - apierrors.ResponseError(w, r, apierrors.NewNotFoundError(storage.ErrConfigNotFound.Error())) - return - } - logging.FromContext(r.Context()).Debugf("GET %s/%s%s", PathConfigs, id, PathTest) - retryPolicy := backoff.NewNoRetry() - attempt, err := webhooks.MakeAttempt(r.Context(), h.httpClient, retryPolicy, uuid.NewString(), - uuid.NewString(), 0, cfgs[0], []byte(`{"data":"test"}`), true) - if err != nil { - logging.FromContext(r.Context()).Errorf("GET %s/%s%s: %s", PathConfigs, id, PathTest, err) - apierrors.ResponseError(w, r, err) - } else { - logging.FromContext(r.Context()).Debugf("GET %s/%s%s", PathConfigs, id, PathTest) - resp := api.BaseResponse[webhooks.Attempt]{ - Data: &attempt, - } - if err := json.NewEncoder(w).Encode(resp); err != nil { - logging.FromContext(r.Context()).Errorf("json.Encoder.Encode: %s", err) - apierrors.ResponseError(w, r, err) - return - } - } - } else { - logging.FromContext(r.Context()).Errorf("GET %s/%s%s: %s", PathConfigs, id, PathTest, err) - apierrors.ResponseError(w, r, err) - } -} diff --git a/ee/webhooks/pkg/storage/migrations.go b/ee/webhooks/pkg/storage/migrations.go deleted file mode 100644 index d832ec9937..0000000000 --- a/ee/webhooks/pkg/storage/migrations.go +++ /dev/null @@ -1,63 +0,0 @@ -package storage - -import ( - "context" - - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/pkg/errors" - - "github.com/formancehq/go-libs/migrations" - "github.com/uptrace/bun" -) - -func Migrate(ctx context.Context, db *bun.DB) error { - migrator := migrations.NewMigrator() - migrator.RegisterMigrations( - migrations.Migration{ - Name: "Init schema", - Up: func(tx bun.Tx) error { - _, err := tx.NewCreateTable().Model((*webhooks.Config)(nil)). - IfNotExists(). - Exec(ctx) - if err != nil { - return errors.Wrap(err, "creating 'configs' table") - } - _, err = tx.NewCreateIndex().Model((*webhooks.Config)(nil)). - IfNotExists(). - Index("configs_idx"). - Column("event_types"). - Exec(ctx) - if err != nil { - return errors.Wrap(err, "creating index on 'configs' table") - } - _, err = tx.NewCreateTable().Model((*webhooks.Attempt)(nil)). - IfNotExists(). - Exec(ctx) - if err != nil { - return errors.Wrap(err, "creating 'attempts' table") - } - _, err = tx.NewCreateIndex().Model((*webhooks.Attempt)(nil)). - IfNotExists(). - Index("attempts_idx"). - Column("webhook_id", "status"). - Exec(ctx) - if err != nil { - return errors.Wrap(err, "creating index on 'attempts' table") - } - return nil - }, - }, - migrations.Migration{ - Up: func(tx bun.Tx) error { - _, err := tx.NewAddColumn(). - Table("configs"). - ColumnExpr("name varchar(255)"). - IfNotExists(). - Exec(ctx) - return errors.Wrap(err, "adding 'name' column") - }, - }, - ) - - return migrator.Up(ctx, db) -} diff --git a/ee/webhooks/pkg/storage/postgres/main_test.go b/ee/webhooks/pkg/storage/postgres/main_test.go deleted file mode 100644 index 5da7b26717..0000000000 --- a/ee/webhooks/pkg/storage/postgres/main_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package postgres_test - -import ( - "testing" - - "github.com/formancehq/go-libs/testing/docker" - "github.com/formancehq/go-libs/testing/utils" - - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/testing/platform/pgtesting" -) - -var srv *pgtesting.PostgresServer - -func TestMain(m *testing.M) { - utils.WithTestMain(func(t *utils.TestingTForMain) int { - srv = pgtesting.CreatePostgresServer(t, docker.NewPool(t, logging.Testing())) - - return m.Run() - }) -} diff --git a/ee/webhooks/pkg/storage/postgres/module.go b/ee/webhooks/pkg/storage/postgres/module.go deleted file mode 100644 index 6e62661b9d..0000000000 --- a/ee/webhooks/pkg/storage/postgres/module.go +++ /dev/null @@ -1,19 +0,0 @@ -package postgres - -import ( - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/bun/bunconnect" - - "github.com/formancehq/webhooks/pkg/storage" - "go.uber.org/fx" -) - -func NewModule(connectionOptions bunconnect.ConnectionOptions, debug bool) fx.Option { - return fx.Options( - bunconnect.Module(connectionOptions, debug), - fx.Provide(func(db *bun.DB) (storage.Store, error) { - return NewStore(db) - }), - ) -} diff --git a/ee/webhooks/pkg/storage/postgres/postgres.go b/ee/webhooks/pkg/storage/postgres/postgres.go deleted file mode 100644 index 5531bef698..0000000000 --- a/ee/webhooks/pkg/storage/postgres/postgres.go +++ /dev/null @@ -1,204 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - "time" - - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/pkg/errors" - "github.com/uptrace/bun" -) - -type Store struct { - db *bun.DB -} - -var _ storage.Store = &Store{} - -func NewStore(db *bun.DB) (storage.Store, error) { - return Store{db: db}, nil -} - -func (s Store) FindManyConfigs(ctx context.Context, filters map[string]any) ([]webhooks.Config, error) { - res := []webhooks.Config{} - sq := s.db.NewSelect().Model(&res) - for key, val := range filters { - switch key { - case "id": - sq = sq.Where("id = ?", val) - case "endpoint": - sq = sq.Where("endpoint = ?", val) - case "active": - sq = sq.Where("active = ?", val) - case "event_types": - sq = sq.Where("? = ANY (event_types)", val) - default: - panic(key) - } - } - sq.Order("updated_at DESC") - if err := sq.Scan(ctx); err != nil { - return nil, errors.Wrap(err, "selecting configs") - } - - return res, nil -} - -func (s Store) InsertOneConfig(ctx context.Context, cfgUser webhooks.ConfigUser) (webhooks.Config, error) { - cfg := webhooks.NewConfig(cfgUser) - if _, err := s.db.NewInsert().Model(&cfg).Exec(ctx); err != nil { - return webhooks.Config{}, errors.Wrap(err, "insert one config") - } - - return cfg, nil -} - -func (s Store) DeleteOneConfig(ctx context.Context, id string) error { - cfg := webhooks.Config{} - if err := s.db.NewSelect().Model(&cfg). - Where("id = ?", id).Scan(ctx); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return storage.ErrConfigNotFound - } - return errors.Wrap(err, "selecting one config before deleting") - } - - if _, err := s.db.NewDelete().Model((*webhooks.Config)(nil)). - Where("id = ?", id).Exec(ctx); err != nil { - return errors.Wrap(err, "deleting one config") - } - - return nil -} - -func (s Store) UpdateOneConfigActivation(ctx context.Context, id string, active bool) (webhooks.Config, error) { - cfg := webhooks.Config{} - if err := s.db.NewSelect().Model(&cfg). - Where("id = ?", id).Scan(ctx); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return webhooks.Config{}, storage.ErrConfigNotFound - } - return webhooks.Config{}, errors.Wrap(err, "selecting one config before updating activation") - } - if cfg.Active == active { - return cfg, storage.ErrConfigNotModified - } - - if _, err := s.db.NewUpdate().Model((*webhooks.Config)(nil)). - Where("id = ?", id). - Set("active = ?", active). - Set("updated_at = ?", time.Now().UTC()). - Exec(ctx); err != nil { - return webhooks.Config{}, errors.Wrap(err, "updating one config activation") - } - - cfg.Active = active - return cfg, nil -} - -func (s Store) UpdateOneConfigSecret(ctx context.Context, id, secret string) (webhooks.Config, error) { - cfg := webhooks.Config{} - if err := s.db.NewSelect().Model(&cfg). - Where("id = ?", id).Scan(ctx); err != nil { - if errors.Is(err, sql.ErrNoRows) { - return webhooks.Config{}, storage.ErrConfigNotFound - } - return webhooks.Config{}, errors.Wrap(err, "selecting one config before updating secret") - } - if cfg.Secret == secret { - return cfg, storage.ErrConfigNotModified - } - - if _, err := s.db.NewUpdate().Model((*webhooks.Config)(nil)). - Where("id = ?", id). - Set("secret = ?", secret). - Set("updated_at = ?", time.Now().UTC()). - Exec(ctx); err != nil { - return webhooks.Config{}, errors.Wrap(err, "updating one config secret") - } - - cfg.Secret = secret - return cfg, nil -} - -func (s Store) FindAttemptsToRetryByWebhookID(ctx context.Context, webhookID string) ([]webhooks.Attempt, error) { - res := []webhooks.Attempt{} - if err := s.db.NewSelect().Model(&res). - Where("webhook_id = ?", webhookID). - Where("status = ?", webhooks.StatusAttemptToRetry). - Where("next_retry_after < ?", time.Now().UTC()). - Order("created_at DESC"). - Scan(ctx); err != nil { - return nil, errors.Wrap(err, "finding attempts to retry") - } - - return res, nil -} - -func (s Store) FindWebhookIDsToRetry(ctx context.Context) ([]string, error) { - atts := []webhooks.Attempt{} - if err := s.db.NewSelect().Model(&atts). - Column("webhook_id").Distinct(). - Where("status = ?", webhooks.StatusAttemptToRetry). - Join("join configs c on c.id = attempt.config->>'id'"). - Where("next_retry_after < ?", time.Now().UTC()). - Scan(ctx); err != nil { - return nil, errors.Wrap(err, "finding distinct webhook IDs to retry") - } - - webhookIDs := []string{} - for _, att := range atts { - webhookIDs = append(webhookIDs, att.WebhookID) - } - - return webhookIDs, nil -} - -func (s Store) UpdateAttemptsStatus(ctx context.Context, webhookID, status string) ([]webhooks.Attempt, error) { - atts := []webhooks.Attempt{} - if err := s.db.NewSelect().Model(&atts). - Where("webhook_id = ?", webhookID).Scan(ctx); err != nil { - return []webhooks.Attempt{}, errors.Wrap(err, "selecting attempts by webhook ID before updating status") - } - if len(atts) == 0 { - return []webhooks.Attempt{}, storage.ErrWebhookIDNotFound - } - - toUpdate := false - for _, att := range atts { - if att.Status != status { - toUpdate = true - } - } - if !toUpdate { - return []webhooks.Attempt{}, storage.ErrAttemptsNotModified - } - - if _, err := s.db.NewUpdate().Model((*webhooks.Attempt)(nil)). - Where("webhook_id = ?", webhookID). - Set("status = ?", status). - Set("updated_at = ?", time.Now().UTC()). - Exec(ctx); err != nil { - return []webhooks.Attempt{}, errors.Wrap(err, "updating attempts status") - } - - for _, att := range atts { - att.Status = status - } - - return atts, nil -} - -func (s Store) InsertOneAttempt(ctx context.Context, att webhooks.Attempt) error { - if _, err := s.db.NewInsert().Model(&att).Exec(ctx); err != nil { - return errors.Wrap(err, "inserting one attempt") - } - - return nil -} - -func (s Store) Close(ctx context.Context) error { - return s.db.Close() -} diff --git a/ee/webhooks/pkg/storage/postgres/postgres_test.go b/ee/webhooks/pkg/storage/postgres/postgres_test.go deleted file mode 100644 index d78e9f7a36..0000000000 --- a/ee/webhooks/pkg/storage/postgres/postgres_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package postgres_test - -import ( - "context" - "testing" - - "github.com/formancehq/go-libs/bun/bundebug" - "github.com/uptrace/bun" - - "github.com/formancehq/go-libs/logging" - - "github.com/formancehq/go-libs/bun/bunconnect" - - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/formancehq/webhooks/pkg/storage/postgres" - "github.com/stretchr/testify/require" -) - -func TestStore(t *testing.T) { - - hooks := make([]bun.QueryHook, 0) - if testing.Verbose() { - hooks = append(hooks, bundebug.NewQueryHook()) - } - - pgDB := srv.NewDatabase(t) - db, err := bunconnect.OpenSQLDB(logging.TestingContext(), bunconnect.ConnectionOptions{ - DatabaseSourceName: pgDB.ConnString(), - }, hooks...) - require.NoError(t, err) - defer func() { - _ = db.Close() - }() - - require.NoError(t, db.Ping()) - require.NoError(t, storage.Migrate(context.Background(), db)) - - // Cleanup tables - require.NoError(t, db.ResetModel(context.TODO(), (*webhooks.Config)(nil))) - require.NoError(t, db.ResetModel(context.TODO(), (*webhooks.Attempt)(nil))) - - store, err := postgres.NewStore(db) - require.NoError(t, err) - t.Cleanup(func() { - _ = store.Close(context.Background()) - }) - - cfgs, err := store.FindManyConfigs(context.Background(), map[string]any{}) - require.NoError(t, err) - require.Equal(t, 0, len(cfgs)) - - ids, err := store.FindWebhookIDsToRetry(context.Background()) - require.NoError(t, err) - require.Equal(t, 0, len(ids)) - - atts, err := store.FindAttemptsToRetryByWebhookID(context.Background(), "") - require.NoError(t, err) - require.Equal(t, 0, len(atts)) -} diff --git a/ee/webhooks/pkg/storage/store.go b/ee/webhooks/pkg/storage/store.go deleted file mode 100644 index 649c490ef4..0000000000 --- a/ee/webhooks/pkg/storage/store.go +++ /dev/null @@ -1,28 +0,0 @@ -package storage - -import ( - "context" - - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/pkg/errors" -) - -var ( - ErrConfigNotFound = errors.New("config not found") - ErrConfigNotModified = errors.New("config not modified") - ErrWebhookIDNotFound = errors.New("webhook ID not found") - ErrAttemptsNotModified = errors.New("attempt not modified") -) - -type Store interface { - FindManyConfigs(ctx context.Context, filter map[string]any) ([]webhooks.Config, error) - InsertOneConfig(ctx context.Context, cfg webhooks.ConfigUser) (webhooks.Config, error) - DeleteOneConfig(ctx context.Context, id string) error - UpdateOneConfigActivation(ctx context.Context, id string, active bool) (webhooks.Config, error) - UpdateOneConfigSecret(ctx context.Context, id, secret string) (webhooks.Config, error) - FindAttemptsToRetryByWebhookID(ctx context.Context, webhookID string) ([]webhooks.Attempt, error) - FindWebhookIDsToRetry(ctx context.Context) (webhookIDs []string, err error) - UpdateAttemptsStatus(ctx context.Context, webhookID string, status string) ([]webhooks.Attempt, error) - InsertOneAttempt(ctx context.Context, att webhooks.Attempt) error - Close(ctx context.Context) error -} diff --git a/ee/webhooks/pkg/worker/handler.go b/ee/webhooks/pkg/worker/handler.go deleted file mode 100644 index 920227bdd7..0000000000 --- a/ee/webhooks/pkg/worker/handler.go +++ /dev/null @@ -1,27 +0,0 @@ -package worker - -import ( - "net/http" - - "github.com/go-chi/chi/v5" - - "github.com/formancehq/go-libs/service" - - "github.com/formancehq/go-libs/logging" -) - -const ( - PathHealthCheck = "/_healthcheck" -) - -func NewWorkerHandler(debug bool) http.Handler { - h := chi.NewRouter() - h.Use(service.OTLPMiddleware("webhooks", debug)) - h.Get(PathHealthCheck, healthCheckHandle) - - return h -} - -func healthCheckHandle(_ http.ResponseWriter, r *http.Request) { - logging.FromContext(r.Context()).Infof("health check OK") -} diff --git a/ee/webhooks/pkg/worker/module.go b/ee/webhooks/pkg/worker/module.go deleted file mode 100644 index 38c8da4704..0000000000 --- a/ee/webhooks/pkg/worker/module.go +++ /dev/null @@ -1,164 +0,0 @@ -package worker - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "os" - "strings" - "time" - - "github.com/spf13/cobra" - - "github.com/formancehq/go-libs/contextutil" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - - "github.com/ThreeDotsLabs/watermill/message" - "github.com/alitto/pond" - "github.com/formancehq/go-libs/logging" - "github.com/formancehq/go-libs/publish" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/google/uuid" - "go.uber.org/fx" -) - -var Tracer = otel.Tracer("listener") - -func StartModule(cmd *cobra.Command, retriesCron time.Duration, retryPolicy webhooks.BackoffPolicy, debug bool, topics []string) fx.Option { - var options []fx.Option - - options = append(options, fx.Invoke(func(r *message.Router, subscriber message.Subscriber, store storage.Store, httpClient *http.Client) { - configureMessageRouter(r, subscriber, topics, store, httpClient, retryPolicy, pond.New(50, 50)) - })) - options = append(options, publish.FXModuleFromFlags(cmd, debug)) - options = append(options, fx.Provide( - func() (time.Duration, webhooks.BackoffPolicy) { - return retriesCron, retryPolicy - }, - NewRetrier, - )) - options = append(options, fx.Invoke(run)) - - logging.Debugf("starting worker with env:") - for _, e := range os.Environ() { - logging.Debugf("%s", e) - } - - return fx.Options(options...) -} - -func run(lc fx.Lifecycle, w *Retrier) { - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - logging.FromContext(ctx).Debugf("starting worker...") - go func() { - if err := w.Run(context.Background()); err != nil { - logging.FromContext(ctx).Errorf("kafka.Retrier.Run: %s", err) - } - }() - return nil - }, - OnStop: func(ctx context.Context) error { - logging.FromContext(ctx).Debugf("stopping worker...") - w.Stop(ctx) - - if err := w.store.Close(ctx); err != nil { - return fmt.Errorf("storage.Store.Close: %w", err) - } - return nil - }, - }) -} - -func configureMessageRouter(r *message.Router, subscriber message.Subscriber, topics []string, - store storage.Store, httpClient *http.Client, retryPolicy webhooks.BackoffPolicy, pool *pond.WorkerPool, -) { - for _, topic := range topics { - r.AddNoPublisherHandler(fmt.Sprintf("messages-%s", topic), topic, subscriber, processMessages(store, httpClient, retryPolicy, pool)) - } -} - -func processMessages(store storage.Store, httpClient *http.Client, retryPolicy webhooks.BackoffPolicy, pool *pond.WorkerPool) func(msg *message.Message) error { - return func(msg *message.Message) error { - pool.Submit(func() { - - var ev *publish.EventMessage - span, ev, err := publish.UnmarshalMessage(msg) - if err != nil { - logging.FromContext(msg.Context()).Error(err.Error()) - return - } - - ctx, span := Tracer.Start(msg.Context(), "HandleEvent", - trace.WithLinks(trace.Link{ - SpanContext: span.SpanContext(), - }), - trace.WithAttributes( - attribute.String("event-id", msg.UUID), - attribute.Bool("duplicate", false), - attribute.String("event-type", ev.Type), - attribute.String("event-payload", string(msg.Payload)), - ), - ) - defer span.End() - defer func() { - if err != nil { - span.RecordError(err) - } - }() - ctx, _ = contextutil.Detached(ctx) - - eventApp := strings.ToLower(ev.App) - eventType := strings.ToLower(ev.Type) - - if eventApp == "" { - ev.Type = eventType - } else { - ev.Type = strings.Join([]string{eventApp, eventType}, ".") - } - - filter := map[string]any{ - "event_types": ev.Type, - "active": true, - } - logging.FromContext(ctx).Debugf("searching configs with event types: %+v", ev.Type) - cfgs, err := store.FindManyConfigs(ctx, filter) - if err != nil { - logging.FromContext(ctx).Error(err) - return - } - - for _, cfg := range cfgs { - logging.FromContext(ctx).Debugf("found one config: %+v", cfg) - data, err := json.Marshal(ev) - if err != nil { - logging.FromContext(ctx).Error(err) - return - } - - attempt, err := webhooks.MakeAttempt(ctx, httpClient, retryPolicy, uuid.NewString(), - uuid.NewString(), 0, cfg, data, false) - if err != nil { - logging.FromContext(ctx).Error(err) - return - } - - if attempt.Status == webhooks.StatusAttemptSuccess { - logging.FromContext(ctx).Debugf( - "webhook sent with ID %s to %s of type %s", - attempt.WebhookID, cfg.Endpoint, ev.Type) - } - - if err := store.InsertOneAttempt(ctx, attempt); err != nil { - logging.FromContext(ctx).Error(err) - return - } - } - }) - return nil - } -} diff --git a/ee/webhooks/pkg/worker/worker.go b/ee/webhooks/pkg/worker/worker.go deleted file mode 100644 index 4c102760e5..0000000000 --- a/ee/webhooks/pkg/worker/worker.go +++ /dev/null @@ -1,131 +0,0 @@ -package worker - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/formancehq/go-libs/logging" - webhooks "github.com/formancehq/webhooks/pkg" - "github.com/formancehq/webhooks/pkg/storage" - "github.com/google/uuid" - "github.com/pkg/errors" -) - -type Retrier struct { - httpClient *http.Client - store storage.Store - - retriesCron time.Duration - retryPolicy webhooks.BackoffPolicy - - stopChan chan chan struct{} -} - -func NewRetrier(store storage.Store, httpClient *http.Client, retriesCron time.Duration, retryPolicy webhooks.BackoffPolicy) (*Retrier, error) { - return &Retrier{ - httpClient: httpClient, - store: store, - retriesCron: retriesCron, - retryPolicy: retryPolicy, - stopChan: make(chan chan struct{}), - }, nil -} - -func (w *Retrier) Run(ctx context.Context) error { - errChan := make(chan error) - ctxWithCancel, cancel := context.WithCancel(ctx) - defer cancel() - - go w.attemptRetries(ctxWithCancel, errChan) - - for { - select { - case ch := <-w.stopChan: - logging.FromContext(ctx).Debug("worker: received from stopChan") - close(ch) - return nil - case <-ctx.Done(): - logging.FromContext(ctx).Debugf("worker: context done: %s", ctx.Err()) - return nil - case err := <-errChan: - return errors.Wrap(err, "kafka.Retrier") - } - } -} - -func (w *Retrier) Stop(ctx context.Context) { - ch := make(chan struct{}) - select { - case <-ctx.Done(): - logging.FromContext(ctx).Debugf("worker stopped: context done: %s", ctx.Err()) - return - case w.stopChan <- ch: - select { - case <-ctx.Done(): - logging.FromContext(ctx).Debugf("worker stopped via stopChan: context done: %s", ctx.Err()) - return - case <-ch: - logging.FromContext(ctx).Debug("worker stopped via stopChan") - } - default: - logging.FromContext(ctx).Debug("trying to stop worker: no communication") - } -} - -var ErrNoAttemptsFound = errors.New("attemptRetries: no attempts found") - -func (w *Retrier) attemptRetries(ctx context.Context, errChan chan error) { - for { - select { - case <-ctx.Done(): - return - default: - // Find all webhookIDs ready to be retried - webhookIDs, err := w.store.FindWebhookIDsToRetry(ctx) - if err != nil { - errChan <- errors.Wrap(err, "storage.Store.FindWebhookIDsToRetry") - continue - } else { - logging.FromContext(ctx).Debugf( - "found %d distinct webhookIDs to retry: %+v", len(webhookIDs), webhookIDs) - } - - for _, webhookID := range webhookIDs { - atts, err := w.store.FindAttemptsToRetryByWebhookID(ctx, webhookID) - if err != nil { - errChan <- errors.Wrap(err, "storage.Store.FindAttemptsToRetryByWebhookID") - continue - } - if len(atts) == 0 { - errChan <- fmt.Errorf("%w for webhookID: %s", ErrNoAttemptsFound, webhookID) - continue - } - - newAttemptNb := atts[0].RetryAttempt + 1 - attempt, err := webhooks.MakeAttempt(ctx, w.httpClient, w.retryPolicy, uuid.NewString(), - webhookID, newAttemptNb, atts[0].Config, []byte(atts[0].Payload), false) - if err != nil { - errChan <- errors.Wrap(err, "webhooks.MakeAttempt") - continue - } - - if err := w.store.InsertOneAttempt(ctx, attempt); err != nil { - errChan <- errors.Wrap(err, "storage.Store.InsertOneAttempt retried") - continue - } - - if _, err := w.store.UpdateAttemptsStatus(ctx, webhookID, attempt.Status); err != nil { - if errors.Is(err, storage.ErrAttemptsNotModified) { - continue - } - errChan <- errors.Wrap(err, "storage.Store.UpdateAttemptsStatus") - continue - } - } - } - - time.Sleep(w.retriesCron) - } -} diff --git a/ee/webhooks/scratch.Dockerfile b/ee/webhooks/scratch.Dockerfile deleted file mode 100644 index 10d5c8c81a..0000000000 --- a/ee/webhooks/scratch.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/formancehq/base:scratch -COPY webhooks /usr/bin/webhooks -ENV OTEL_SERVICE_NAME webhooks -ENTRYPOINT ["/usr/bin/webhooks"] -CMD ["server"] diff --git a/tests/integration/Earthfile b/tests/integration/Earthfile index e599183bdb..67c0263adb 100644 --- a/tests/integration/Earthfile +++ b/tests/integration/Earthfile @@ -1,6 +1,13 @@ VERSION 0.8 IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core +IMPORT github.com/formancehq/ledger:main AS ledger +IMPORT github.com/formancehq/payments:main AS payments +IMPORT github.com/formancehq/gateway:main AS gateway +IMPORT github.com/formancehq/auth:main AS auth +IMPORT github.com/formancehq/search:main AS search +IMPORT github.com/formancehq/stargate:main AS stargate +IMPORT github.com/formancehq/webhooks:main AS webhooks IMPORT ../.. AS stack IMPORT ../../releases AS releases @@ -29,9 +36,13 @@ tests: COPY --pass-args (stack+sources/out --LOCATION=go.mod) /src/go.mod COPY --pass-args (stack+sources/out --LOCATION=go.sum) /src/go.sum COPY --pass-args (stack+sources/out --LOCATION=libs) /src/libs - COPY --pass-args (stack+sources/out --LOCATION=components/ledger) /src/components/ledger - COPY --pass-args (stack+sources/out --LOCATION=components/payments) /src/components/payments + COPY --pass-args (ledger+sources/src) /src/components/ledger + COPY --pass-args (payments+sources/src) /src/components/payments COPY --pass-args (stack+sources/out --LOCATION=ee) /src/ee + COPY --pass-args (gateway+sources/src) /src/ee/gateway/ + COPY --pass-args (auth+sources/src) /src/ee/auth/ + COPY --pass-args (search+sources/src) /src/ee/search/ + COPY --pass-args (webhooks+sources/src) /src/ee/webhooks/ COPY --pass-args (stack+build-final-spec/latest.json) /src/releases/build/latest.json COPY --pass-args (releases+sdk-generate/go) /src/releases/sdks/go COPY . /src/tests/integration @@ -64,9 +75,13 @@ sources: WORKDIR src COPY --pass-args (stack+sources/out --LOCATION=libs) /src/libs COPY --pass-args (releases+sdk-generate/go) /src/releases/sdks/go - COPY --pass-args (stack+sources/out --LOCATION=components/ledger) /src/components/ledger - COPY --pass-args (stack+sources/out --LOCATION=components/payments) /src/components/payments + COPY --pass-args (ledger+sources/src) /src/components/ledger + COPY --pass-args (payments+sources/src) /src/components/payments COPY --pass-args (stack+sources/out --LOCATION=ee) /src/ee + COPY --pass-args (gateway+sources/src) /src/ee/gateway/ + COPY --pass-args (auth+sources/src) /src/ee/auth/ + COPY --pass-args (search+sources/src) /src/ee/search/ + COPY --pass-args (webhooks+sources/src) /src/ee/webhooks/ COPY --pass-args (stack+sources/out --LOCATION=go.mod) /src/go.mod COPY --pass-args (stack+sources/out --LOCATION=go.sum) /src/go.sum COPY . /src/tests/integration diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 8c9c4ce672..64ceccd3d6 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -271,16 +271,16 @@ require ( ) replace ( - github.com/formancehq/auth => ../../ee/auth + github.com/formancehq/auth => github.com/formancehq/auth v0.0.0-20240925174204-6f5cb5948775 github.com/formancehq/formance-sdk-go/v2 => ../../releases/sdks/go - github.com/formancehq/ledger => ../../components/ledger + github.com/formancehq/ledger => github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02 github.com/formancehq/orchestration => ../../ee/orchestration - github.com/formancehq/payments => ../../components/payments - github.com/formancehq/payments/genericclient => ../../components/payments/cmd/connectors/internal/connectors/generic/client/generated + github.com/formancehq/payments => github.com/formancehq/payments v0.0.0-20240925171236-82f077b2e178 + github.com/formancehq/payments/genericclient => github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client/generated v0.0.0-20240925171236-82f077b2e178 github.com/formancehq/reconciliation => ../../ee/reconciliation - github.com/formancehq/search => ../../ee/search + github.com/formancehq/search => github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd github.com/formancehq/stack/libs/events => ../../libs/events github.com/formancehq/wallets => ../../ee/wallets - github.com/formancehq/webhooks => ../../ee/webhooks + github.com/formancehq/webhooks => github.com/formancehq/webhooks v0.0.0-20240925174047-7d4341ccfc5f github.com/zitadel/oidc => github.com/formancehq/oidc v0.0.0-20220923202448-e2960a99b71c ) diff --git a/tests/integration/go.sum b/tests/integration/go.sum index 754ef699b0..226983708e 100644 --- a/tests/integration/go.sum +++ b/tests/integration/go.sum @@ -788,8 +788,20 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/formancehq/auth v0.0.0-20240925174204-6f5cb5948775 h1:i4kSl3M+DSlIb2t/HEcnjT5cg3rJuk5XDSkB97kpQes= +github.com/formancehq/auth v0.0.0-20240925174204-6f5cb5948775/go.mod h1:sKfqRI3+6IZ0X+wrKRM0jLRRE2YyaMXGLm1iBBSlYn4= github.com/formancehq/go-libs v1.7.1 h1:9D5cxKWFlVtdX5AYDXeUz1Nb9PdoEfQX0f/yeLsU324= github.com/formancehq/go-libs v1.7.1/go.mod h1:pWTScpoyieF7OoJ6WVmXNG9NhDjbZbAmFqd7UOw85iI= +github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02 h1:0jB2JrN653A25DxUh4THjujWFZseMQqg5VMwGQxv7gc= +github.com/formancehq/ledger v0.0.0-20240925161848-3cf78b93df02/go.mod h1:sGscj1S3S2ndAzOPVFQFBuC72YF8t+OAm/hcqDcxpxI= +github.com/formancehq/payments v0.0.0-20240925171236-82f077b2e178 h1:hDGGwY8D0/RvoxIxxdGrGP1b/J7DP7aoZlC+vV1/NPU= +github.com/formancehq/payments v0.0.0-20240925171236-82f077b2e178/go.mod h1:U4BNKrXyhCtk6pkZVNJjaLFkg5B9RQk3RiQq9gt18CY= +github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client/generated v0.0.0-20240925171236-82f077b2e178 h1:2bb1Zg4XtgrRDhqR0g/lSnnfyrgjh2FrqNh2UK4LpCs= +github.com/formancehq/payments/cmd/connectors/internal/connectors/generic/client/generated v0.0.0-20240925171236-82f077b2e178/go.mod h1:Xsj5yVWO0e2XZx45RjL93JbPa+78CzNoSRlITuDA97c= +github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd h1:zsv//syEUoJWo2VswR9vXvrbDAAoxm8rLXkCxYl114s= +github.com/formancehq/search v0.0.0-20240925174151-fe1cfd5a69cd/go.mod h1:vmUx4wanfOqGBZk0t2bhAXx+r+Pp9WaIA1UQQNc9Zds= +github.com/formancehq/webhooks v0.0.0-20240925174047-7d4341ccfc5f h1:hOm4zNG8Z/RH4TlISdGx3AzHvtFPtMuaV1tx4bvKIho= +github.com/formancehq/webhooks v0.0.0-20240925174047-7d4341ccfc5f/go.mod h1:ILmAle/plVuBCFjUKZtI3wrAFcyzbHbuKbXyrd/VtWA= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=