diff --git a/.github/workflows/packages-manager.yml b/.github/workflows/packages-manager.yml
index 012c57b6..71cc8e07 100644
--- a/.github/workflows/packages-manager.yml
+++ b/.github/workflows/packages-manager.yml
@@ -61,33 +61,13 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- - name: Set up Chocolatey
- run: |
- Set-ExecutionPolicy Bypass -Scope Process -Force
- [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
- Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression
- shell: pwsh
-
- - name: Update nuspec version
- run: |
- $nuspecPath = Resolve-Path .\chocolatey\mdz.nuspec
-
- if (-Not (Test-Path $nuspecPath)) {
- Write-Error "The nuspec file was not found at $nuspecPath"
- exit 1
- }
-
- Write-Host "Updating nuspec version to ${{ needs.get_branch.outputs.version }}"
- (Get-Content $nuspecPath) -replace '.*', "${{ needs.get_branch.outputs.version }}" | Set-Content $nuspecPath
- shell: pwsh
-
- name: Download and extract ZIP
run: |
$toolsDir = "$(Resolve-Path .\chocolatey\tools)"
New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
$zipFile = Join-Path $toolsDir 'mdz.zip'
$outputFile = Join-Path $toolsDir 'mdz.exe'
-
+
$url = "https://github.com/LerianStudio/midaz/releases/download/v${{ needs.get_branch.outputs.version }}/midaz_${{ needs.get_branch.outputs.version }}_windows_amd64.zip"
Write-Host "Downloading ZIP from $url to $zipFile"
Invoke-WebRequest -Uri $url -OutFile $zipFile
@@ -96,20 +76,26 @@ jobs:
Expand-Archive -Path $zipFile -DestinationPath $toolsDir -Force
shell: pwsh
- - name: Calculate checksum
- id: calculate-checksum
+ - name: Prepare
run: |
- $outputFile = "$(Resolve-Path .\chocolatey\tools\mdz.exe)"
- $checksum = (Get-FileHash -Path $outputFile -Algorithm SHA256).Hash
+ # Checksum Calculate
+ $zipFile = "$(Resolve-Path .\chocolatey\tools\mdz.zip)"
+ $checksum = (Get-FileHash -Path $zipFile -Algorithm SHA256).Hash
- echo "checksum=$checksum" >> $GITHUB_OUTPUT
- shell: pwsh
+ Remove-Item $zipFile
- - name: Replace checksum in chocolateyinstall.ps1
- run: |
- (Get-Content .\chocolatey\tools\chocolateyinstall.ps1) `
- -replace '{{CHECKSUM}}', '${{ steps.calculate-checksum.outputs.checksum }}' `
- | Set-Content .\chocolatey\tools\chocolateyinstall.ps1
+ Write-Host "Updating nuspec version to ${{ needs.get_branch.outputs.version }}"
+ $nuspecPath = Resolve-Path .\chocolatey\mdz.nuspec
+ (Get-Content $nuspecPath) -replace '.*', "${{ needs.get_branch.outputs.version }}" | Set-Content $nuspecPath
+
+ Write-Host "Updating Checksum files $checksum"
+ $chocoInstallPath = Resolve-Path .\chocolatey\tools\chocolateyinstall.ps1
+ (Get-Content $chocoInstallPath) -replace '{{CHECKSUM}}', "$checksum" | Set-Content $chocoInstallPath
+
+ $verificationPath = Resolve-Path .\chocolatey\tools\VERIFICATION.txt
+ (Get-Content $verificationPath) -replace '{{CHECKSUM}}', "$checksum" | Set-Content $verificationPath
+
+ (Get-Content $verificationPath) -replace '{{VERSION}}', "${{ needs.get_branch.outputs.version }}" -replace '{{VERSION}}', "${{ needs.get_branch.outputs.version }}" | Set-Content $verificationPath
shell: pwsh
- name: Publish Chocolatey package
@@ -118,12 +104,11 @@ jobs:
shell: pwsh
run: |
choco pack chocolatey/mdz.nuspec
- ls
# install local test
choco install mdz --version=${{ needs.get_branch.outputs.version }} --prerelease --source="D:\a\midaz\midaz"
mdz version
-
+
choco apikey add -s="https://push.chocolatey.org/" -k="$env:CHOCO_API_KEY"
choco push mdz.${{ needs.get_branch.outputs.version }}.nupkg --source https://push.chocolatey.org/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03e209aa..29f4d97f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,60 @@
+## [1.45.0-beta.3](https://github.com/LerianStudio/midaz/compare/v1.45.0-beta.2...v1.45.0-beta.3) (2025-01-17)
+
+
+### Features
+
+* add api on postman; adjust lint; generate swagger and open api; :sparkles: ([095f2d6](https://github.com/LerianStudio/midaz/commit/095f2d6942e7d8ab6be0822e66509d6d65392a10))
+* add transaction body on database; :sparkles: ([d5b6197](https://github.com/LerianStudio/midaz/commit/d5b619788782563d2c336fccaa01f4584ac54e57))
+* final adjusts to rever transaction :sparkles: ([44b650c](https://github.com/LerianStudio/midaz/commit/44b650cb66cf14773119b7b94c790fa61a7e6231))
+* new implementatios; :sparkles: ([a8f5a6d](https://github.com/LerianStudio/midaz/commit/a8f5a6deb065858eb90a3a9b74c641ecc304e4f5))
+
+
+### Bug Fixes
+
+* add revert logic to object :bug: ([e0d36ad](https://github.com/LerianStudio/midaz/commit/e0d36ad33bb4962d4a7b4fc6646f5750e842a8f8))
+
+## [1.45.0-beta.2](https://github.com/LerianStudio/midaz/compare/v1.45.0-beta.1...v1.45.0-beta.2) (2025-01-15)
+
+
+### Features
+
+* add go routines to update; some postgres configs :sparkles: ([25fdb70](https://github.com/LerianStudio/midaz/commit/25fdb706ef65ec550172bb7f6d47652eb8f944f5))
+* add logger :sparkles: ([6ff156d](https://github.com/LerianStudio/midaz/commit/6ff156d268d5647f19c9bcae394a5e788fddac4b))
+* add magic numbers to constant :sparkles: ([acc57a3](https://github.com/LerianStudio/midaz/commit/acc57a3fc91ebbe4d4a7492bbd4bd49d23945e78))
+* add optimistic lock on database using version to control race condition; ([3f37ade](https://github.com/LerianStudio/midaz/commit/3f37adeb3592531aa64612915066998defa00c06))
+* adjust time :sparkles: ([60da1cf](https://github.com/LerianStudio/midaz/commit/60da1cf7dce25469c87bf5786aa6b603fbe79638))
+* create race condition using gorotine and chanel ([3248ee7](https://github.com/LerianStudio/midaz/commit/3248ee7e8fca50dc8882327e94f4c9fcbfd3529e))
+* new race condition implementation ([6bb89dd](https://github.com/LerianStudio/midaz/commit/6bb89dd46e163b057cb4c7c32cdd8e3a8c418147))
+* new updates to avoid race condition ([97448dc](https://github.com/LerianStudio/midaz/commit/97448dc69d6bcfafe66bbd94d81cff8b4733da3e))
+* update time lock :sparkles: ([beb4921](https://github.com/LerianStudio/midaz/commit/beb49216d8e7c7ddcc76a841c9c454304abd0e62))
+
+
+### Bug Fixes
+
+* add defer rollback :bug: ([0cdabd1](https://github.com/LerianStudio/midaz/commit/0cdabd13e58d91d6f86170c70baf4d602690bd16))
+* add rollback in case of error to unlock database; :bug: ([66d7416](https://github.com/LerianStudio/midaz/commit/66d74168b2da61b5fc74662ff7150403fb624b36))
+* add unlock :bug: ([0c62a31](https://github.com/LerianStudio/midaz/commit/0c62a314e4ad86918b6955ba3792ce2017102c8e))
+* adjust to remove lock of get accounts :bug: ([1ddf09f](https://github.com/LerianStudio/midaz/commit/1ddf09f939da41d4ebabfd339b00d7caf9dc29f6))
+* change place :bug: ([3970a04](https://github.com/LerianStudio/midaz/commit/3970a04dd1ac5157081815726766387434ad0b66))
+* improve idempotency using setnx; :bug: ([5a7988e](https://github.com/LerianStudio/midaz/commit/5a7988e161e0bb64a64149c1871ba2f0c9f2dbd5))
+* lint; add version; :bug: ([a7df566](https://github.com/LerianStudio/midaz/commit/a7df566659892e15eddc0486087d93a74cf707d4))
+* make lint :bug: ([ff3a8ad](https://github.com/LerianStudio/midaz/commit/ff3a8ad792b1b592fb3faa93bcd86dc4f45a1572))
+* merge with develop :bug: ([a04a8ee](https://github.com/LerianStudio/midaz/commit/a04a8eebeda62ff6f1812606c778aa5bcbf15041))
+* reduce lock time :bug: ([ddb6a60](https://github.com/LerianStudio/midaz/commit/ddb6a60ca16b3560d6b8c0802e12fb9a246894bf))
+* unlock specify by get accounts :bug: ([26af469](https://github.com/LerianStudio/midaz/commit/26af4697aff95095a98681af98a3c0658a60c75b))
+* update go mod dependabot :bug: ([f776fcc](https://github.com/LerianStudio/midaz/commit/f776fcc678cbbb652dfb01ff6ab7890b6ac85777))
+* updates to improve race condition :bug: ([022c3c9](https://github.com/LerianStudio/midaz/commit/022c3c90f6827149bc8ba4e78b2acb314895bbc8))
+
+## [1.45.0-beta.1](https://github.com/LerianStudio/midaz/compare/v1.44.1-beta.1...v1.45.0-beta.1) (2025-01-09)
+
+
+### Features
+
+* added router find account by alias :sparkles: ([a2e8c99](https://github.com/LerianStudio/midaz/commit/a2e8c99ec816149c23beb5962a600ca7d7bb0328))
+* push choco :sparkles: ([4d19380](https://github.com/LerianStudio/midaz/commit/4d19380685d0bad020ed4f0b67e73dcac372876e))
+
+## [1.44.1-beta.1](https://github.com/LerianStudio/midaz/compare/v1.44.0...v1.44.1-beta.1) (2025-01-08)
+
## [1.44.0](https://github.com/LerianStudio/midaz/compare/v1.43.0...v1.44.0) (2025-01-08)
diff --git a/chocolatey/mdz.nuspec b/chocolatey/mdz.nuspec
index b99cdb2c..d2bd796f 100644
--- a/chocolatey/mdz.nuspec
+++ b/chocolatey/mdz.nuspec
@@ -2,12 +2,16 @@
mdz
- 2.0.0
+ 1.0.0
mdz
Lerian Studio
https://github.com/LerianStudio/midaz
https://github.com/LerianStudio/midaz/blob/main/LICENSE
https://avatars.githubusercontent.com/u/148895005
+ https://docs.lerian.studio/docs/midaz-cli
+ https://discord.gg/DnhqKwkGv3
+ https://github.com/LerianStudio/midaz/issues
+ https://github.com/LerianStudio/midaz
mdz cli ledger golang financial
An open-source ledger for multi-asset, multi-currency financial systems.
diff --git a/chocolatey/tools/LICENSE.txt b/chocolatey/tools/LICENSE.txt
new file mode 100644
index 00000000..fdc5bdb0
--- /dev/null
+++ b/chocolatey/tools/LICENSE.txt
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2024 Leriand LTDA
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/chocolatey/tools/VERIFICATION.txt b/chocolatey/tools/VERIFICATION.txt
index 89dbbf15..571e8fd3 100644
--- a/chocolatey/tools/VERIFICATION.txt
+++ b/chocolatey/tools/VERIFICATION.txt
@@ -1,5 +1,4 @@
-# VERIFICATION.txt
-
+VERIFICATION
The purpose of the verification is to help moderators and the Chocolatey community to
verify that the contents of this package can be trusted.
@@ -8,3 +7,10 @@ identical to those on the GitHub release page for the windows_amd64 target.
The binaries included in this package were downloaded directly from the official GitHub release page:
https://github.com/LerianStudio/midaz/releases
+
+Download the zipped application `windows_amd64.zip` from the github release tab
+
+Inside the zip is an mdz.exe file
+Downloaded from: https://github.com/LerianStudio/midaz/releases/download/v{{VERSION}}/midaz_{{VERSION}}_windows_amd64.zip
+
+It's hash should match SHA256: "{{CHECKSUM}}"
diff --git a/chocolatey/tools/chocolateyinstall.ps1 b/chocolatey/tools/chocolateyinstall.ps1
index 87c1c327..d4875150 100644
--- a/chocolatey/tools/chocolateyinstall.ps1
+++ b/chocolatey/tools/chocolateyinstall.ps1
@@ -1,4 +1,4 @@
-$version = 'v1.44.0'
+$version = 'v1.45.0'
$ErrorActionPreference = 'Stop';
@@ -8,12 +8,12 @@ $outputFile = Join-Path $toolsDir 'mdz.exe'
$versionFmt = $version -replace '^v', ''
-# URL do arquivo zipado
+# Zipped file URL
$url = "https://github.com/LerianStudio/midaz/releases/download/"+$version+"/midaz_"+$versionFmt+"_windows_amd64.zip"
$checksum = '{{CHECKSUM}}'
$silentArgs = ''
-# Argumentos do pacote
+# Package arguments
$packageArgs = @{
packageName = 'mdz'
unzipLocation = $toolsDir
@@ -23,22 +23,22 @@ $packageArgs = @{
checksumType = 'sha256'
}
-# Instalar e descompactar o pacote
+# Install and unzip the package
Install-ChocolateyZipPackage @packageArgs
-# Verificar se o arquivo .exe foi extraído corretamente
+# Check that the .exe file has been extracted correctly
if (-Not (Test-Path $outputFile)) {
- throw "O arquivo mdz.exe não foi encontrado após a extração do zip."
+ throw "The file mdz.exe was not found after extracting the zip."
}
-# Certificar-se de que o diretório global 'bin' existe
+# Make sure the global directory 'bin' exists
if (-Not (Test-Path $binDir)) {
New-Item -ItemType Directory -Path $binDir | Out-Null
}
-# Mover o executável para o diretório global
-Write-Host "Copiando $outputFile para $binDir"
+# Move the executable to the global directory
+Write-Host "Copying $outputFile to $binDir"
Copy-Item -Path $outputFile -Destination $binDir -Force
-# Confirmar a instalação
-Write-Host "Instalação completa. O executável mdz está disponível globalmente."
+# Confirm installation
+Write-Host "Installation complete. The mdz executable is available globally."
diff --git a/components/audit/.env.example b/components/audit/.env.example
index e487209f..c74246e8 100644
--- a/components/audit/.env.example
+++ b/components/audit/.env.example
@@ -2,7 +2,7 @@
# ENV_NAME=production
# APP
-VERSION=v1.44.0
+VERSION=v1.45.0
SERVER_PORT=3005
SERVER_ADDRESS=:${SERVER_PORT}
diff --git a/components/ledger/.env.example b/components/ledger/.env.example
index 3149fb88..bb0b58c2 100644
--- a/components/ledger/.env.example
+++ b/components/ledger/.env.example
@@ -4,7 +4,7 @@
# ENV_NAME=production
# APP
-VERSION=v1.44.0
+VERSION=v1.45.0
SERVER_PORT=3000
SERVER_ADDRESS=:${SERVER_PORT}
diff --git a/components/ledger/Makefile b/components/ledger/Makefile
index 757e7169..faebdd87 100644
--- a/components/ledger/Makefile
+++ b/components/ledger/Makefile
@@ -95,7 +95,7 @@ ps:
# App Commands
.PHONY: grpc-ledger-gen
grpc-ledger-gen:
- @protoc --proto_path=../../common/mgrpc --go-grpc_out=../../common/mgrpc --go_out=../../common/mgrpc ../../common/mgrpc/account/account.proto
+ @protoc --proto_path=../../pkg/mgrpc --go-grpc_out=../../pkg/mgrpc --go_out=../../pkg/mgrpc ../../pkg/mgrpc/account/account.proto
.PHONY: run
run:
diff --git a/components/ledger/api/docs.go b/components/ledger/api/docs.go
index 84168ed5..20d5b66e 100644
--- a/components/ledger/api/docs.go
+++ b/components/ledger/api/docs.go
@@ -680,12 +680,6 @@ const docTemplate = `{
"description": "Sort Order",
"name": "sort_order",
"in": "query"
- },
- {
- "type": "string",
- "description": "Find alias",
- "name": "alias",
- "in": "query"
}
],
"responses": {
@@ -778,6 +772,62 @@ const docTemplate = `{
}
}
},
+ "/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{alias}": {
+ "get": {
+ "description": "Get an Account with the input Alias",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Accounts"
+ ],
+ "summary": "Get an Account by Alias",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Authorization Bearer Token",
+ "name": "Authorization",
+ "in": "header",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Request ID",
+ "name": "Midaz-Id",
+ "in": "header"
+ },
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organization_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Ledger ID",
+ "name": "ledger_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Alias",
+ "name": "alias",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/Account"
+ }
+ }
+ }
+ }
+ },
"/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{id}": {
"get": {
"description": "Get an Account with the input ID",
diff --git a/components/ledger/api/openapi.yaml b/components/ledger/api/openapi.yaml
index 7b89d7a1..4497b395 100644
--- a/components/ledger/api/openapi.yaml
+++ b/components/ledger/api/openapi.yaml
@@ -482,11 +482,6 @@ paths:
- asc
- desc
type: string
- - description: Find alias
- in: query
- name: alias
- schema:
- type: string
responses:
"200":
content:
@@ -541,6 +536,49 @@ paths:
tags:
- Accounts
x-codegen-request-body-name: account
+ /v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{alias}:
+ get:
+ description: Get an Account with the input Alias
+ parameters:
+ - description: Authorization Bearer Token
+ in: header
+ name: Authorization
+ required: true
+ schema:
+ type: string
+ - description: Request ID
+ in: header
+ name: Midaz-Id
+ schema:
+ type: string
+ - description: Organization ID
+ in: path
+ name: organization_id
+ required: true
+ schema:
+ type: string
+ - description: Ledger ID
+ in: path
+ name: ledger_id
+ required: true
+ schema:
+ type: string
+ - description: Alias
+ in: path
+ name: alias
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Account'
+ description: OK
+ summary: Get an Account by Alias
+ tags:
+ - Accounts
/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{id}:
delete:
description: Delete an Account with the input ID
diff --git a/components/ledger/api/swagger.json b/components/ledger/api/swagger.json
index 1bebc019..c6c18182 100644
--- a/components/ledger/api/swagger.json
+++ b/components/ledger/api/swagger.json
@@ -674,12 +674,6 @@
"description": "Sort Order",
"name": "sort_order",
"in": "query"
- },
- {
- "type": "string",
- "description": "Find alias",
- "name": "alias",
- "in": "query"
}
],
"responses": {
@@ -772,6 +766,62 @@
}
}
},
+ "/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{alias}": {
+ "get": {
+ "description": "Get an Account with the input Alias",
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Accounts"
+ ],
+ "summary": "Get an Account by Alias",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Authorization Bearer Token",
+ "name": "Authorization",
+ "in": "header",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Request ID",
+ "name": "Midaz-Id",
+ "in": "header"
+ },
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organization_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Ledger ID",
+ "name": "ledger_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Alias",
+ "name": "alias",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/Account"
+ }
+ }
+ }
+ }
+ },
"/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{id}": {
"get": {
"description": "Get an Account with the input ID",
diff --git a/components/ledger/api/swagger.yaml b/components/ledger/api/swagger.yaml
index 48d5818d..06e16ce5 100644
--- a/components/ledger/api/swagger.yaml
+++ b/components/ledger/api/swagger.yaml
@@ -986,10 +986,6 @@ paths:
in: query
name: sort_order
type: string
- - description: Find alias
- in: query
- name: alias
- type: string
produces:
- application/json
responses:
@@ -1051,6 +1047,44 @@ paths:
summary: Create an Account
tags:
- Accounts
+ /v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{alias}:
+ get:
+ description: Get an Account with the input Alias
+ parameters:
+ - description: Authorization Bearer Token
+ in: header
+ name: Authorization
+ required: true
+ type: string
+ - description: Request ID
+ in: header
+ name: Midaz-Id
+ type: string
+ - description: Organization ID
+ in: path
+ name: organization_id
+ required: true
+ type: string
+ - description: Ledger ID
+ in: path
+ name: ledger_id
+ required: true
+ type: string
+ - description: Alias
+ in: path
+ name: alias
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/Account'
+ summary: Get an Account by Alias
+ tags:
+ - Accounts
/v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{id}:
delete:
description: Delete an Account with the input ID
diff --git a/components/ledger/internal/adapters/grpc/in/account.go b/components/ledger/internal/adapters/grpc/in/account.go
index 31aa45b5..90502791 100644
--- a/components/ledger/internal/adapters/grpc/in/account.go
+++ b/components/ledger/internal/adapters/grpc/in/account.go
@@ -120,8 +120,8 @@ func (ap *AccountProto) GetAccountsByAliases(ctx context.Context, aliases *accou
return &response, nil
}
-// UpdateAccounts is a method that update Account balances by a given ids.
-func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) {
+// UpdateAccountsByIDS is a method that update Account balances by a given ids.
+func (ap *AccountProto) UpdateAccountsByIDS(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) {
logger := pkg.NewLoggerFromContext(ctx)
tracer := pkg.NewTracerFromContext(ctx)
@@ -196,3 +196,33 @@ func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.Acco
return &response, nil
}
+
+// UpdateAccounts is a method that update Account balances by a given ids.
+func (ap *AccountProto) UpdateAccounts(ctx context.Context, update *account.AccountsRequest) (*account.AccountsResponse, error) {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "handler.UpdateAccounts")
+ defer span.End()
+
+ organizationID, err := uuid.Parse(update.OrganizationId)
+ if err != nil {
+ return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), organizationID)
+ }
+
+ ledgerID, err := uuid.Parse(update.LedgerId)
+ if err != nil {
+ return nil, pkg.ValidateBusinessError(constant.ErrInvalidPathParameter, reflect.TypeOf(mmodel.Account{}).Name(), ledgerID)
+ }
+
+ err = ap.Command.UpdateAccounts(ctx, organizationID, ledgerID, update.GetAccounts())
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to update balance in Account by id", err)
+
+ logger.Errorf("Failed to update balance in Account by id for organizationId %v and ledgerId %v in grpc, Error: %v", organizationID, ledgerID, err.Error())
+
+ return nil, err
+ }
+
+ return nil, nil
+}
diff --git a/components/ledger/internal/adapters/http/in/account.go b/components/ledger/internal/adapters/http/in/account.go
index c89a5a3a..9b1a2436 100644
--- a/components/ledger/internal/adapters/http/in/account.go
+++ b/components/ledger/internal/adapters/http/in/account.go
@@ -89,7 +89,6 @@ func (handler *AccountHandler) CreateAccount(i any, c *fiber.Ctx) error {
// @Param start_date query string false "Start Date" example "2021-01-01"
// @Param end_date query string false "End Date" example "2021-01-01"
// @Param sort_order query string false "Sort Order" Enums(asc,desc)
-// @Param alias query string false "Find alias" example "@wallet_12345123"
// @Success 200 {object} mpostgres.Pagination{items=[]mmodel.Account,page=int,limit=int}
// @Router /v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts [get]
func (handler *AccountHandler) GetAllAccounts(c *fiber.Ctx) error {
@@ -120,7 +119,6 @@ func (handler *AccountHandler) GetAllAccounts(c *fiber.Ctx) error {
SortOrder: headerParams.SortOrder,
StartDate: headerParams.StartDate,
EndDate: headerParams.EndDate,
- Alias: headerParams.Alias,
}
if !pkg.IsNilOrEmpty(&headerParams.PortfolioID) {
@@ -211,6 +209,48 @@ func (handler *AccountHandler) GetAccountByID(c *fiber.Ctx) error {
return http.OK(c, account)
}
+// GetAccountByAlias is a method that retrieves Account information by a given account alias.
+//
+// @Summary Get an Account by Alias
+// @Description Get an Account with the input Alias
+// @Tags Accounts
+// @Produce json
+// @Param Authorization header string true "Authorization Bearer Token"
+// @Param Midaz-Id header string false "Request ID"
+// @Param organization_id path string true "Organization ID"
+// @Param ledger_id path string true "Ledger ID"
+// @Param alias path string true "Alias"
+// @Success 200 {object} mmodel.Account
+// @Router /v1/organizations/{organization_id}/ledgers/{ledger_id}/accounts/{alias} [get]
+func (handler *AccountHandler) GetAccountByAlias(c *fiber.Ctx) error {
+ ctx := c.UserContext()
+
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "handler.get_account_by_id")
+ defer span.End()
+
+ organizationID := c.Locals("organization_id").(uuid.UUID)
+ ledgerID := c.Locals("ledger_id").(uuid.UUID)
+ alias := c.Params("alias")
+
+ logger.Infof("Initiating retrieval of Account with Account Alias: %s", alias)
+
+ account, err := handler.Query.GetAccountByAlias(ctx, organizationID, ledgerID, nil, alias)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to retrieve Account on query", err)
+
+ logger.Errorf("Failed to retrieve Account with Account Alias: %s, Error: %s", alias, err.Error())
+
+ return http.WithError(c, err)
+ }
+
+ logger.Infof("Successfully retrieved Account with Account Alias: %s", alias)
+
+ return http.OK(c, account)
+}
+
// UpdateAccount is a method that updates Account information.
//
// @Summary Update an Account
diff --git a/components/ledger/internal/adapters/http/in/routes.go b/components/ledger/internal/adapters/http/in/routes.go
index 17778c8c..b527c18b 100644
--- a/components/ledger/internal/adapters/http/in/routes.go
+++ b/components/ledger/internal/adapters/http/in/routes.go
@@ -65,6 +65,8 @@ func NewRouter(lg mlog.Logger, tl *mopentelemetry.Telemetry, cc *mcasdoor.Casdoo
f.Patch("/v1/organizations/:organization_id/ledgers/:ledger_id/accounts/:id", jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, http.WithBody(new(mmodel.UpdateAccountInput), ah.UpdateAccount))
f.Get("/v1/organizations/:organization_id/ledgers/:ledger_id/accounts", jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, ah.GetAllAccounts)
f.Get("/v1/organizations/:organization_id/ledgers/:ledger_id/accounts/:id", jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, ah.GetAccountByID)
+ f.Get("/v1/organizations/:organization_id/ledgers/:ledger_id/accounts/alias/:alias",
+ jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, ah.GetAccountByAlias)
f.Delete("/v1/organizations/:organization_id/ledgers/:ledger_id/accounts/:id", jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, ah.DeleteAccountByID)
// Will be deprecated in the future. Use "POST /v1/organizations/:organization_id/ledgers/:ledger_id/accounts" instead.
f.Post("/v1/organizations/:organization_id/ledgers/:ledger_id/portfolios/:portfolio_id/accounts", jwt.ProtectHTTP(), jwt.WithPermissionHTTP("account"), http.ParseUUIDPathParameters, http.WithBody(new(mmodel.CreateAccountInput), ah.CreateAccountFromPortfolio))
diff --git a/components/ledger/internal/adapters/postgres/account/account.go b/components/ledger/internal/adapters/postgres/account/account.go
index 0928104b..a23920a5 100644
--- a/components/ledger/internal/adapters/postgres/account/account.go
+++ b/components/ledger/internal/adapters/postgres/account/account.go
@@ -28,6 +28,7 @@ type AccountPostgreSQLModel struct {
AllowReceiving bool
Alias *string
Type string
+ Version int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt sql.NullTime
@@ -63,6 +64,7 @@ func (t *AccountPostgreSQLModel) ToEntity() *mmodel.Account {
AllowReceiving: &t.AllowReceiving,
Alias: t.Alias,
Type: t.Type,
+ Version: t.Version,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
DeletedAt: nil,
@@ -94,6 +96,7 @@ func (t *AccountPostgreSQLModel) FromEntity(account *mmodel.Account) {
StatusDescription: account.Status.Description,
Alias: account.Alias,
Type: account.Type,
+ Version: account.Version,
CreatedAt: account.CreatedAt,
UpdatedAt: account.UpdatedAt,
}
diff --git a/components/ledger/internal/adapters/postgres/account/account.mock.go b/components/ledger/internal/adapters/postgres/account/account.mock.go
index 0895340b..b3a8c3e4 100644
--- a/components/ledger/internal/adapters/postgres/account/account.mock.go
+++ b/components/ledger/internal/adapters/postgres/account/account.mock.go
@@ -13,6 +13,7 @@ import (
context "context"
reflect "reflect"
+ account "github.com/LerianStudio/midaz/pkg/mgrpc/account"
mmodel "github.com/LerianStudio/midaz/pkg/mmodel"
http "github.com/LerianStudio/midaz/pkg/net/http"
uuid "github.com/google/uuid"
@@ -23,7 +24,6 @@ import (
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
- isgomock struct{}
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository.
@@ -44,180 +44,209 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
}
// Create mocks base method.
-func (m *MockRepository) Create(ctx context.Context, acc *mmodel.Account) (*mmodel.Account, error) {
+func (m *MockRepository) Create(arg0 context.Context, arg1 *mmodel.Account) (*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Create", ctx, acc)
+ ret := m.ctrl.Call(m, "Create", arg0, arg1)
ret0, _ := ret[0].(*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
-func (mr *MockRepositoryMockRecorder) Create(ctx, acc any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Create(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), ctx, acc)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), arg0, arg1)
}
// Delete mocks base method.
-func (m *MockRepository) Delete(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) error {
+func (m *MockRepository) Delete(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) error {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Delete", ctx, organizationID, ledgerID, portfolioID, id)
+ ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
-func (mr *MockRepositoryMockRecorder) Delete(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Delete(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), ctx, organizationID, ledgerID, portfolioID, id)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), arg0, arg1, arg2, arg3, arg4)
}
// Find mocks base method.
-func (m *MockRepository) Find(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error) {
+func (m *MockRepository) Find(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) (*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Find", ctx, organizationID, ledgerID, portfolioID, id)
+ ret := m.ctrl.Call(m, "Find", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Find indicates an expected call of Find.
-func (mr *MockRepositoryMockRecorder) Find(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Find(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), ctx, organizationID, ledgerID, portfolioID, id)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), arg0, arg1, arg2, arg3, arg4)
+}
+
+// FindAlias mocks base method.
+func (m *MockRepository) FindAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 string) (*mmodel.Account, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "FindAlias", arg0, arg1, arg2, arg3, arg4)
+ ret0, _ := ret[0].(*mmodel.Account)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// FindAlias indicates an expected call of FindAlias.
+func (mr *MockRepositoryMockRecorder) FindAlias(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAlias", reflect.TypeOf((*MockRepository)(nil).FindAlias), arg0, arg1, arg2, arg3, arg4)
}
// FindAll mocks base method.
-func (m *MockRepository) FindAll(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, filter http.Pagination) ([]*mmodel.Account, error) {
+func (m *MockRepository) FindAll(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 http.Pagination) ([]*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "FindAll", ctx, organizationID, ledgerID, portfolioID, filter)
+ ret := m.ctrl.Call(m, "FindAll", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].([]*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindAll indicates an expected call of FindAll.
-func (mr *MockRepositoryMockRecorder) FindAll(ctx, organizationID, ledgerID, portfolioID, filter any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) FindAll(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), ctx, organizationID, ledgerID, portfolioID, filter)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), arg0, arg1, arg2, arg3, arg4)
}
// FindByAlias mocks base method.
-func (m *MockRepository) FindByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, alias string) (bool, error) {
+func (m *MockRepository) FindByAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 string) (bool, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "FindByAlias", ctx, organizationID, ledgerID, alias)
+ ret := m.ctrl.Call(m, "FindByAlias", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByAlias indicates an expected call of FindByAlias.
-func (mr *MockRepositoryMockRecorder) FindByAlias(ctx, organizationID, ledgerID, alias any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) FindByAlias(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByAlias", reflect.TypeOf((*MockRepository)(nil).FindByAlias), ctx, organizationID, ledgerID, alias)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByAlias", reflect.TypeOf((*MockRepository)(nil).FindByAlias), arg0, arg1, arg2, arg3)
}
// FindWithDeleted mocks base method.
-func (m *MockRepository) FindWithDeleted(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error) {
+func (m *MockRepository) FindWithDeleted(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID) (*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "FindWithDeleted", ctx, organizationID, ledgerID, portfolioID, id)
+ ret := m.ctrl.Call(m, "FindWithDeleted", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindWithDeleted indicates an expected call of FindWithDeleted.
-func (mr *MockRepositoryMockRecorder) FindWithDeleted(ctx, organizationID, ledgerID, portfolioID, id any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) FindWithDeleted(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindWithDeleted", reflect.TypeOf((*MockRepository)(nil).FindWithDeleted), ctx, organizationID, ledgerID, portfolioID, id)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindWithDeleted", reflect.TypeOf((*MockRepository)(nil).FindWithDeleted), arg0, arg1, arg2, arg3, arg4)
}
// ListAccountsByAlias mocks base method.
-func (m *MockRepository) ListAccountsByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, aliases []string) ([]*mmodel.Account, error) {
+func (m *MockRepository) ListAccountsByAlias(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []string) ([]*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListAccountsByAlias", ctx, organizationID, ledgerID, aliases)
+ ret := m.ctrl.Call(m, "ListAccountsByAlias", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAccountsByAlias indicates an expected call of ListAccountsByAlias.
-func (mr *MockRepositoryMockRecorder) ListAccountsByAlias(ctx, organizationID, ledgerID, aliases any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) ListAccountsByAlias(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByAlias", reflect.TypeOf((*MockRepository)(nil).ListAccountsByAlias), ctx, organizationID, ledgerID, aliases)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByAlias", reflect.TypeOf((*MockRepository)(nil).ListAccountsByAlias), arg0, arg1, arg2, arg3)
}
// ListAccountsByIDs mocks base method.
-func (m *MockRepository) ListAccountsByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error) {
+func (m *MockRepository) ListAccountsByIDs(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []uuid.UUID) ([]*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListAccountsByIDs", ctx, organizationID, ledgerID, ids)
+ ret := m.ctrl.Call(m, "ListAccountsByIDs", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAccountsByIDs indicates an expected call of ListAccountsByIDs.
-func (mr *MockRepositoryMockRecorder) ListAccountsByIDs(ctx, organizationID, ledgerID, ids any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) ListAccountsByIDs(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByIDs", reflect.TypeOf((*MockRepository)(nil).ListAccountsByIDs), ctx, organizationID, ledgerID, ids)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAccountsByIDs", reflect.TypeOf((*MockRepository)(nil).ListAccountsByIDs), arg0, arg1, arg2, arg3)
}
// ListByAlias mocks base method.
-func (m *MockRepository) ListByAlias(ctx context.Context, organizationID, ledgerID, portfolioID uuid.UUID, alias []string) ([]*mmodel.Account, error) {
+func (m *MockRepository) ListByAlias(arg0 context.Context, arg1, arg2, arg3 uuid.UUID, arg4 []string) ([]*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListByAlias", ctx, organizationID, ledgerID, portfolioID, alias)
+ ret := m.ctrl.Call(m, "ListByAlias", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].([]*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListByAlias indicates an expected call of ListByAlias.
-func (mr *MockRepositoryMockRecorder) ListByAlias(ctx, organizationID, ledgerID, portfolioID, alias any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) ListByAlias(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByAlias", reflect.TypeOf((*MockRepository)(nil).ListByAlias), ctx, organizationID, ledgerID, portfolioID, alias)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByAlias", reflect.TypeOf((*MockRepository)(nil).ListByAlias), arg0, arg1, arg2, arg3, arg4)
}
// ListByIDs mocks base method.
-func (m *MockRepository) ListByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error) {
+func (m *MockRepository) ListByIDs(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 []uuid.UUID) ([]*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListByIDs", ctx, organizationID, ledgerID, portfolioID, ids)
+ ret := m.ctrl.Call(m, "ListByIDs", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].([]*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListByIDs indicates an expected call of ListByIDs.
-func (mr *MockRepositoryMockRecorder) ListByIDs(ctx, organizationID, ledgerID, portfolioID, ids any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) ListByIDs(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), ctx, organizationID, ledgerID, portfolioID, ids)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), arg0, arg1, arg2, arg3, arg4)
}
// Update mocks base method.
-func (m *MockRepository) Update(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error) {
+func (m *MockRepository) Update(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 *uuid.UUID, arg4 uuid.UUID, arg5 *mmodel.Account) (*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Update", ctx, organizationID, ledgerID, portfolioID, id, acc)
+ ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2, arg3, arg4, arg5)
ret0, _ := ret[0].(*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
-func (mr *MockRepositoryMockRecorder) Update(ctx, organizationID, ledgerID, portfolioID, id, acc any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Update(arg0, arg1, arg2, arg3, arg4, arg5 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), ctx, organizationID, ledgerID, portfolioID, id, acc)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), arg0, arg1, arg2, arg3, arg4, arg5)
}
// UpdateAccountByID mocks base method.
-func (m *MockRepository) UpdateAccountByID(ctx context.Context, organizationID, ledgerID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error) {
+func (m *MockRepository) UpdateAccountByID(arg0 context.Context, arg1, arg2, arg3 uuid.UUID, arg4 *mmodel.Account) (*mmodel.Account, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "UpdateAccountByID", ctx, organizationID, ledgerID, id, acc)
+ ret := m.ctrl.Call(m, "UpdateAccountByID", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*mmodel.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAccountByID indicates an expected call of UpdateAccountByID.
-func (mr *MockRepositoryMockRecorder) UpdateAccountByID(ctx, organizationID, ledgerID, id, acc any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) UpdateAccountByID(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountByID", reflect.TypeOf((*MockRepository)(nil).UpdateAccountByID), arg0, arg1, arg2, arg3, arg4)
+}
+
+// UpdateAccounts mocks base method.
+func (m *MockRepository) UpdateAccounts(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []*account.Account) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateAccounts", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// UpdateAccounts indicates an expected call of UpdateAccounts.
+func (mr *MockRepositoryMockRecorder) UpdateAccounts(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountByID", reflect.TypeOf((*MockRepository)(nil).UpdateAccountByID), ctx, organizationID, ledgerID, id, acc)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccounts", reflect.TypeOf((*MockRepository)(nil).UpdateAccounts), arg0, arg1, arg2, arg3)
}
diff --git a/components/ledger/internal/adapters/postgres/account/account.postgresql.go b/components/ledger/internal/adapters/postgres/account/account.postgresql.go
index ebf8bf24..3488a1b0 100644
--- a/components/ledger/internal/adapters/postgres/account/account.postgresql.go
+++ b/components/ledger/internal/adapters/postgres/account/account.postgresql.go
@@ -4,9 +4,11 @@ import (
"context"
"database/sql"
"errors"
+ "github.com/LerianStudio/midaz/pkg/mgrpc/account"
"reflect"
"strconv"
"strings"
+ "sync"
"time"
"github.com/LerianStudio/midaz/pkg/mpointers"
@@ -33,6 +35,7 @@ type Repository interface {
FindAll(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, filter http.Pagination) ([]*mmodel.Account, error)
Find(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error)
FindWithDeleted(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, id uuid.UUID) (*mmodel.Account, error)
+ FindAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, alias string) (*mmodel.Account, error)
FindByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, alias string) (bool, error)
ListByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error)
ListByAlias(ctx context.Context, organizationID, ledgerID, portfolioID uuid.UUID, alias []string) ([]*mmodel.Account, error)
@@ -41,6 +44,7 @@ type Repository interface {
ListAccountsByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*mmodel.Account, error)
ListAccountsByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, aliases []string) ([]*mmodel.Account, error)
UpdateAccountByID(ctx context.Context, organizationID, ledgerID uuid.UUID, id uuid.UUID, acc *mmodel.Account) (*mmodel.Account, error)
+ UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, acc []*account.Account) error
}
// AccountPostgreSQLRepository is a Postgresql-specific implementation of the AccountRepository.
@@ -92,7 +96,7 @@ func (r *AccountPostgreSQLRepository) Create(ctx context.Context, acc *mmodel.Ac
result, err := db.ExecContext(ctx, `INSERT INTO account VALUES
(
- $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21
+ $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22
)
RETURNING *`,
record.ID,
@@ -113,6 +117,7 @@ func (r *AccountPostgreSQLRepository) Create(ctx context.Context, acc *mmodel.Ac
record.AllowReceiving,
record.Alias,
record.Type,
+ record.Version,
record.CreatedAt,
record.UpdatedAt,
record.DeletedAt,
@@ -177,10 +182,6 @@ func (r *AccountPostgreSQLRepository) FindAll(ctx context.Context, organizationI
Where(squirrel.GtOrEq{"created_at": pkg.NormalizeDate(filter.StartDate, mpointers.Int(-1))}).
Where(squirrel.LtOrEq{"created_at": pkg.NormalizeDate(filter.EndDate, mpointers.Int(1))})
- if len(filter.Alias) > 0 {
- findAll = findAll.Where(squirrel.Expr("alias = ?", filter.Alias))
- }
-
findAll = findAll.Limit(pkg.SafeIntToUint64(filter.Limit)).
Offset(pkg.SafeIntToUint64((filter.Page - 1) * filter.Limit)).
PlaceholderFormat(squirrel.Dollar)
@@ -225,6 +226,7 @@ func (r *AccountPostgreSQLRepository) FindAll(ctx context.Context, organizationI
&acc.AllowReceiving,
&acc.Alias,
&acc.Type,
+ &acc.Version,
&acc.CreatedAt,
&acc.UpdatedAt,
&acc.DeletedAt,
@@ -271,7 +273,7 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID,
query += " ORDER BY created_at DESC"
- account := &AccountPostgreSQLModel{}
+ acc := &AccountPostgreSQLModel{}
ctx, spanQuery := tracer.Start(ctx, "postgres.find.query")
@@ -280,27 +282,28 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID,
spanQuery.End()
if err := row.Scan(
- &account.ID,
- &account.Name,
- &account.ParentAccountID,
- &account.EntityID,
- &account.AssetCode,
- &account.OrganizationID,
- &account.LedgerID,
- &account.PortfolioID,
- &account.ProductID,
- &account.AvailableBalance,
- &account.OnHoldBalance,
- &account.BalanceScale,
- &account.Status,
- &account.StatusDescription,
- &account.AllowSending,
- &account.AllowReceiving,
- &account.Alias,
- &account.Type,
- &account.CreatedAt,
- &account.UpdatedAt,
- &account.DeletedAt,
+ &acc.ID,
+ &acc.Name,
+ &acc.ParentAccountID,
+ &acc.EntityID,
+ &acc.AssetCode,
+ &acc.OrganizationID,
+ &acc.LedgerID,
+ &acc.PortfolioID,
+ &acc.ProductID,
+ &acc.AvailableBalance,
+ &acc.OnHoldBalance,
+ &acc.BalanceScale,
+ &acc.Status,
+ &acc.StatusDescription,
+ &acc.AllowSending,
+ &acc.AllowReceiving,
+ &acc.Alias,
+ &acc.Type,
+ &acc.Version,
+ &acc.CreatedAt,
+ &acc.UpdatedAt,
+ &acc.DeletedAt,
); err != nil {
mopentelemetry.HandleSpanError(&span, "Failed to scan row", err)
@@ -311,7 +314,7 @@ func (r *AccountPostgreSQLRepository) Find(ctx context.Context, organizationID,
return nil, err
}
- return account.ToEntity(), nil
+ return acc.ToEntity(), nil
}
// FindWithDeleted retrieves an Account entity from the database using the provided ID (including soft-deleted ones).
@@ -339,7 +342,7 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ
query += " ORDER BY created_at DESC"
- account := &AccountPostgreSQLModel{}
+ acc := &AccountPostgreSQLModel{}
ctx, spanQuery := tracer.Start(ctx, "postgres.find_with_deleted.query")
@@ -348,27 +351,28 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ
spanQuery.End()
if err := row.Scan(
- &account.ID,
- &account.Name,
- &account.ParentAccountID,
- &account.EntityID,
- &account.AssetCode,
- &account.OrganizationID,
- &account.LedgerID,
- &account.PortfolioID,
- &account.ProductID,
- &account.AvailableBalance,
- &account.OnHoldBalance,
- &account.BalanceScale,
- &account.Status,
- &account.StatusDescription,
- &account.AllowSending,
- &account.AllowReceiving,
- &account.Alias,
- &account.Type,
- &account.CreatedAt,
- &account.UpdatedAt,
- &account.DeletedAt,
+ &acc.ID,
+ &acc.Name,
+ &acc.ParentAccountID,
+ &acc.EntityID,
+ &acc.AssetCode,
+ &acc.OrganizationID,
+ &acc.LedgerID,
+ &acc.PortfolioID,
+ &acc.ProductID,
+ &acc.AvailableBalance,
+ &acc.OnHoldBalance,
+ &acc.BalanceScale,
+ &acc.Status,
+ &acc.StatusDescription,
+ &acc.AllowSending,
+ &acc.AllowReceiving,
+ &acc.Alias,
+ &acc.Type,
+ &acc.Version,
+ &acc.CreatedAt,
+ &acc.UpdatedAt,
+ &acc.DeletedAt,
); err != nil {
mopentelemetry.HandleSpanError(&span, "Failed to scan row", err)
@@ -379,7 +383,76 @@ func (r *AccountPostgreSQLRepository) FindWithDeleted(ctx context.Context, organ
return nil, err
}
- return account.ToEntity(), nil
+ return acc.ToEntity(), nil
+}
+
+// FindAlias retrieves an Account entity from the database using the provided Alias (including soft-deleted ones).
+func (r *AccountPostgreSQLRepository) FindAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, alias string) (*mmodel.Account, error) {
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "postgres.find_alias")
+ defer span.End()
+
+ db, err := r.connection.GetDB()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get database connection", err)
+
+ return nil, err
+ }
+
+ query := "SELECT * FROM account WHERE organization_id = $1 AND ledger_id = $2 AND alias = $3"
+ args := []any{organizationID, ledgerID, alias}
+
+ if portfolioID != nil && *portfolioID != uuid.Nil {
+ query += " AND portfolio_id = $4"
+
+ args = append(args, portfolioID)
+ }
+
+ query += " ORDER BY created_at DESC"
+
+ acc := &AccountPostgreSQLModel{}
+
+ ctx, spanQuery := tracer.Start(ctx, "postgres.find_with_deleted.query")
+
+ row := db.QueryRowContext(ctx, query, args...)
+
+ spanQuery.End()
+
+ if err := row.Scan(
+ &acc.ID,
+ &acc.Name,
+ &acc.ParentAccountID,
+ &acc.EntityID,
+ &acc.AssetCode,
+ &acc.OrganizationID,
+ &acc.LedgerID,
+ &acc.PortfolioID,
+ &acc.ProductID,
+ &acc.AvailableBalance,
+ &acc.OnHoldBalance,
+ &acc.BalanceScale,
+ &acc.Status,
+ &acc.StatusDescription,
+ &acc.AllowSending,
+ &acc.AllowReceiving,
+ &acc.Alias,
+ &acc.Type,
+ &acc.Version,
+ &acc.CreatedAt,
+ &acc.UpdatedAt,
+ &acc.DeletedAt,
+ ); err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to scan row", err)
+
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, pkg.ValidateBusinessError(constant.ErrAccountAliasNotFound, reflect.TypeOf(mmodel.Account{}).Name())
+ }
+
+ return nil, err
+ }
+
+ return acc.ToEntity(), nil
}
// FindByAlias find account from the database using Organization and Ledger id and Alias. Returns true and ErrAliasUnavailability error if the alias is already taken.
@@ -480,6 +553,7 @@ func (r *AccountPostgreSQLRepository) ListByIDs(ctx context.Context, organizatio
&acc.AllowReceiving,
&acc.Alias,
&acc.Type,
+ &acc.Version,
&acc.CreatedAt,
&acc.UpdatedAt,
&acc.DeletedAt,
@@ -551,6 +625,7 @@ func (r *AccountPostgreSQLRepository) ListByAlias(ctx context.Context, organizat
&acc.AllowReceiving,
&acc.Alias,
&acc.Type,
+ &acc.Version,
&acc.CreatedAt,
&acc.UpdatedAt,
&acc.DeletedAt,
@@ -765,6 +840,7 @@ func (r *AccountPostgreSQLRepository) ListAccountsByIDs(ctx context.Context, org
&acc.AllowReceiving,
&acc.Alias,
&acc.Type,
+ &acc.Version,
&acc.CreatedAt,
&acc.UpdatedAt,
&acc.DeletedAt,
@@ -835,6 +911,7 @@ func (r *AccountPostgreSQLRepository) ListAccountsByAlias(ctx context.Context, o
&acc.AllowReceiving,
&acc.Alias,
&acc.Type,
+ &acc.Version,
&acc.CreatedAt,
&acc.UpdatedAt,
&acc.DeletedAt,
@@ -939,3 +1016,102 @@ func (r *AccountPostgreSQLRepository) UpdateAccountByID(ctx context.Context, org
return record.ToEntity(), nil
}
+
+// UpdateAccounts an update all Accounts entity by ID only into Postgresql.
+func (r *AccountPostgreSQLRepository) UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error {
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "postgres.update_accounts")
+ defer span.End()
+
+ db, err := r.connection.GetDB()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get database connection", err)
+
+ return err
+ }
+
+ tx, err := db.Begin()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to init transaction", err)
+
+ return err
+ }
+
+ var wg sync.WaitGroup
+
+ errChan := make(chan error, len(accounts))
+
+ for _, acc := range accounts {
+ wg.Add(1)
+
+ go func(acc *account.Account) {
+ defer wg.Done()
+
+ var updates []string
+
+ var args []any
+
+ updates = append(updates, "available_balance = $"+strconv.Itoa(len(args)+1))
+ args = append(args, acc.Balance.Available)
+
+ updates = append(updates, "on_hold_balance = $"+strconv.Itoa(len(args)+1))
+ args = append(args, acc.Balance.OnHold)
+
+ updates = append(updates, "balance_scale = $"+strconv.Itoa(len(args)+1))
+ args = append(args, acc.Balance.Scale)
+
+ updates = append(updates, "version = $"+strconv.Itoa(len(args)+1))
+ version := acc.Version + 1
+ args = append(args, version)
+
+ updates = append(updates, "updated_at = $"+strconv.Itoa(len(args)+1))
+ args = append(args, time.Now(), organizationID, ledgerID, acc.Id, acc.Version)
+
+ query := `UPDATE account SET ` + strings.Join(updates, ", ") +
+ ` WHERE organization_id = $` + strconv.Itoa(len(args)-3) +
+ ` AND ledger_id = $` + strconv.Itoa(len(args)-2) +
+ ` AND id = $` + strconv.Itoa(len(args)-1) +
+ ` AND version = $` + strconv.Itoa(len(args)) +
+ ` AND deleted_at IS NULL`
+
+ result, err := tx.ExecContext(ctx, query, args...)
+ if err != nil {
+ errChan <- err
+ return
+ }
+
+ rowsAffected, err := result.RowsAffected()
+ if err != nil || rowsAffected == 0 {
+ if err == nil {
+ err = sql.ErrNoRows
+ }
+ errChan <- err
+ }
+ }(acc)
+ }
+
+ wg.Wait()
+ close(errChan)
+
+ for err := range errChan {
+ if err != nil {
+ rollbackErr := tx.Rollback()
+ if rollbackErr != nil {
+ return rollbackErr
+ }
+
+ return err
+ }
+ }
+
+ if commitErr := tx.Commit(); commitErr != nil {
+ err := pkg.ValidateBusinessError(constant.ErrEntityNotFound, reflect.TypeOf(mmodel.Account{}).Name())
+
+ mopentelemetry.HandleSpanError(&span, "Failed to commit accounts", err)
+
+ return commitErr
+ }
+
+ return nil
+}
diff --git a/components/ledger/internal/services/command/update-account-id.go b/components/ledger/internal/services/command/update-account-id.go
index 51a2c39c..3f5f0b84 100644
--- a/components/ledger/internal/services/command/update-account-id.go
+++ b/components/ledger/internal/services/command/update-account-id.go
@@ -3,6 +3,7 @@ package command
import (
"context"
"errors"
+ "github.com/LerianStudio/midaz/pkg/mgrpc/account"
"reflect"
"github.com/LerianStudio/midaz/components/ledger/internal/services"
@@ -24,11 +25,11 @@ func (uc *UseCase) UpdateAccountByID(ctx context.Context, organizationID, ledger
logger.Infof("Trying to update account by id: %v", id)
- account := &mmodel.Account{
+ acc := &mmodel.Account{
Balance: *balance,
}
- accountUpdated, err := uc.AccountRepo.UpdateAccountByID(ctx, organizationID, ledgerID, id, account)
+ accountUpdated, err := uc.AccountRepo.UpdateAccountByID(ctx, organizationID, ledgerID, id, acc)
if err != nil {
mopentelemetry.HandleSpanError(&span, "Failed to update account on repo by id", err)
@@ -43,3 +44,28 @@ func (uc *UseCase) UpdateAccountByID(ctx context.Context, organizationID, ledger
return accountUpdated, nil
}
+
+func (uc *UseCase) UpdateAccounts(ctx context.Context, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "command.update_accounts")
+ defer span.End()
+
+ logger.Infof("Trying to update accounts")
+
+ err := uc.AccountRepo.UpdateAccounts(ctx, organizationID, ledgerID, accounts)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to update account on repo by id", err)
+
+ logger.Errorf("Error updating account on repo by id: %v", err)
+
+ if errors.Is(err, services.ErrDatabaseItemNotFound) {
+ return pkg.ValidateBusinessError(constant.ErrAccountIDNotFound, reflect.TypeOf(mmodel.Account{}).Name())
+ }
+
+ return err
+ }
+
+ return nil
+}
diff --git a/components/ledger/internal/services/query/get-alias-account.go b/components/ledger/internal/services/query/get-alias-account.go
new file mode 100644
index 00000000..0b411269
--- /dev/null
+++ b/components/ledger/internal/services/query/get-alias-account.go
@@ -0,0 +1,54 @@
+package query
+
+import (
+ "context"
+ "errors"
+ "reflect"
+
+ "github.com/LerianStudio/midaz/components/ledger/internal/services"
+ "github.com/LerianStudio/midaz/pkg"
+ "github.com/LerianStudio/midaz/pkg/constant"
+ "github.com/LerianStudio/midaz/pkg/mmodel"
+ "github.com/LerianStudio/midaz/pkg/mopentelemetry"
+ "github.com/google/uuid"
+)
+
+// GetAccountByAlias get an Account from the repository by given alias (including soft-deleted ones).
+func (uc *UseCase) GetAccountByAlias(ctx context.Context, organizationID, ledgerID uuid.UUID, portfolioID *uuid.UUID, alias string) (*mmodel.Account, error) {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "query.get_account_by_alias")
+
+ logger.Infof("Retrieving account for alias: %s", alias)
+
+ account, err := uc.AccountRepo.FindAlias(ctx, organizationID, ledgerID, portfolioID, alias)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get account on repo by alias", err)
+
+ logger.Errorf("Error getting account on repo by alias: %v", err)
+
+ if errors.Is(err, services.ErrDatabaseItemNotFound) {
+ return nil, pkg.ValidateBusinessError(constant.ErrAccountAliasNotFound, reflect.TypeOf(mmodel.Account{}).Name())
+ }
+
+ return nil, err
+ }
+
+ if account != nil {
+ metadata, err := uc.MetadataRepo.FindByEntity(ctx, reflect.TypeOf(mmodel.Account{}).Name(), alias)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get metadata on mongodb account", err)
+
+ logger.Errorf("Error get metadata on mongodb account: %v", err)
+
+ return nil, err
+ }
+
+ if metadata != nil {
+ account.Metadata = metadata.Data
+ }
+ }
+
+ return account, nil
+}
diff --git a/components/ledger/internal/services/query/get-alias-account_test.go b/components/ledger/internal/services/query/get-alias-account_test.go
new file mode 100644
index 00000000..963fe2b8
--- /dev/null
+++ b/components/ledger/internal/services/query/get-alias-account_test.go
@@ -0,0 +1,112 @@
+package query
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/LerianStudio/midaz/components/ledger/internal/adapters/mongodb"
+ "github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account"
+ "github.com/LerianStudio/midaz/components/ledger/internal/services"
+ "github.com/LerianStudio/midaz/pkg/mmodel"
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "go.uber.org/mock/gomock"
+)
+
+func TestUseCase_GetAccountByAlias(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ mockAccountRepo := account.NewMockRepository(ctrl)
+ mockMetadataRepo := mongodb.NewMockRepository(ctrl)
+
+ uc := &UseCase{
+ AccountRepo: mockAccountRepo,
+ MetadataRepo: mockMetadataRepo,
+ }
+
+ tests := []struct {
+ name string
+ organizationID uuid.UUID
+ ledgerID uuid.UUID
+ portfolioID *uuid.UUID
+ alias string
+ mockSetup func()
+ expectErr bool
+ expectedResult *mmodel.Account
+ }{
+ {
+ name: "Success - Retrieve account with metadata",
+ organizationID: uuid.New(),
+ ledgerID: uuid.New(),
+ portfolioID: nil,
+ alias: "case01",
+ mockSetup: func() {
+ accountID := uuid.New()
+ mockAccountRepo.EXPECT().
+ FindAlias(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(&mmodel.Account{ID: accountID.String(), Name: "Test Account", Status: mmodel.Status{Code: "active"}}, nil)
+ mockMetadataRepo.EXPECT().
+ FindByEntity(gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(&mongodb.Metadata{Data: map[string]any{"key": "value"}}, nil)
+ },
+ expectErr: false,
+ expectedResult: &mmodel.Account{
+ ID: "valid-uuid",
+ Name: "Test Account",
+ Status: mmodel.Status{Code: "active"},
+ Metadata: map[string]any{"key": "value"},
+ },
+ },
+ {
+ name: "Error - Account not found",
+ organizationID: uuid.New(),
+ ledgerID: uuid.New(),
+ portfolioID: nil,
+ alias: "case02",
+ mockSetup: func() {
+ mockAccountRepo.EXPECT().
+ FindAlias(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(nil, services.ErrDatabaseItemNotFound)
+ },
+ expectErr: true,
+ expectedResult: nil,
+ },
+ {
+ name: "Error - Failed to retrieve metadata",
+ organizationID: uuid.New(),
+ ledgerID: uuid.New(),
+ portfolioID: nil,
+ alias: "case03",
+ mockSetup: func() {
+ accountID := uuid.New()
+ mockAccountRepo.EXPECT().
+ FindAlias(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(&mmodel.Account{ID: accountID.String(), Name: "Test Account", Status: mmodel.Status{Code: "active"}}, nil)
+ mockMetadataRepo.EXPECT().
+ FindByEntity(gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(nil, errors.New("metadata retrieval error"))
+ },
+ expectErr: true,
+ expectedResult: nil,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tt.mockSetup()
+
+ ctx := context.Background()
+ result, err := uc.GetAccountByAlias(ctx, tt.organizationID, tt.ledgerID, tt.portfolioID, tt.alias)
+
+ if tt.expectErr {
+ assert.Error(t, err)
+ assert.Nil(t, result)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+ }
+ })
+ }
+}
diff --git a/components/ledger/internal/services/query/get-alias-accounts_test.go b/components/ledger/internal/services/query/get-alias-accounts_test.go
index dbe04334..5440797b 100644
--- a/components/ledger/internal/services/query/get-alias-accounts_test.go
+++ b/components/ledger/internal/services/query/get-alias-accounts_test.go
@@ -3,12 +3,12 @@ package query
import (
"context"
"errors"
+ "github.com/LerianStudio/midaz/pkg/mpointers"
"testing"
"github.com/LerianStudio/midaz/components/ledger/internal/adapters/postgres/account"
"github.com/LerianStudio/midaz/components/ledger/internal/services"
"github.com/LerianStudio/midaz/pkg/mmodel"
- "github.com/LerianStudio/midaz/pkg/mpointers"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
diff --git a/components/ledger/migrations/000005_create_account_table.up.sql b/components/ledger/migrations/000005_create_account_table.up.sql
index 672c4eb1..4ab65f56 100644
--- a/components/ledger/migrations/000005_create_account_table.up.sql
+++ b/components/ledger/migrations/000005_create_account_table.up.sql
@@ -18,6 +18,7 @@ CREATE TABLE IF NOT EXISTS account
allow_receiving BOOLEAN NOT NULL,
alias TEXT NULL,
type TEXT NOT NULL,
+ version NUMERIC DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
diff --git a/components/mdz/.env.example b/components/mdz/.env.example
index 5da79e4e..d88cea36 100644
--- a/components/mdz/.env.example
+++ b/components/mdz/.env.example
@@ -2,4 +2,4 @@ CLIENT_ID=9670e0ca55a29a466d31
CLIENT_SECRET=dd03f916cacf4a98c6a413d9c38ba102dce436a9
URL_API_AUTH=http://127.0.0.1:8080
URL_API_LEDGER=http://127.0.0.1:3000
-VERSION=v1.44.0
+VERSION=v1.45.0
diff --git a/components/transaction/.env.example b/components/transaction/.env.example
index ce0b4635..2640ac7e 100644
--- a/components/transaction/.env.example
+++ b/components/transaction/.env.example
@@ -4,7 +4,7 @@
# ENV_NAME=production
# APP
-VERSION=v1.44.0
+VERSION=v1.45.0
APP_CONTEXT=/transaction/v1
SERVER_PORT=3002
SERVER_ADDRESS=:${SERVER_PORT}
diff --git a/components/transaction/api/docs.go b/components/transaction/api/docs.go
index 938098a0..e9dc5864 100644
--- a/components/transaction/api/docs.go
+++ b/components/transaction/api/docs.go
@@ -1044,6 +1044,65 @@ const docTemplate = `{
}
}
}
+ },
+ "/v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/{transaction_id}/revert": {
+ "post": {
+ "description": "Revert a Transaction with Transaction ID only",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Transactions"
+ ],
+ "summary": "Revert a Transaction",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Authorization Bearer Token",
+ "name": "Authorization",
+ "in": "header",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Request ID",
+ "name": "Midaz-Id",
+ "in": "header"
+ },
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organization_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Ledger ID",
+ "name": "ledger_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Transaction ID",
+ "name": "transaction_id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/Transaction"
+ }
+ }
+ }
+ }
}
},
"definitions": {
diff --git a/components/transaction/api/openapi.yaml b/components/transaction/api/openapi.yaml
index a17bbacf..c064ae46 100644
--- a/components/transaction/api/openapi.yaml
+++ b/components/transaction/api/openapi.yaml
@@ -731,6 +731,49 @@ paths:
tags:
- Operations
x-codegen-request-body-name: operation
+ /v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/{transaction_id}/revert:
+ post:
+ description: Revert a Transaction with Transaction ID only
+ parameters:
+ - description: Authorization Bearer Token
+ in: header
+ name: Authorization
+ required: true
+ schema:
+ type: string
+ - description: Request ID
+ in: header
+ name: Midaz-Id
+ schema:
+ type: string
+ - description: Organization ID
+ in: path
+ name: organization_id
+ required: true
+ schema:
+ type: string
+ - description: Ledger ID
+ in: path
+ name: ledger_id
+ required: true
+ schema:
+ type: string
+ - description: Transaction ID
+ in: path
+ name: transaction_id
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Transaction'
+ description: OK
+ summary: Revert a Transaction
+ tags:
+ - Transactions
components:
schemas:
Amount:
diff --git a/components/transaction/api/swagger.json b/components/transaction/api/swagger.json
index 9ada0103..c2cced01 100644
--- a/components/transaction/api/swagger.json
+++ b/components/transaction/api/swagger.json
@@ -1038,6 +1038,65 @@
}
}
}
+ },
+ "/v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/{transaction_id}/revert": {
+ "post": {
+ "description": "Revert a Transaction with Transaction ID only",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Transactions"
+ ],
+ "summary": "Revert a Transaction",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Authorization Bearer Token",
+ "name": "Authorization",
+ "in": "header",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Request ID",
+ "name": "Midaz-Id",
+ "in": "header"
+ },
+ {
+ "type": "string",
+ "description": "Organization ID",
+ "name": "organization_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Ledger ID",
+ "name": "ledger_id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "Transaction ID",
+ "name": "transaction_id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/Transaction"
+ }
+ }
+ }
+ }
}
},
"definitions": {
diff --git a/components/transaction/api/swagger.yaml b/components/transaction/api/swagger.yaml
index f34b8d1c..284ce4c5 100644
--- a/components/transaction/api/swagger.yaml
+++ b/components/transaction/api/swagger.yaml
@@ -1032,6 +1032,46 @@ paths:
summary: Update an Operation
tags:
- Operations
+ /v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/{transaction_id}/revert:
+ post:
+ consumes:
+ - application/json
+ description: Revert a Transaction with Transaction ID only
+ parameters:
+ - description: Authorization Bearer Token
+ in: header
+ name: Authorization
+ required: true
+ type: string
+ - description: Request ID
+ in: header
+ name: Midaz-Id
+ type: string
+ - description: Organization ID
+ in: path
+ name: organization_id
+ required: true
+ type: string
+ - description: Ledger ID
+ in: path
+ name: ledger_id
+ required: true
+ type: string
+ - description: Transaction ID
+ in: path
+ name: transaction_id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/Transaction'
+ summary: Revert a Transaction
+ tags:
+ - Transactions
/v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/dsl:
post:
consumes:
diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go
index b200ab1c..e515bc1c 100644
--- a/components/transaction/internal/adapters/http/in/transaction.go
+++ b/components/transaction/internal/adapters/http/in/transaction.go
@@ -169,7 +169,18 @@ func (handler *TransactionHandler) CommitTransaction(c *fiber.Ctx) error {
// RevertTransaction method that revert transaction created before
//
-// TODO: Implement this method and the swagger documentation related to it
+// @Summary Revert a Transaction
+// @Description Revert a Transaction with Transaction ID only
+// @Tags Transactions
+// @Accept json
+// @Produce json
+// @Param Authorization header string true "Authorization Bearer Token"
+// @Param Midaz-Id header string false "Request ID"
+// @Param organization_id path string true "Organization ID"
+// @Param ledger_id path string true "Ledger ID"
+// @Param transaction_id path string true "Transaction ID"
+// @Success 200 {object} transaction.Transaction
+// @Router /v1/organizations/{organization_id}/ledgers/{ledger_id}/transactions/{transaction_id}/revert [post]
func (handler *TransactionHandler) RevertTransaction(c *fiber.Ctx) error {
ctx := c.UserContext()
@@ -179,7 +190,53 @@ func (handler *TransactionHandler) RevertTransaction(c *fiber.Ctx) error {
_, span := tracer.Start(ctx, "handler.revert_transaction")
defer span.End()
- return http.Created(c, logger)
+ organizationID := c.Locals("organization_id").(uuid.UUID)
+ ledgerID := c.Locals("ledger_id").(uuid.UUID)
+ transactionID := c.Locals("transaction_id").(uuid.UUID)
+
+ parent, err := handler.Query.GetParentByTransactionID(ctx, organizationID, ledgerID, transactionID)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to retrieve Parent Transaction on query", err)
+
+ logger.Errorf("Failed to retrieve Parent Transaction with ID: %s, Error: %s", transactionID.String(), err.Error())
+
+ return http.WithError(c, err)
+ }
+
+ if parent != nil {
+ err = pkg.ValidateBusinessError(constant.ErrTransactionIDHasAlreadyParentTransaction, "RevertTransaction")
+
+ mopentelemetry.HandleSpanError(&span, "Transaction Has Already Parent Transaction", err)
+
+ logger.Errorf("Transaction Has Already Parent Transaction with ID: %s, Error: %s", transactionID.String(), err)
+
+ return http.WithError(c, err)
+ }
+
+ tran, err := handler.Query.GetTransactionByID(ctx, organizationID, ledgerID, transactionID)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to retrieve transaction on query", err)
+
+ logger.Errorf("Failed to retrieve Transaction with ID: %s, Error: %s", transactionID.String(), err.Error())
+
+ return http.WithError(c, err)
+ }
+
+ if tran.ParentTransactionID != nil {
+ err = pkg.ValidateBusinessError(constant.ErrTransactionIDIsAlreadyARevert, "RevertTransaction")
+
+ mopentelemetry.HandleSpanError(&span, "Transaction Has Already Parent Transaction", err)
+
+ logger.Errorf("Transaction Has Already Parent Transaction with ID: %s, Error: %s", transactionID.String(), err)
+
+ return http.WithError(c, err)
+ }
+
+ transactionReverted := tran.TransactionRevert()
+
+ response := handler.createTransaction(c, logger, transactionReverted)
+
+ return response
}
// UpdateTransaction method that patch transaction created before
@@ -385,8 +442,9 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
organizationID := c.Locals("organization_id").(uuid.UUID)
ledgerID := c.Locals("ledger_id").(uuid.UUID)
+ transactionID, _ := c.Locals("transaction_id").(uuid.UUID)
- _, spanRedis := tracer.Start(ctx, "handler.create_transaction_idempotency")
+ _, spanIdempotency := tracer.Start(ctx, "handler.create_transaction_idempotency")
ts, _ := pkg.StructToJSONString(parserDSL)
hash := pkg.HashSHA256(ts)
@@ -394,14 +452,14 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
err := handler.Command.CreateOrCheckIdempotencyKey(ctx, organizationID, ledgerID, key, hash, ttl)
if err != nil {
- mopentelemetry.HandleSpanError(&spanRedis, "Redis idempotency key", err)
+ mopentelemetry.HandleSpanError(&spanIdempotency, "Redis idempotency key", err)
- logger.Error("Redis idempotency key:", err.Error())
+ logger.Infof("Redis idempotency key: %v", err.Error())
return http.WithError(c, err)
}
- spanRedis.End()
+ spanIdempotency.End()
_, spanValidateDSL := tracer.Start(ctx, "handler.create_transaction_validate_dsl")
@@ -416,29 +474,29 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
spanValidateDSL.End()
+ _, spanRaceCondition := tracer.Start(ctx, "handler.create_transaction.race_condition")
+
+ handler.Command.AllKeysUnlocked(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanRaceCondition.End()
+
ctxGetAccounts, spanGetAccounts := tracer.Start(ctx, "handler.create_transaction.get_accounts")
token := http.GetTokenHeader(c)
- accounts, err := handler.getAccounts(ctxGetAccounts, logger, token, organizationID, ledgerID, validate.Aliases)
+ accounts, err := handler.getAccountsAndValidate(ctxGetAccounts, logger, token, hash, organizationID, ledgerID, validate, parserDSL)
if err != nil {
mopentelemetry.HandleSpanError(&spanGetAccounts, "Failed to get accounts", err)
- return http.WithError(c, err)
- }
+ _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
- spanGetAccounts.End()
-
- _, spanValidateAccounts := tracer.Start(ctx, "handler.create_transaction.validate_accounts")
-
- err = goldModel.ValidateAccounts(*validate, accounts)
- if err != nil {
- mopentelemetry.HandleSpanError(&spanValidateAccounts, "Failed to validate accounts", err)
+ spanReleaseLock.End()
return http.WithError(c, err)
}
- spanValidateAccounts.End()
+ spanGetAccounts.End()
ctxCreateTransaction, spanCreateTransaction := tracer.Start(ctx, "handler.create_transaction.create_transaction")
@@ -446,15 +504,20 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
if err != nil {
mopentelemetry.HandleSpanError(&spanCreateTransaction, "Failed to convert parserDSL from struct to JSON string", err)
- return err
+ return http.WithError(c, err)
}
- tran, err := handler.Command.CreateTransaction(ctxCreateTransaction, organizationID, ledgerID, &parserDSL)
+ tran, err := handler.Command.CreateTransaction(ctxCreateTransaction, organizationID, ledgerID, transactionID, &parserDSL)
if err != nil {
mopentelemetry.HandleSpanError(&spanCreateTransaction, "Failed to create transaction", err)
logger.Error("Failed to create transaction", err.Error())
+ _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanReleaseLock.End()
+
return http.WithError(c, err)
}
@@ -476,36 +539,63 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
logger.Error("Failed to create operations: ", err.Error())
+ _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanReleaseLock.End()
+
return http.WithError(c, err)
}
spanCreateOperation.End()
- ctxProcessAccounts, spanProcessAccounts := tracer.Start(ctx, "handler.create_transaction.process_accounts")
+ ctxProcessAccounts, spanUpdateAccounts := tracer.Start(ctx, "handler.create_transaction.update_accounts")
- err = mopentelemetry.SetSpanAttributesFromStruct(&spanProcessAccounts, "payload_handler_process_accounts", accounts)
+ err = mopentelemetry.SetSpanAttributesFromStruct(&spanUpdateAccounts, "payload_handler_update_accounts", accounts)
if err != nil {
- mopentelemetry.HandleSpanError(&spanProcessAccounts, "Failed to convert accounts from struct to JSON string", err)
+ mopentelemetry.HandleSpanError(&spanUpdateAccounts, "Failed to convert accounts from struct to JSON string", err)
}
- err = handler.processAccounts(ctxProcessAccounts, logger, *validate, token, organizationID, ledgerID, accounts)
+ err = handler.Command.UpdateAccounts(ctxProcessAccounts, logger, *validate, token, organizationID, ledgerID, accounts)
if err != nil {
- mopentelemetry.HandleSpanError(&spanProcessAccounts, "Failed to process accounts", err)
+ mopentelemetry.HandleSpanError(&spanUpdateAccounts, "Failed to update accounts", err)
+
+ _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanReleaseLock.End()
+
+ ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.update_accounts.update_transaction_status")
+ _, er := handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.DECLINED)
+
+ if er != nil {
+ mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err)
+
+ logger.Errorf("Failed to update Transaction with ID: %s, Error: %s", tran.ID, err.Error())
+
+ return http.WithError(c, er)
+ }
+
+ spanUpdateTransactionStatus.End()
return http.WithError(c, err)
}
- spanProcessAccounts.End()
+ spanUpdateAccounts.End()
ctxUpdateTransactionStatus, spanUpdateTransactionStatus := tracer.Start(ctx, "handler.create_transaction.update_transaction_status")
-
- //TODO: use event driven and broken and parts
_, err = handler.Command.UpdateTransactionStatus(ctxUpdateTransactionStatus, organizationID, ledgerID, tran.IDtoUUID(), constant.APPROVED)
+
if err != nil {
mopentelemetry.HandleSpanError(&spanUpdateTransactionStatus, "Failed to update transaction status", err)
logger.Errorf("Failed to update Transaction with ID: %s, Error: %s", tran.ID, err.Error())
+ _, spanReleaseLock := tracer.Start(ctx, "handler.update_accounts.delete_locks_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanReleaseLock.End()
+
return http.WithError(c, err)
}
@@ -513,7 +603,6 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
ctxGetTransaction, spanGetTransaction := tracer.Start(ctx, "handler.create_transaction.get_transaction")
- //TODO: use event driven and broken and parts
tran, err = handler.Query.GetTransactionByID(ctxGetTransaction, organizationID, ledgerID, tran.IDtoUUID())
if err != nil {
mopentelemetry.HandleSpanError(&spanGetTransaction, "Failed to retrieve transaction", err)
@@ -531,11 +620,53 @@ func (handler *TransactionHandler) createTransaction(c *fiber.Ctx, logger mlog.L
logger.Infof("Successfully updated Transaction with Organization ID: %s, Ledger ID: %s and ID: %s", organizationID.String(), ledgerID.String(), tran.ID)
+ _, spanReleaseLock := tracer.Start(ctx, "handler.create_transaction.delete_race_condition")
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ spanReleaseLock.End()
+
go handler.logTransaction(ctx, operations, organizationID, ledgerID, tran.IDtoUUID())
return http.Created(c, tran)
}
+// getAccounts is a function that split aliases and ids, call the properly function and return Accounts
+func (handler *TransactionHandler) getAccountsAndValidate(ctx context.Context, logger mlog.Logger, token, hash string, organizationID, ledgerID uuid.UUID, validate *goldModel.Responses, body goldModel.Transaction) ([]*account.Account, error) {
+ span := trace.SpanFromContext(ctx)
+ defer span.End()
+
+ accounts, err := handler.Query.GetAccountsLedger(ctx, logger, token, organizationID, ledgerID, validate.Aliases)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get accounts", err)
+
+ return nil, err
+ }
+
+ err = goldModel.ValidateAccounts(body, *validate, accounts)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to validate accounts", err)
+
+ handler.Command.DeleteLocks(ctx, organizationID, ledgerID, validate.Aliases, hash)
+
+ return nil, err
+ }
+
+ searchAgain, err := handler.Command.LockBalanceVersion(ctx, organizationID, ledgerID, validate.Aliases, accounts)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err)
+
+ logger.Error("Failed to get account by alias gRPC on Ledger", err.Error())
+
+ return nil, err
+ }
+
+ if searchAgain {
+ return handler.getAccountsAndValidate(ctx, logger, token, hash, organizationID, ledgerID, validate, body)
+ }
+
+ return accounts, nil
+}
+
// logTransaction creates a message representing a transaction log and sends to auditing exchange
func (handler *TransactionHandler) logTransaction(ctx context.Context, operations []*operation.Operation, organizationID uuid.UUID, ledgerID uuid.UUID, transactionID uuid.UUID) {
logger := pkg.NewLoggerFromContext(ctx)
@@ -582,105 +713,6 @@ func (handler *TransactionHandler) logTransaction(ctx context.Context, operation
}
}
-// getAccounts is a function that split aliases and ids, call the properly function and return Accounts
-func (handler *TransactionHandler) getAccounts(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) {
- span := trace.SpanFromContext(ctx)
-
- var ids []string
-
- var aliases []string
-
- for _, item := range input {
- if pkg.IsUUID(item) {
- ids = append(ids, item)
- } else {
- aliases = append(aliases, item)
- }
- }
-
- var accounts []*account.Account
-
- if len(ids) > 0 {
- gRPCAccounts, err := handler.Query.AccountGRPCRepo.GetAccountsByIds(ctx, token, organizationID, ledgerID, ids)
- if err != nil {
- mopentelemetry.HandleSpanError(&span, "Failed to get account by ids gRPC on Ledger", err)
-
- logger.Error("Failed to get account gRPC by ids on Ledger", err.Error())
-
- return nil, err
- }
-
- accounts = append(accounts, gRPCAccounts.GetAccounts()...)
- }
-
- if len(aliases) > 0 {
- gRPCAccounts, err := handler.Query.AccountGRPCRepo.GetAccountsByAlias(ctx, token, organizationID, ledgerID, aliases)
- if err != nil {
- mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err)
-
- logger.Error("Failed to get account by alias gRPC on Ledger", err.Error())
-
- return nil, err
- }
-
- accounts = append(accounts, gRPCAccounts.GetAccounts()...)
- }
-
- return accounts, nil
-}
-
-// processAccounts is a function that adjust balance on Accounts
-func (handler *TransactionHandler) processAccounts(ctx context.Context, logger mlog.Logger, validate goldModel.Responses, token string, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error {
- span := trace.SpanFromContext(ctx)
-
- e := make(chan error)
- result := make(chan []*account.Account)
-
- var accountsToUpdate []*account.Account
-
- go goldModel.UpdateAccounts(constant.DEBIT, validate.From, accounts, result, e)
- select {
- case r := <-result:
- accountsToUpdate = append(accountsToUpdate, r...)
- case err := <-e:
- mopentelemetry.HandleSpanError(&span, "Failed to update debit accounts", err)
-
- return err
- }
-
- go goldModel.UpdateAccounts(constant.CREDIT, validate.To, accounts, result, e)
- select {
- case r := <-result:
- accountsToUpdate = append(accountsToUpdate, r...)
- case err := <-e:
- mopentelemetry.HandleSpanError(&span, "Failed to update credit accounts", err)
-
- return err
- }
-
- err := mopentelemetry.SetSpanAttributesFromStruct(&span, "payload_grpc_update_accounts", accountsToUpdate)
- if err != nil {
- mopentelemetry.HandleSpanError(&span, "Failed to convert accountsToUpdate from struct to JSON string", err)
-
- return err
- }
-
- acc, err := handler.Command.AccountGRPCRepo.UpdateAccounts(ctx, token, organizationID, ledgerID, accountsToUpdate)
- if err != nil {
- mopentelemetry.HandleSpanError(&span, "Failed to update accounts gRPC on Ledger", err)
-
- logger.Error("Failed to update accounts gRPC on Ledger", err.Error())
-
- return err
- }
-
- for _, a := range acc.Accounts {
- logger.Infof(a.UpdatedAt)
- }
-
- return nil
-}
-
func isAuditLogEnabled() bool {
envValue := strings.ToLower(strings.TrimSpace(os.Getenv("AUDIT_LOG_ENABLED")))
return envValue != "false"
diff --git a/components/transaction/internal/adapters/postgres/transaction/transaction.go b/components/transaction/internal/adapters/postgres/transaction/transaction.go
index eab9e2fb..2cc467cd 100644
--- a/components/transaction/internal/adapters/postgres/transaction/transaction.go
+++ b/components/transaction/internal/adapters/postgres/transaction/transaction.go
@@ -25,6 +25,7 @@ type TransactionPostgreSQLModel struct {
ChartOfAccountsGroupName string
LedgerID string
OrganizationID string
+ Body goldModel.Transaction
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt sql.NullTime
@@ -92,6 +93,7 @@ type Transaction struct {
Destination []string `json:"destination" example:"@person2"`
LedgerID string `json:"ledgerId" example:"00000000-0000-0000-0000-000000000000"`
OrganizationID string `json:"organizationId" example:"00000000-0000-0000-0000-000000000000"`
+ Body goldModel.Transaction `json:"-"`
CreatedAt time.Time `json:"createdAt" example:"2021-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updatedAt" example:"2021-01-01T00:00:00Z"`
DeletedAt *time.Time `json:"deletedAt" example:"2021-01-01T00:00:00Z"`
@@ -123,6 +125,7 @@ func (t *TransactionPostgreSQLModel) ToEntity() *Transaction {
ChartOfAccountsGroupName: t.ChartOfAccountsGroupName,
LedgerID: t.LedgerID,
OrganizationID: t.OrganizationID,
+ Body: t.Body,
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
}
@@ -150,6 +153,7 @@ func (t *TransactionPostgreSQLModel) FromEntity(transaction *Transaction) {
ChartOfAccountsGroupName: transaction.ChartOfAccountsGroupName,
LedgerID: transaction.LedgerID,
OrganizationID: transaction.OrganizationID,
+ Body: transaction.Body,
CreatedAt: transaction.CreatedAt,
UpdatedAt: transaction.UpdatedAt,
}
@@ -180,3 +184,49 @@ func (cti *CreateTransactionInput) FromDSl() *goldModel.Transaction {
return dsl
}
+
+// TransactionRevert is a func that revert transaction
+func (t Transaction) TransactionRevert() goldModel.Transaction {
+ froms := make([]goldModel.FromTo, 0)
+
+ for _, to := range t.Body.Send.Distribute.To {
+ to.IsFrom = true
+ froms = append(froms, to)
+ }
+
+ newSource := goldModel.Source{
+ From: froms,
+ Remaining: t.Body.Send.Distribute.Remaining,
+ }
+
+ tos := make([]goldModel.FromTo, 0)
+
+ for _, from := range t.Body.Send.Source.From {
+ from.IsFrom = false
+ tos = append(tos, from)
+ }
+
+ newDistribute := goldModel.Distribute{
+ To: tos,
+ Remaining: t.Body.Send.Source.Remaining,
+ }
+
+ send := goldModel.Send{
+ Asset: t.Body.Send.Asset,
+ Value: t.Body.Send.Value,
+ Scale: t.Body.Send.Scale,
+ Source: newSource,
+ Distribute: newDistribute,
+ }
+
+ transaction := goldModel.Transaction{
+ ChartOfAccountsGroupName: t.Body.ChartOfAccountsGroupName,
+ Description: t.Body.Description,
+ Code: t.Body.Code,
+ Pending: t.Body.Pending,
+ Metadata: t.Body.Metadata,
+ Send: send,
+ }
+
+ return transaction
+}
diff --git a/components/transaction/internal/adapters/postgres/transaction/transaction.mock.go b/components/transaction/internal/adapters/postgres/transaction/transaction.mock.go
index c6c62931..38dec921 100644
--- a/components/transaction/internal/adapters/postgres/transaction/transaction.mock.go
+++ b/components/transaction/internal/adapters/postgres/transaction/transaction.mock.go
@@ -22,7 +22,6 @@ import (
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
- isgomock struct{}
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository.
@@ -43,53 +42,53 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
}
// Create mocks base method.
-func (m *MockRepository) Create(ctx context.Context, transaction *Transaction) (*Transaction, error) {
+func (m *MockRepository) Create(arg0 context.Context, arg1 *Transaction) (*Transaction, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Create", ctx, transaction)
+ ret := m.ctrl.Call(m, "Create", arg0, arg1)
ret0, _ := ret[0].(*Transaction)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
-func (mr *MockRepositoryMockRecorder) Create(ctx, transaction any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Create(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), ctx, transaction)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockRepository)(nil).Create), arg0, arg1)
}
// Delete mocks base method.
-func (m *MockRepository) Delete(ctx context.Context, organizationID, ledgerID, id uuid.UUID) error {
+func (m *MockRepository) Delete(arg0 context.Context, arg1, arg2, arg3 uuid.UUID) error {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Delete", ctx, organizationID, ledgerID, id)
+ ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
-func (mr *MockRepositoryMockRecorder) Delete(ctx, organizationID, ledgerID, id any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Delete(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), ctx, organizationID, ledgerID, id)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), arg0, arg1, arg2, arg3)
}
// Find mocks base method.
-func (m *MockRepository) Find(ctx context.Context, organizationID, ledgerID, id uuid.UUID) (*Transaction, error) {
+func (m *MockRepository) Find(arg0 context.Context, arg1, arg2, arg3 uuid.UUID) (*Transaction, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Find", ctx, organizationID, ledgerID, id)
+ ret := m.ctrl.Call(m, "Find", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(*Transaction)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Find indicates an expected call of Find.
-func (mr *MockRepositoryMockRecorder) Find(ctx, organizationID, ledgerID, id any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Find(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), ctx, organizationID, ledgerID, id)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockRepository)(nil).Find), arg0, arg1, arg2, arg3)
}
// FindAll mocks base method.
-func (m *MockRepository) FindAll(ctx context.Context, organizationID, ledgerID uuid.UUID, filter http.Pagination) ([]*Transaction, http.CursorPagination, error) {
+func (m *MockRepository) FindAll(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 http.Pagination) ([]*Transaction, http.CursorPagination, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "FindAll", ctx, organizationID, ledgerID, filter)
+ ret := m.ctrl.Call(m, "FindAll", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]*Transaction)
ret1, _ := ret[1].(http.CursorPagination)
ret2, _ := ret[2].(error)
@@ -97,37 +96,52 @@ func (m *MockRepository) FindAll(ctx context.Context, organizationID, ledgerID u
}
// FindAll indicates an expected call of FindAll.
-func (mr *MockRepositoryMockRecorder) FindAll(ctx, organizationID, ledgerID, filter any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) FindAll(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), ctx, organizationID, ledgerID, filter)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindAll", reflect.TypeOf((*MockRepository)(nil).FindAll), arg0, arg1, arg2, arg3)
+}
+
+// FindByParentID mocks base method.
+func (m *MockRepository) FindByParentID(arg0 context.Context, arg1, arg2, arg3 uuid.UUID) (*Transaction, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "FindByParentID", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].(*Transaction)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// FindByParentID indicates an expected call of FindByParentID.
+func (mr *MockRepositoryMockRecorder) FindByParentID(arg0, arg1, arg2, arg3 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByParentID", reflect.TypeOf((*MockRepository)(nil).FindByParentID), arg0, arg1, arg2, arg3)
}
// ListByIDs mocks base method.
-func (m *MockRepository) ListByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*Transaction, error) {
+func (m *MockRepository) ListByIDs(arg0 context.Context, arg1, arg2 uuid.UUID, arg3 []uuid.UUID) ([]*Transaction, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ListByIDs", ctx, organizationID, ledgerID, ids)
+ ret := m.ctrl.Call(m, "ListByIDs", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].([]*Transaction)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListByIDs indicates an expected call of ListByIDs.
-func (mr *MockRepositoryMockRecorder) ListByIDs(ctx, organizationID, ledgerID, ids any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) ListByIDs(arg0, arg1, arg2, arg3 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), ctx, organizationID, ledgerID, ids)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByIDs", reflect.TypeOf((*MockRepository)(nil).ListByIDs), arg0, arg1, arg2, arg3)
}
// Update mocks base method.
-func (m *MockRepository) Update(ctx context.Context, organizationID, ledgerID, id uuid.UUID, transaction *Transaction) (*Transaction, error) {
+func (m *MockRepository) Update(arg0 context.Context, arg1, arg2, arg3 uuid.UUID, arg4 *Transaction) (*Transaction, error) {
m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "Update", ctx, organizationID, ledgerID, id, transaction)
+ ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*Transaction)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
-func (mr *MockRepositoryMockRecorder) Update(ctx, organizationID, ledgerID, id, transaction any) *gomock.Call {
+func (mr *MockRepositoryMockRecorder) Update(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), ctx, organizationID, ledgerID, id, transaction)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRepository)(nil).Update), arg0, arg1, arg2, arg3, arg4)
}
diff --git a/components/transaction/internal/adapters/postgres/transaction/transaction.postgresql.go b/components/transaction/internal/adapters/postgres/transaction/transaction.postgresql.go
index ff98293d..e9db33f2 100644
--- a/components/transaction/internal/adapters/postgres/transaction/transaction.postgresql.go
+++ b/components/transaction/internal/adapters/postgres/transaction/transaction.postgresql.go
@@ -3,6 +3,7 @@ package transaction
import (
"context"
"database/sql"
+ "encoding/json"
"errors"
"github.com/LerianStudio/midaz/pkg/mpointers"
"github.com/LerianStudio/midaz/pkg/net/http"
@@ -28,6 +29,7 @@ type Repository interface {
Create(ctx context.Context, transaction *Transaction) (*Transaction, error)
FindAll(ctx context.Context, organizationID, ledgerID uuid.UUID, filter http.Pagination) ([]*Transaction, http.CursorPagination, error)
Find(ctx context.Context, organizationID, ledgerID, id uuid.UUID) (*Transaction, error)
+ FindByParentID(ctx context.Context, organizationID, ledgerID, parentID uuid.UUID) (*Transaction, error)
ListByIDs(ctx context.Context, organizationID, ledgerID uuid.UUID, ids []uuid.UUID) ([]*Transaction, error)
Update(ctx context.Context, organizationID, ledgerID, id uuid.UUID, transaction *Transaction) (*Transaction, error)
Delete(ctx context.Context, organizationID, ledgerID, id uuid.UUID) error
@@ -80,7 +82,7 @@ func (r *TransactionPostgreSQLRepository) Create(ctx context.Context, transactio
return nil, err
}
- result, err := db.ExecContext(ctx, `INSERT INTO transaction VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING *`,
+ result, err := db.ExecContext(ctx, `INSERT INTO transaction VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING *`,
record.ID,
record.ParentTransactionID,
record.Description,
@@ -93,6 +95,7 @@ func (r *TransactionPostgreSQLRepository) Create(ctx context.Context, transactio
record.ChartOfAccountsGroupName,
record.LedgerID,
record.OrganizationID,
+ record.Body,
record.CreatedAt,
record.UpdatedAt,
record.DeletedAt,
@@ -184,6 +187,9 @@ func (r *TransactionPostgreSQLRepository) FindAll(ctx context.Context, organizat
for rows.Next() {
var transaction TransactionPostgreSQLModel
+
+ var body string
+
if err := rows.Scan(
&transaction.ID,
&transaction.ParentTransactionID,
@@ -197,6 +203,7 @@ func (r *TransactionPostgreSQLRepository) FindAll(ctx context.Context, organizat
&transaction.ChartOfAccountsGroupName,
&transaction.LedgerID,
&transaction.OrganizationID,
+ &body,
&transaction.CreatedAt,
&transaction.UpdatedAt,
&transaction.DeletedAt,
@@ -206,6 +213,13 @@ func (r *TransactionPostgreSQLRepository) FindAll(ctx context.Context, organizat
return nil, http.CursorPagination{}, err
}
+ err = json.Unmarshal([]byte(body), &transaction.Body)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to unmarshal address", err)
+
+ return nil, http.CursorPagination{}, err
+ }
+
transactions = append(transactions, transaction.ToEntity())
}
@@ -260,6 +274,9 @@ func (r *TransactionPostgreSQLRepository) ListByIDs(ctx context.Context, organiz
for rows.Next() {
var transaction TransactionPostgreSQLModel
+
+ var body string
+
if err := rows.Scan(
&transaction.ID,
&transaction.ParentTransactionID,
@@ -273,6 +290,7 @@ func (r *TransactionPostgreSQLRepository) ListByIDs(ctx context.Context, organiz
&transaction.ChartOfAccountsGroupName,
&transaction.LedgerID,
&transaction.OrganizationID,
+ &body,
&transaction.CreatedAt,
&transaction.UpdatedAt,
&transaction.DeletedAt,
@@ -282,6 +300,13 @@ func (r *TransactionPostgreSQLRepository) ListByIDs(ctx context.Context, organiz
return nil, err
}
+ err = json.Unmarshal([]byte(body), &transaction.Body)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to unmarshal address", err)
+
+ return nil, err
+ }
+
transactions = append(transactions, transaction.ToEntity())
}
@@ -310,6 +335,8 @@ func (r *TransactionPostgreSQLRepository) Find(ctx context.Context, organization
transaction := &TransactionPostgreSQLModel{}
+ var body string
+
ctx, spanQuery := tracer.Start(ctx, "postgres.find.query")
row := db.QueryRowContext(ctx, "SELECT * FROM transaction WHERE organization_id = $1 AND ledger_id = $2 AND id = $3 AND deleted_at IS NULL",
@@ -330,6 +357,7 @@ func (r *TransactionPostgreSQLRepository) Find(ctx context.Context, organization
&transaction.ChartOfAccountsGroupName,
&transaction.LedgerID,
&transaction.OrganizationID,
+ &body,
&transaction.CreatedAt,
&transaction.UpdatedAt,
&transaction.DeletedAt,
@@ -343,6 +371,75 @@ func (r *TransactionPostgreSQLRepository) Find(ctx context.Context, organization
return nil, err
}
+ err = json.Unmarshal([]byte(body), &transaction.Body)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to unmarshal address", err)
+
+ return nil, err
+ }
+
+ return transaction.ToEntity(), nil
+}
+
+// FindByParentID retrieves a Transaction entity from the database using the provided parent ID.
+func (r *TransactionPostgreSQLRepository) FindByParentID(ctx context.Context, organizationID, ledgerID, parentID uuid.UUID) (*Transaction, error) {
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "postgres.find_transaction")
+ defer span.End()
+
+ db, err := r.connection.GetDB()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get database connection", err)
+
+ return nil, err
+ }
+
+ transaction := &TransactionPostgreSQLModel{}
+
+ var body string
+
+ ctx, spanQuery := tracer.Start(ctx, "postgres.find.query")
+
+ row := db.QueryRowContext(ctx, "SELECT * FROM transaction WHERE organization_id = $1 AND ledger_id = $2 AND parent_transaction_id = $3 AND deleted_at IS NULL",
+ organizationID, ledgerID, parentID)
+
+ spanQuery.End()
+
+ if err := row.Scan(
+ &transaction.ID,
+ &transaction.ParentTransactionID,
+ &transaction.Description,
+ &transaction.Template,
+ &transaction.Status,
+ &transaction.StatusDescription,
+ &transaction.Amount,
+ &transaction.AmountScale,
+ &transaction.AssetCode,
+ &transaction.ChartOfAccountsGroupName,
+ &transaction.LedgerID,
+ &transaction.OrganizationID,
+ &body,
+ &transaction.CreatedAt,
+ &transaction.UpdatedAt,
+ &transaction.DeletedAt,
+ ); err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to scan row", err)
+
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, nil
+ }
+
+ return nil, err
+ }
+
+ err = json.Unmarshal([]byte(body), &transaction.Body)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to unmarshal address", err)
+
+ return nil, err
+ }
+
return transaction.ToEntity(), nil
}
diff --git a/components/transaction/internal/adapters/redis/consumer.redis.go b/components/transaction/internal/adapters/redis/consumer.redis.go
index 22bb5574..d6036951 100644
--- a/components/transaction/internal/adapters/redis/consumer.redis.go
+++ b/components/transaction/internal/adapters/redis/consumer.redis.go
@@ -14,8 +14,10 @@ import (
//go:generate mockgen --destination=redis.mock.go --package=redis . RedisRepository
type RedisRepository interface {
Set(ctx context.Context, key, value string, ttl time.Duration) error
+ SetNX(ctx context.Context, key, value string, ttl time.Duration) (bool, error)
Get(ctx context.Context, key string) (string, error)
Del(ctx context.Context, key string) error
+ Incr(ctx context.Context, key string) int64
}
// RedisConsumerRepository is a Redis implementation of the Redis consumer.
@@ -61,6 +63,32 @@ func (rr *RedisConsumerRepository) Set(ctx context.Context, key, value string, t
return nil
}
+func (rr *RedisConsumerRepository) SetNX(ctx context.Context, key, value string, ttl time.Duration) (bool, error) {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "redis.set_nx")
+ defer span.End()
+
+ rds, err := rr.conn.GetClient(ctx)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get redis", err)
+
+ return false, err
+ }
+
+ logger.Infof("value of ttl: %v", ttl*time.Second)
+
+ isLocked, err := rds.SetNX(ctx, key, value, ttl*time.Second).Result()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to set nx on redis", err)
+
+ return false, err
+ }
+
+ return isLocked, nil
+}
+
func (rr *RedisConsumerRepository) Get(ctx context.Context, key string) (string, error) {
logger := pkg.NewLoggerFromContext(ctx)
tracer := pkg.NewTracerFromContext(ctx)
@@ -88,5 +116,43 @@ func (rr *RedisConsumerRepository) Get(ctx context.Context, key string) (string,
}
func (rr *RedisConsumerRepository) Del(ctx context.Context, key string) error {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "redis.del")
+ defer span.End()
+
+ rds, err := rr.conn.GetClient(ctx)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to del redis", err)
+
+ return err
+ }
+
+ val, err := rds.Del(ctx, key).Result()
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to del on redis", err)
+
+ return err
+ }
+
+ logger.Infof("value : %v", val)
+
return nil
}
+
+func (rr *RedisConsumerRepository) Incr(ctx context.Context, key string) int64 {
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "redis.incr")
+ defer span.End()
+
+ rds, err := rr.conn.GetClient(ctx)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get redis", err)
+
+ return 0
+ }
+
+ return rds.Incr(ctx, key).Val()
+}
diff --git a/components/transaction/internal/adapters/redis/redis.mock.go b/components/transaction/internal/adapters/redis/redis.mock.go
index 3506c88b..b3ea9588 100644
--- a/components/transaction/internal/adapters/redis/redis.mock.go
+++ b/components/transaction/internal/adapters/redis/redis.mock.go
@@ -69,6 +69,20 @@ func (mr *MockRedisRepositoryMockRecorder) Get(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRedisRepository)(nil).Get), arg0, arg1)
}
+// Incr mocks base method.
+func (m *MockRedisRepository) Incr(arg0 context.Context, arg1 string) int64 {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Incr", arg0, arg1)
+ ret0, _ := ret[0].(int64)
+ return ret0
+}
+
+// Incr indicates an expected call of Incr.
+func (mr *MockRedisRepositoryMockRecorder) Incr(arg0, arg1 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Incr", reflect.TypeOf((*MockRedisRepository)(nil).Incr), arg0, arg1)
+}
+
// Set mocks base method.
func (m *MockRedisRepository) Set(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) error {
m.ctrl.T.Helper()
@@ -82,3 +96,18 @@ func (mr *MockRedisRepositoryMockRecorder) Set(arg0, arg1, arg2, arg3 any) *gomo
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockRedisRepository)(nil).Set), arg0, arg1, arg2, arg3)
}
+
+// SetNX mocks base method.
+func (m *MockRedisRepository) SetNX(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SetNX", arg0, arg1, arg2, arg3)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SetNX indicates an expected call of SetNX.
+func (mr *MockRedisRepositoryMockRecorder) SetNX(arg0, arg1, arg2, arg3 any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNX", reflect.TypeOf((*MockRedisRepository)(nil).SetNX), arg0, arg1, arg2, arg3)
+}
diff --git a/components/transaction/internal/services/command/create-idempotency-key.go b/components/transaction/internal/services/command/create-idempotency-key.go
index 15f8a393..5b994c14 100644
--- a/components/transaction/internal/services/command/create-idempotency-key.go
+++ b/components/transaction/internal/services/command/create-idempotency-key.go
@@ -13,7 +13,7 @@ func (uc *UseCase) CreateOrCheckIdempotencyKey(ctx context.Context, organization
logger := pkg.NewLoggerFromContext(ctx)
tracer := pkg.NewTracerFromContext(ctx)
- ctx, span := tracer.Start(ctx, "command.create-idempotency-key")
+ _, span := tracer.Start(ctx, "command.create-idempotency-key")
defer span.End()
logger.Infof("Trying to create or check idempotency key in redis")
@@ -22,19 +22,14 @@ func (uc *UseCase) CreateOrCheckIdempotencyKey(ctx context.Context, organization
key = hash
}
- internalKey := organizationID.String() + ":" + ledgerID.String() + ":" + key
+ internalKey := pkg.InternalKey(organizationID, ledgerID, key)
- value, err := uc.RedisRepo.Get(ctx, internalKey)
+ success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, ttl)
if err != nil {
- logger.Error("Error to get idempotency key on redis failed:", err.Error())
+ logger.Error("Error to lock idempotency key on redis failed:", err.Error())
}
- if value == "" {
- err = uc.RedisRepo.Set(ctx, internalKey, hash, ttl)
- if err != nil {
- logger.Error("Error to set idempotency key on redis failed:", err.Error())
- }
- } else {
+ if !success {
err = pkg.ValidateBusinessError(constant.ErrIdempotencyKey, "CreateOrCheckIdempotencyKey", key)
mopentelemetry.HandleSpanError(&span, "Failed exists value on redis with this key", err)
diff --git a/components/transaction/internal/services/command/create-lock-race-condition.go b/components/transaction/internal/services/command/create-lock-race-condition.go
new file mode 100644
index 00000000..66789fc1
--- /dev/null
+++ b/components/transaction/internal/services/command/create-lock-race-condition.go
@@ -0,0 +1,148 @@
+package command
+
+import (
+ "context"
+ "errors"
+ "github.com/LerianStudio/midaz/pkg"
+ "github.com/LerianStudio/midaz/pkg/constant"
+ "github.com/LerianStudio/midaz/pkg/mgrpc/account"
+ "github.com/LerianStudio/midaz/pkg/mopentelemetry"
+ "github.com/google/uuid"
+ "github.com/redis/go-redis/v9"
+ "strconv"
+ "sync"
+ "time"
+)
+
+func (uc *UseCase) AllKeysUnlocked(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) {
+ logger := pkg.NewLoggerFromContext(context.Background())
+ tracer := pkg.NewTracerFromContext(context.Background())
+
+ ctx, span := tracer.Start(ctx, "redis.all_keys_unlocked")
+ defer span.End()
+
+ var wg sync.WaitGroup
+
+ resultChan := make(chan bool, len(keys))
+
+ for _, key := range keys {
+ internalKey := pkg.LockInternalKey(organizationID, ledgerID, key)
+
+ logger.Infof("Account try to lock on redis: %v", internalKey)
+
+ wg.Add(1)
+
+ go uc.checkAndReleaseLock(ctx, &wg, internalKey, hash, resultChan)
+ }
+
+ wg.Wait()
+ close(resultChan)
+}
+
+func (uc *UseCase) checkAndReleaseLock(ctx context.Context, wg *sync.WaitGroup, internalKey, hash string, resultChan chan bool) {
+ logger := pkg.NewLoggerFromContext(context.Background())
+ tracer := pkg.NewTracerFromContext(context.Background())
+
+ _, span := tracer.Start(ctx, "redis.check_and_release_lock")
+ defer span.End()
+
+ defer wg.Done()
+
+ for {
+ success, err := uc.RedisRepo.SetNX(context.Background(), internalKey, hash, constant.TimeSetLock)
+ if err != nil {
+ resultChan <- false
+ return
+ }
+
+ logger.Infof("Account locked on redis: %v", internalKey)
+
+ if success {
+ resultChan <- true
+ return
+ }
+
+ time.Sleep(constant.CheckAndReleaseLock * time.Millisecond)
+ }
+}
+
+func (uc *UseCase) DeleteLocks(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, hash string) {
+ logger := pkg.NewLoggerFromContext(context.Background())
+ tracer := pkg.NewTracerFromContext(context.Background())
+
+ ctx, span := tracer.Start(ctx, "redis.delete_locks")
+ defer span.End()
+
+ for _, key := range keys {
+ internalKey := pkg.LockInternalKey(organizationID, ledgerID, key)
+
+ logger.Infof("Account releasing lock on redis: %v", internalKey)
+
+ val, err := uc.RedisRepo.Get(ctx, key)
+ if !errors.Is(err, redis.Nil) && err != nil && val == hash {
+ err = uc.RedisRepo.Del(ctx, internalKey)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to release Accounts lock", err)
+
+ logger.Errorf("Failed to release Accounts lock: %v", err)
+ }
+ }
+ }
+}
+
+func (uc *UseCase) LockBalanceVersion(ctx context.Context, organizationID, ledgerID uuid.UUID, keys []string, accounts []*account.Account) (bool, error) {
+ logger := pkg.NewLoggerFromContext(context.Background())
+ tracer := pkg.NewTracerFromContext(context.Background())
+
+ ctx, span := tracer.Start(ctx, "redis.lock_balance_version")
+ defer span.End()
+
+ accountsMap := make(map[string]*account.Account)
+ for _, acc := range accounts {
+ accountsMap[acc.Id] = acc
+ accountsMap[acc.Alias] = acc
+ }
+
+ for _, key := range keys {
+ if acc, exists := accountsMap[key]; exists {
+ internalKey := pkg.LockVersionInternalKey(organizationID, ledgerID, key, strconv.FormatInt(acc.Version, 10))
+
+ logger.Infof("Account balance version releasing lock on redis: %v", internalKey)
+
+ isSuccess, err := uc.RedisRepo.SetNX(ctx, internalKey, "0", constant.TimeSetLockBalance)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to lock Account balance version: ", err)
+
+ logger.Errorf("Failed to lock Account balance version: %v", err)
+
+ return false, err
+ }
+
+ total := uc.RedisRepo.Incr(ctx, internalKey)
+ logger.Infof("%v attempt(s) to get Account balance", internalKey)
+
+ if total > constant.RedisTimesRetry {
+ err = uc.RedisRepo.Del(ctx, internalKey)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to release Account balance version lock", err)
+
+ logger.Errorf("Failed to release Account balance version lock: %v", err)
+ }
+
+ logger.Infof("Account balance version releasing lock on redis: %v", internalKey)
+
+ return false, pkg.ValidateBusinessError(constant.ErrLockVersionAccountBalance, "LockBalanceVersion")
+ }
+
+ if !isSuccess {
+ time.Sleep(constant.LockRetry * time.Millisecond)
+
+ logger.Infof("Lock already exists for key, get Accounts again: %v", internalKey)
+
+ return true, nil
+ }
+ }
+ }
+
+ return false, nil
+}
diff --git a/components/transaction/internal/services/command/create-transaction.go b/components/transaction/internal/services/command/create-transaction.go
index 9df4f4a5..31f7a768 100644
--- a/components/transaction/internal/services/command/create-transaction.go
+++ b/components/transaction/internal/services/command/create-transaction.go
@@ -16,7 +16,7 @@ import (
)
// CreateTransaction creates a new transaction persisting data in the repository.
-func (uc *UseCase) CreateTransaction(ctx context.Context, organizationID, ledgerID uuid.UUID, t *goldModel.Transaction) (*transaction.Transaction, error) {
+func (uc *UseCase) CreateTransaction(ctx context.Context, organizationID, ledgerID, transactionID uuid.UUID, t *goldModel.Transaction) (*transaction.Transaction, error) {
logger := pkg.NewLoggerFromContext(ctx)
tracer := pkg.NewTracerFromContext(ctx)
@@ -34,9 +34,16 @@ func (uc *UseCase) CreateTransaction(ctx context.Context, organizationID, ledger
amount := float64(t.Send.Value)
scale := float64(t.Send.Scale)
+ var parentTransactionID *string
+
+ if transactionID != uuid.Nil {
+ value := transactionID.String()
+ parentTransactionID = &value
+ }
+
save := &transaction.Transaction{
ID: pkg.GenerateUUIDv7().String(),
- ParentTransactionID: nil,
+ ParentTransactionID: parentTransactionID,
OrganizationID: organizationID.String(),
LedgerID: ledgerID.String(),
Description: t.Description,
@@ -46,6 +53,7 @@ func (uc *UseCase) CreateTransaction(ctx context.Context, organizationID, ledger
AmountScale: &scale,
AssetCode: t.Send.Asset,
ChartOfAccountsGroupName: t.ChartOfAccountsGroupName,
+ Body: *t,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
diff --git a/components/transaction/internal/services/command/update-accounts-ledger-grpc.go b/components/transaction/internal/services/command/update-accounts-ledger-grpc.go
new file mode 100644
index 00000000..e9fae267
--- /dev/null
+++ b/components/transaction/internal/services/command/update-accounts-ledger-grpc.go
@@ -0,0 +1,65 @@
+package command
+
+import (
+ "context"
+
+ "github.com/LerianStudio/midaz/pkg/constant"
+ goldModel "github.com/LerianStudio/midaz/pkg/gold/transaction/model"
+ "github.com/LerianStudio/midaz/pkg/mgrpc/account"
+ "github.com/LerianStudio/midaz/pkg/mlog"
+ "github.com/LerianStudio/midaz/pkg/mopentelemetry"
+ "github.com/google/uuid"
+ "go.opentelemetry.io/otel/trace"
+)
+
+// UpdateAccounts methods that is responsible to update accounts on ledger by gRpc.
+func (uc *UseCase) UpdateAccounts(ctx context.Context, logger mlog.Logger, validate goldModel.Responses, token string, organizationID, ledgerID uuid.UUID, accounts []*account.Account) error {
+ span := trace.SpanFromContext(ctx)
+
+ e := make(chan error)
+ result := make(chan []*account.Account)
+
+ var accountsToUpdate []*account.Account
+
+ go goldModel.UpdateAccounts(constant.DEBIT, validate.From, accounts, result, e)
+ select {
+ case r := <-result:
+ accountsToUpdate = append(accountsToUpdate, r...)
+ case err := <-e:
+ mopentelemetry.HandleSpanError(&span, "Failed to update debit accounts", err)
+
+ return err
+ }
+
+ go goldModel.UpdateAccounts(constant.CREDIT, validate.To, accounts, result, e)
+ select {
+ case r := <-result:
+ accountsToUpdate = append(accountsToUpdate, r...)
+ case err := <-e:
+ mopentelemetry.HandleSpanError(&span, "Failed to update credit accounts", err)
+
+ return err
+ }
+
+ err := mopentelemetry.SetSpanAttributesFromStruct(&span, "payload_grpc_update_accounts", accountsToUpdate)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to convert accountsToUpdate from struct to JSON string", err)
+
+ return err
+ }
+
+ acc, err := uc.AccountGRPCRepo.UpdateAccounts(ctx, token, organizationID, ledgerID, accountsToUpdate)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to update accounts gRPC on Ledger", err)
+
+ logger.Error("Failed to update accounts gRPC on Ledger", err.Error())
+
+ return err
+ }
+
+ for _, a := range acc.Accounts {
+ logger.Infof(a.UpdatedAt)
+ }
+
+ return nil
+}
diff --git a/components/transaction/internal/services/command/update-transaction.go b/components/transaction/internal/services/command/update-transaction.go
index dcca08b7..349bccd5 100644
--- a/components/transaction/internal/services/command/update-transaction.go
+++ b/components/transaction/internal/services/command/update-transaction.go
@@ -80,7 +80,7 @@ func (uc *UseCase) UpdateTransactionStatus(ctx context.Context, organizationID,
Status: status,
}
- transUpdated, err := uc.TransactionRepo.Update(ctx, organizationID, ledgerID, transactionID, trans)
+ _, err := uc.TransactionRepo.Update(ctx, organizationID, ledgerID, transactionID, trans)
if err != nil {
mopentelemetry.HandleSpanError(&span, "Failed to update status transaction on repo by id", err)
@@ -93,5 +93,5 @@ func (uc *UseCase) UpdateTransactionStatus(ctx context.Context, organizationID,
return nil, err
}
- return transUpdated, nil
+ return nil, nil
}
diff --git a/components/transaction/internal/services/query/get-accounts-ledger-grpc.go b/components/transaction/internal/services/query/get-accounts-ledger-grpc.go
new file mode 100644
index 00000000..766de9f9
--- /dev/null
+++ b/components/transaction/internal/services/query/get-accounts-ledger-grpc.go
@@ -0,0 +1,58 @@
+package query
+
+import (
+ "context"
+ "github.com/LerianStudio/midaz/pkg"
+ "github.com/LerianStudio/midaz/pkg/mgrpc/account"
+ "github.com/LerianStudio/midaz/pkg/mlog"
+ "github.com/LerianStudio/midaz/pkg/mopentelemetry"
+ "github.com/google/uuid"
+ "go.opentelemetry.io/otel/trace"
+)
+
+// GetAccountsLedger methods responsible to get accounts on ledger by gRpc.
+func (uc *UseCase) GetAccountsLedger(ctx context.Context, logger mlog.Logger, token string, organizationID, ledgerID uuid.UUID, input []string) ([]*account.Account, error) {
+ span := trace.SpanFromContext(ctx)
+
+ var ids []string
+
+ var aliases []string
+
+ for _, item := range input {
+ if pkg.IsUUID(item) {
+ ids = append(ids, item)
+ } else {
+ aliases = append(aliases, item)
+ }
+ }
+
+ var accounts []*account.Account
+
+ if len(ids) > 0 {
+ gRPCAccounts, err := uc.AccountGRPCRepo.GetAccountsByIds(ctx, token, organizationID, ledgerID, ids)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get account by ids gRPC on Ledger", err)
+
+ logger.Error("Failed to get account gRPC by ids on Ledger", err.Error())
+
+ return nil, err
+ }
+
+ accounts = append(accounts, gRPCAccounts.GetAccounts()...)
+ }
+
+ if len(aliases) > 0 {
+ gRPCAccounts, err := uc.AccountGRPCRepo.GetAccountsByAlias(ctx, token, organizationID, ledgerID, aliases)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get account by alias gRPC on Ledger", err)
+
+ logger.Error("Failed to get account by alias gRPC on Ledger", err.Error())
+
+ return nil, err
+ }
+
+ accounts = append(accounts, gRPCAccounts.GetAccounts()...)
+ }
+
+ return accounts, nil
+}
diff --git a/components/transaction/internal/services/query/get-parent-id-transaction.go b/components/transaction/internal/services/query/get-parent-id-transaction.go
new file mode 100644
index 00000000..d653a236
--- /dev/null
+++ b/components/transaction/internal/services/query/get-parent-id-transaction.go
@@ -0,0 +1,49 @@
+package query
+
+import (
+ "context"
+ "reflect"
+
+ "github.com/LerianStudio/midaz/components/transaction/internal/adapters/postgres/transaction"
+ "github.com/LerianStudio/midaz/pkg"
+ "github.com/LerianStudio/midaz/pkg/mopentelemetry"
+
+ "github.com/google/uuid"
+)
+
+// GetParentByTransactionID gets data in the repository.
+func (uc *UseCase) GetParentByTransactionID(ctx context.Context, organizationID, ledgerID, parentID uuid.UUID) (*transaction.Transaction, error) {
+ logger := pkg.NewLoggerFromContext(ctx)
+ tracer := pkg.NewTracerFromContext(ctx)
+
+ ctx, span := tracer.Start(ctx, "query.get_parent_by_transaction_id")
+ defer span.End()
+
+ logger.Infof("Trying to get transaction")
+
+ tran, err := uc.TransactionRepo.FindByParentID(ctx, organizationID, ledgerID, parentID)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get parent transaction on repo by id", err)
+
+ logger.Errorf("Error getting parent transaction: %v", err)
+
+ return nil, err
+ }
+
+ if tran != nil {
+ metadata, err := uc.MetadataRepo.FindByEntity(ctx, reflect.TypeOf(transaction.Transaction{}).Name(), tran.ID)
+ if err != nil {
+ mopentelemetry.HandleSpanError(&span, "Failed to get metadata on mongodb account", err)
+
+ logger.Errorf("Error get metadata on mongodb account: %v", err)
+
+ return nil, err
+ }
+
+ if metadata != nil {
+ tran.Metadata = metadata.Data
+ }
+ }
+
+ return tran, nil
+}
diff --git a/components/transaction/internal/services/query/get-parent-id-transaction_test.go b/components/transaction/internal/services/query/get-parent-id-transaction_test.go
new file mode 100644
index 00000000..eb07c1bd
--- /dev/null
+++ b/components/transaction/internal/services/query/get-parent-id-transaction_test.go
@@ -0,0 +1,62 @@
+package query
+
+import (
+ "context"
+ "errors"
+ "go.uber.org/mock/gomock"
+ "testing"
+
+ "github.com/LerianStudio/midaz/components/transaction/internal/adapters/postgres/transaction"
+ "github.com/LerianStudio/midaz/pkg"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetParentByTransactionID(t *testing.T) {
+ ID := pkg.GenerateUUIDv7()
+ organizationID := pkg.GenerateUUIDv7()
+ ledgerID := pkg.GenerateUUIDv7()
+ parentID := ID.String()
+
+ tran := &transaction.Transaction{
+ ParentTransactionID: &parentID,
+ OrganizationID: organizationID.String(),
+ LedgerID: ledgerID.String(),
+ }
+
+ uc := UseCase{
+ TransactionRepo: transaction.NewMockRepository(gomock.NewController(t)),
+ }
+
+ uc.TransactionRepo.(*transaction.MockRepository).
+ EXPECT().
+ FindByParentID(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(tran, nil).
+ Times(1)
+ res, err := uc.TransactionRepo.FindByParentID(context.TODO(), organizationID, ledgerID, ID)
+
+ assert.Equal(t, tran, res)
+ assert.Nil(t, err)
+}
+
+func TestGetParentByTransactionIDError(t *testing.T) {
+ errMSG := "err to create account on database"
+ ID := pkg.GenerateUUIDv7()
+ organizationID := pkg.GenerateUUIDv7()
+ ledgerID := pkg.GenerateUUIDv7()
+
+ uc := UseCase{
+ TransactionRepo: transaction.NewMockRepository(gomock.NewController(t)),
+ }
+
+ uc.TransactionRepo.(*transaction.MockRepository).
+ EXPECT().
+ FindByParentID(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(nil, errors.New(errMSG)).
+ Times(1)
+ res, err := uc.TransactionRepo.FindByParentID(context.TODO(), organizationID, ledgerID, ID)
+
+ assert.NotEmpty(t, err)
+ assert.Equal(t, err.Error(), errMSG)
+ assert.Nil(t, res)
+}
diff --git a/components/transaction/migrations/000000_create_transaction_table.up.sql b/components/transaction/migrations/000000_create_transaction_table.up.sql
index ac32ab00..0cf2e046 100644
--- a/components/transaction/migrations/000000_create_transaction_table.up.sql
+++ b/components/transaction/migrations/000000_create_transaction_table.up.sql
@@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS "transaction" (
chart_of_accounts_group_name TEXT NOT NULL,
ledger_id UUID NOT NULL,
organization_id UUID NOT NULL,
+ body JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
diff --git a/go.mod b/go.mod
index 150f9848..70ca8b54 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/gofiber/fiber/v2 v2.52.6
- github.com/google/trillian v1.7.0
+ github.com/google/trillian v1.7.1
github.com/icrowley/fake v0.0.0-20240710202011-f797eb4a99c0
github.com/jackc/pgx/v5 v5.7.2
github.com/jarcoal/httpmock v1.3.1
@@ -29,7 +29,7 @@ require (
github.com/swaggo/fiber-swagger v1.3.0
github.com/swaggo/swag v1.16.4
github.com/transparency-dev/merkle v0.0.2
- go.mongodb.org/mongo-driver v1.17.1
+ go.mongodb.org/mongo-driver v1.17.2
go.opentelemetry.io/contrib/bridges/otelzap v0.8.0
go.opentelemetry.io/otel v1.33.0
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.9.0
@@ -43,7 +43,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.33.0
go.opentelemetry.io/otel/trace v1.33.0
go.uber.org/mock v0.5.0
- google.golang.org/grpc v1.69.2
+ google.golang.org/grpc v1.69.4
google.golang.org/protobuf v1.36.2
gotest.tools v2.2.0+incompatible
)
@@ -132,7 +132,7 @@ require (
github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.17.11 // indirect
github.com/lestrrat-go/jwx v1.2.30
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
diff --git a/go.sum b/go.sum
index 1c34b968..4d339505 100644
--- a/go.sum
+++ b/go.sum
@@ -123,8 +123,8 @@ 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/trillian v1.7.0 h1:Oib7mKRvZ0Z3GjvNcn2C4clRmFouEOkBcbzw7q8JlFI=
-github.com/google/trillian v1.7.0/go.mod h1:JMp1zzzHe7j2m9m8P/eTWOaoon3R/SwgqUnFMhm4vfw=
+github.com/google/trillian v1.7.1 h1:+zX8jLM3524bAMPS+VxaDIDgsMv3/ty6DuLWerHXcek=
+github.com/google/trillian v1.7.1/go.mod h1:E1UMAHqpZCA8AQdrKdWmHmtUfSeiD0sDWD1cv00Xa+c=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -192,9 +192,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
-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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
@@ -301,8 +300,8 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zU
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
-go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
+go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
+go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/otelzap v0.8.0 h1:4jqXEd0FGULFBy1bF1ledBePc0Ssu8YVddTgr8BXDTc=
@@ -385,7 +384,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
@@ -415,8 +413,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
-google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
-google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
+google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
+google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/constant/account.go b/pkg/constant/account.go
index 24585c4e..187b25ac 100644
--- a/pkg/constant/account.go
+++ b/pkg/constant/account.go
@@ -3,4 +3,9 @@ package constant
const (
DefaultExternalAccountAliasPrefix = "@external/"
ExternalAccountType = "external"
+ TimeSetLock = 1
+ TimeSetLockBalance = 5
+ LockRetry = 100
+ RedisTimesRetry = 3
+ CheckAndReleaseLock = 200
)
diff --git a/pkg/constant/errors.go b/pkg/constant/errors.go
index 8dc2d27a..9aa50034 100644
--- a/pkg/constant/errors.go
+++ b/pkg/constant/errors.go
@@ -8,88 +8,92 @@ import (
* For more details, refer to the API documentation: https://docs.midaz.io/midaz/api-reference/resources/errors-list
*/
var (
- ErrDuplicateLedger = errors.New("0001")
- ErrLedgerNameConflict = errors.New("0002")
- ErrAssetNameOrCodeDuplicate = errors.New("0003")
- ErrCodeUppercaseRequirement = errors.New("0004")
- ErrCurrencyCodeStandardCompliance = errors.New("0005")
- ErrUnmodifiableField = errors.New("0006")
- ErrEntityNotFound = errors.New("0007")
- ErrActionNotPermitted = errors.New("0008")
- ErrMissingFieldsInRequest = errors.New("0009")
- ErrAccountTypeImmutable = errors.New("0010")
- ErrInactiveAccountType = errors.New("0011")
- ErrAccountBalanceDeletion = errors.New("0012")
- ErrResourceAlreadyDeleted = errors.New("0013")
- ErrProductIDInactive = errors.New("0014")
- ErrDuplicateProductName = errors.New("0015")
- ErrBalanceRemainingDeletion = errors.New("0016")
- ErrInvalidScriptFormat = errors.New("0017")
- ErrInsufficientFunds = errors.New("0018")
- ErrAccountIneligibility = errors.New("0019")
- ErrAliasUnavailability = errors.New("0020")
- ErrParentTransactionIDNotFound = errors.New("0021")
- ErrImmutableField = errors.New("0022")
- ErrTransactionTimingRestriction = errors.New("0023")
- ErrAccountStatusTransactionRestriction = errors.New("0024")
- ErrInsufficientAccountBalance = errors.New("0025")
- ErrTransactionMethodRestriction = errors.New("0026")
- ErrDuplicateTransactionTemplateCode = errors.New("0027")
- ErrDuplicateAssetPair = errors.New("0028")
- ErrInvalidParentAccountID = errors.New("0029")
- ErrMismatchedAssetCode = errors.New("0030")
- ErrChartTypeNotFound = errors.New("0031")
- ErrInvalidCountryCode = errors.New("0032")
- ErrInvalidCodeFormat = errors.New("0033")
- ErrAssetCodeNotFound = errors.New("0034")
- ErrPortfolioIDNotFound = errors.New("0035")
- ErrProductIDNotFound = errors.New("0036")
- ErrLedgerIDNotFound = errors.New("0037")
- ErrOrganizationIDNotFound = errors.New("0038")
- ErrParentOrganizationIDNotFound = errors.New("0039")
- ErrInvalidType = errors.New("0040")
- ErrTokenMissing = errors.New("0041")
- ErrInvalidToken = errors.New("0042")
- ErrInsufficientPrivileges = errors.New("0043")
- ErrPermissionEnforcement = errors.New("0044")
- ErrJWKFetch = errors.New("0045")
- ErrInternalServer = errors.New("0046")
- ErrBadRequest = errors.New("0047")
- ErrInvalidDSLFileFormat = errors.New("0048")
- ErrEmptyDSLFile = errors.New("0049")
- ErrMetadataKeyLengthExceeded = errors.New("0050")
- ErrMetadataValueLengthExceeded = errors.New("0051")
- ErrAccountIDNotFound = errors.New("0052")
- ErrUnexpectedFieldsInTheRequest = errors.New("0053")
- ErrIDsNotFoundForAccounts = errors.New("0054")
- ErrAssetIDNotFound = errors.New("0055")
- ErrNoAssetsFound = errors.New("0056")
- ErrNoProductsFound = errors.New("0057")
- ErrNoPortfoliosFound = errors.New("0058")
- ErrNoOrganizationsFound = errors.New("0059")
- ErrNoLedgersFound = errors.New("0060")
- ErrBalanceUpdateFailed = errors.New("0061")
- ErrNoAccountIDsProvided = errors.New("0062")
- ErrFailedToRetrieveAccountsByAliases = errors.New("0063")
- ErrNoAccountsFound = errors.New("0064")
- ErrInvalidPathParameter = errors.New("0065")
- ErrInvalidAccountType = errors.New("0066")
- ErrInvalidMetadataNesting = errors.New("0067")
- ErrOperationIDNotFound = errors.New("0068")
- ErrNoOperationsFound = errors.New("0069")
- ErrTransactionIDNotFound = errors.New("0070")
- ErrNoTransactionsFound = errors.New("0071")
- ErrInvalidTransactionType = errors.New("0072")
- ErrTransactionValueMismatch = errors.New("0073")
- ErrForbiddenExternalAccountManipulation = errors.New("0074")
- ErrAuditRecordNotRetrieved = errors.New("0075")
- ErrAuditTreeRecordNotFound = errors.New("0076")
- ErrInvalidDateFormat = errors.New("0077")
- ErrInvalidFinalDate = errors.New("0078")
- ErrDateRangeExceedsLimit = errors.New("0079")
- ErrPaginationLimitExceeded = errors.New("0080")
- ErrInvalidSortOrder = errors.New("0081")
- ErrInvalidQueryParameter = errors.New("0082")
- ErrInvalidDateRange = errors.New("0083")
- ErrIdempotencyKey = errors.New("0084")
+ ErrDuplicateLedger = errors.New("0001")
+ ErrLedgerNameConflict = errors.New("0002")
+ ErrAssetNameOrCodeDuplicate = errors.New("0003")
+ ErrCodeUppercaseRequirement = errors.New("0004")
+ ErrCurrencyCodeStandardCompliance = errors.New("0005")
+ ErrUnmodifiableField = errors.New("0006")
+ ErrEntityNotFound = errors.New("0007")
+ ErrActionNotPermitted = errors.New("0008")
+ ErrMissingFieldsInRequest = errors.New("0009")
+ ErrAccountTypeImmutable = errors.New("0010")
+ ErrInactiveAccountType = errors.New("0011")
+ ErrAccountBalanceDeletion = errors.New("0012")
+ ErrResourceAlreadyDeleted = errors.New("0013")
+ ErrProductIDInactive = errors.New("0014")
+ ErrDuplicateProductName = errors.New("0015")
+ ErrBalanceRemainingDeletion = errors.New("0016")
+ ErrInvalidScriptFormat = errors.New("0017")
+ ErrInsufficientFunds = errors.New("0018")
+ ErrAccountIneligibility = errors.New("0019")
+ ErrAliasUnavailability = errors.New("0020")
+ ErrParentTransactionIDNotFound = errors.New("0021")
+ ErrImmutableField = errors.New("0022")
+ ErrTransactionTimingRestriction = errors.New("0023")
+ ErrAccountStatusTransactionRestriction = errors.New("0024")
+ ErrInsufficientAccountBalance = errors.New("0025")
+ ErrTransactionMethodRestriction = errors.New("0026")
+ ErrDuplicateTransactionTemplateCode = errors.New("0027")
+ ErrDuplicateAssetPair = errors.New("0028")
+ ErrInvalidParentAccountID = errors.New("0029")
+ ErrMismatchedAssetCode = errors.New("0030")
+ ErrChartTypeNotFound = errors.New("0031")
+ ErrInvalidCountryCode = errors.New("0032")
+ ErrInvalidCodeFormat = errors.New("0033")
+ ErrAssetCodeNotFound = errors.New("0034")
+ ErrPortfolioIDNotFound = errors.New("0035")
+ ErrProductIDNotFound = errors.New("0036")
+ ErrLedgerIDNotFound = errors.New("0037")
+ ErrOrganizationIDNotFound = errors.New("0038")
+ ErrParentOrganizationIDNotFound = errors.New("0039")
+ ErrInvalidType = errors.New("0040")
+ ErrTokenMissing = errors.New("0041")
+ ErrInvalidToken = errors.New("0042")
+ ErrInsufficientPrivileges = errors.New("0043")
+ ErrPermissionEnforcement = errors.New("0044")
+ ErrJWKFetch = errors.New("0045")
+ ErrInternalServer = errors.New("0046")
+ ErrBadRequest = errors.New("0047")
+ ErrInvalidDSLFileFormat = errors.New("0048")
+ ErrEmptyDSLFile = errors.New("0049")
+ ErrMetadataKeyLengthExceeded = errors.New("0050")
+ ErrMetadataValueLengthExceeded = errors.New("0051")
+ ErrAccountIDNotFound = errors.New("0052")
+ ErrUnexpectedFieldsInTheRequest = errors.New("0053")
+ ErrIDsNotFoundForAccounts = errors.New("0054")
+ ErrAssetIDNotFound = errors.New("0055")
+ ErrNoAssetsFound = errors.New("0056")
+ ErrNoProductsFound = errors.New("0057")
+ ErrNoPortfoliosFound = errors.New("0058")
+ ErrNoOrganizationsFound = errors.New("0059")
+ ErrNoLedgersFound = errors.New("0060")
+ ErrBalanceUpdateFailed = errors.New("0061")
+ ErrNoAccountIDsProvided = errors.New("0062")
+ ErrFailedToRetrieveAccountsByAliases = errors.New("0063")
+ ErrNoAccountsFound = errors.New("0064")
+ ErrInvalidPathParameter = errors.New("0065")
+ ErrInvalidAccountType = errors.New("0066")
+ ErrInvalidMetadataNesting = errors.New("0067")
+ ErrOperationIDNotFound = errors.New("0068")
+ ErrNoOperationsFound = errors.New("0069")
+ ErrTransactionIDNotFound = errors.New("0070")
+ ErrNoTransactionsFound = errors.New("0071")
+ ErrInvalidTransactionType = errors.New("0072")
+ ErrTransactionValueMismatch = errors.New("0073")
+ ErrForbiddenExternalAccountManipulation = errors.New("0074")
+ ErrAuditRecordNotRetrieved = errors.New("0075")
+ ErrAuditTreeRecordNotFound = errors.New("0076")
+ ErrInvalidDateFormat = errors.New("0077")
+ ErrInvalidFinalDate = errors.New("0078")
+ ErrDateRangeExceedsLimit = errors.New("0079")
+ ErrPaginationLimitExceeded = errors.New("0080")
+ ErrInvalidSortOrder = errors.New("0081")
+ ErrInvalidQueryParameter = errors.New("0082")
+ ErrInvalidDateRange = errors.New("0083")
+ ErrIdempotencyKey = errors.New("0084")
+ ErrAccountAliasNotFound = errors.New("0085")
+ ErrLockVersionAccountBalance = errors.New("0086")
+ ErrTransactionIDHasAlreadyParentTransaction = errors.New("0087")
+ ErrTransactionIDIsAlreadyARevert = errors.New("0088")
)
diff --git a/pkg/errors.go b/pkg/errors.go
index dc418467..bda0f76b 100644
--- a/pkg/errors.go
+++ b/pkg/errors.go
@@ -783,6 +783,30 @@ func ValidateBusinessError(err error, entityType string, args ...any) error {
Title: "Duplicate Idempotency Key",
Message: fmt.Sprintf("The idempotency key %v is already in use. Please provide a unique key and try again.", args),
},
+ constant.ErrAccountAliasNotFound: ValidationError{
+ EntityType: entityType,
+ Code: constant.ErrAccountAliasNotFound.Error(),
+ Title: "Account Alias Not Found",
+ Message: "The provided account Alias does not exist in our records. Please verify the account Alias and try again.",
+ },
+ constant.ErrLockVersionAccountBalance: ValidationError{
+ EntityType: entityType,
+ Code: constant.ErrLockVersionAccountBalance.Error(),
+ Title: "Race conditioning detected",
+ Message: "A race condition was detected while processing your request. Please try again",
+ },
+ constant.ErrTransactionIDHasAlreadyParentTransaction: ValidationError{
+ EntityType: entityType,
+ Code: constant.ErrTransactionIDHasAlreadyParentTransaction.Error(),
+ Title: "Transaction Revert already exist",
+ Message: "Transaction revert already exists. Please try again.",
+ },
+ constant.ErrTransactionIDIsAlreadyARevert: ValidationError{
+ EntityType: entityType,
+ Code: constant.ErrTransactionIDIsAlreadyARevert.Error(),
+ Title: "Transaction is already a reversal",
+ Message: "Transaction is already a reversal. Please try again",
+ },
}
if mappedError, found := errorMap[err]; found {
diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go
index ef118273..be98aa8c 100644
--- a/pkg/gold/transaction/model/validations.go
+++ b/pkg/gold/transaction/model/validations.go
@@ -11,7 +11,7 @@ import (
)
// ValidateAccounts function with some validates in accounts and DSL operations
-func ValidateAccounts(validate Responses, accounts []*a.Account) error {
+func ValidateAccounts(transaction Transaction, validate Responses, accounts []*a.Account) error {
if len(accounts) != (len(validate.From) + len(validate.To)) {
return pkg.ValidateBusinessError(constant.ErrAccountIneligibility, "ValidateAccounts")
}
@@ -24,6 +24,26 @@ func ValidateAccounts(validate Responses, accounts []*a.Account) error {
if err := validateToAccounts(acc, validate.To, validate.Asset); err != nil {
return err
}
+
+ if err := validateBalance(transaction, validate.From, acc); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func validateBalance(dsl Transaction, from map[string]Amount, acc *a.Account) error {
+ for key := range from {
+ for _, f := range dsl.Send.Source.From {
+ if acc.Id == key || acc.Alias == key {
+ ba := OperateAmounts(from[f.Account], acc.Balance, constant.DEBIT)
+
+ if ba.Available < 0 && acc.Type != constant.ExternalAccountType {
+ return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateBalance", acc.Alias)
+ }
+ }
+ }
}
return nil
@@ -33,15 +53,15 @@ func validateFromAccounts(acc *a.Account, from map[string]Amount, asset string)
for key := range from {
if acc.Id == key || acc.Alias == key {
if acc.AssetCode != asset {
- return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "ValidateAccounts")
+ return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "validateFromAccounts")
}
if !acc.AllowSending {
- return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "ValidateAccounts")
+ return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "validateFromAccounts")
}
if acc.Balance.Available <= 0 && acc.Type != constant.ExternalAccountType {
- return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateAccounts", acc.Alias)
+ return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateFromAccounts", acc.Alias)
}
}
}
@@ -53,15 +73,15 @@ func validateToAccounts(acc *a.Account, to map[string]Amount, asset string) erro
for key := range to {
if acc.Id == key || acc.Alias == key {
if acc.AssetCode != asset {
- return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "ValidateAccounts")
+ return pkg.ValidateBusinessError(constant.ErrAssetCodeNotFound, "validateToAccounts")
}
if !acc.AllowReceiving {
- return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "ValidateAccounts")
+ return pkg.ValidateBusinessError(constant.ErrAccountStatusTransactionRestriction, "validateToAccounts")
}
if acc.Balance.Available > 0 && acc.Type == constant.ExternalAccountType {
- return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateAccounts", acc.Alias)
+ return pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "validateToAccounts", acc.Alias)
}
}
}
@@ -78,7 +98,7 @@ func ValidateFromToOperation(ft FromTo, validate Responses, acc *a.Account) (Amo
if ft.IsFrom {
ba := OperateAmounts(validate.From[ft.Account], acc.Balance, constant.DEBIT)
- if ba.Available < 0 && acc.Type != "external" {
+ if ba.Available < 0 && acc.Type != constant.ExternalAccountType {
return amount, balanceAfter, pkg.ValidateBusinessError(constant.ErrInsufficientFunds, "ValidateFromToOperation", acc.Alias)
}
@@ -137,6 +157,7 @@ func UpdateAccounts(operation string, fromTo map[string]Amount, accounts []*a.Ac
AllowSending: acc.AllowSending,
AllowReceiving: acc.AllowReceiving,
Type: acc.Type,
+ Version: acc.Version,
CreatedAt: acc.CreatedAt,
UpdatedAt: acc.UpdatedAt,
}
diff --git a/pkg/gold/transaction/model/validations_test.go b/pkg/gold/transaction/model/validations_test.go
index 2095ebad..5bc9c69d 100644
--- a/pkg/gold/transaction/model/validations_test.go
+++ b/pkg/gold/transaction/model/validations_test.go
@@ -14,6 +14,7 @@ import (
func TestValidateAccounts(t *testing.T) {
tests := []struct {
name string
+ tran Transaction
validate Responses
accounts []*a.Account
expectedError error
@@ -65,7 +66,7 @@ func TestValidateAccounts(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- err := ValidateAccounts(tt.validate, tt.accounts)
+ err := ValidateAccounts(tt.tran, tt.validate, tt.accounts)
if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) {
t.Fatalf("Expected error: %v, got: %v", tt.expectedError, err)
@@ -82,6 +83,7 @@ func TestValidateFromToOperation(t *testing.T) {
tests := []struct {
name string
ft FromTo
+ tran Transaction
validate Responses
acc *a.Account
expectedError error
diff --git a/pkg/mgrpc/account/account.pb.go b/pkg/mgrpc/account/account.pb.go
index 70a29d1e..6d1f5000 100644
--- a/pkg/mgrpc/account/account.pb.go
+++ b/pkg/mgrpc/account/account.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
-// protoc-gen-go v1.25.0-devel
-// protoc v3.14.0
+// protoc-gen-go v1.34.2
+// protoc v5.27.0
// source: account/account.proto
package account
@@ -205,10 +205,11 @@ type Account struct {
AllowReceiving bool `protobuf:"varint,13,opt,name=allow_receiving,json=allowReceiving,proto3" json:"allow_receiving,omitempty"`
Alias string `protobuf:"bytes,14,opt,name=alias,proto3" json:"alias,omitempty"`
Type string `protobuf:"bytes,15,opt,name=type,proto3" json:"type,omitempty"`
- CreatedAt string `protobuf:"bytes,16,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
- UpdatedAt string `protobuf:"bytes,17,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
- DeletedAt string `protobuf:"bytes,18,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"`
- Metadata *Metadata `protobuf:"bytes,19,opt,name=metadata,proto3" json:"metadata,omitempty"`
+ Version int64 `protobuf:"varint,16,opt,name=version,proto3" json:"version,omitempty"`
+ CreatedAt string `protobuf:"bytes,17,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+ UpdatedAt string `protobuf:"bytes,18,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
+ DeletedAt string `protobuf:"bytes,19,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"`
+ Metadata *Metadata `protobuf:"bytes,20,opt,name=metadata,proto3" json:"metadata,omitempty"`
}
func (x *Account) Reset() {
@@ -348,6 +349,13 @@ func (x *Account) GetType() string {
return ""
}
+func (x *Account) GetVersion() int64 {
+ if x != nil {
+ return x.Version
+ }
+ return 0
+}
+
func (x *Account) GetCreatedAt() string {
if x != nil {
return x.CreatedAt
@@ -634,7 +642,7 @@ var file_account_account_proto_rawDesc = []byte{
0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
- 0x6f, 0x6e, 0x22, 0xf6, 0x04, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e,
+ 0x6f, 0x6e, 0x22, 0x90, 0x05, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e,
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x63, 0x63,
@@ -665,57 +673,59 @@ var file_account_account_proto_rawDesc = []byte{
0x69, 0x76, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x0e,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74,
0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12,
- 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x10, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d,
- 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x11, 0x20, 0x01,
- 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a,
- 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x08,
- 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11,
- 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
- 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x40, 0x0a, 0x10, 0x41,
- 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
- 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
- 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f,
- 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x01,
- 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
- 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
- 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61,
- 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65,
- 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c,
- 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75,
- 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f,
- 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63,
- 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x64, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
- 0x73, 0x49, 0x44, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72,
- 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09,
- 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73,
- 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x6f, 0x0a, 0x0d, 0x41,
- 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x27, 0x0a, 0x0f,
- 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
- 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f,
- 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72,
- 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20,
- 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x32, 0xea, 0x01, 0x0a,
- 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x44, 0x0a,
- 0x10, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x49, 0x64,
- 0x73, 0x12, 0x13, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f,
- 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
- 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
- 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
- 0x74, 0x73, 0x42, 0x79, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x63,
- 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c,
- 0x69, 0x61, 0x73, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63,
- 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
- 0x12, 0x47, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
- 0x74, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63,
- 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61,
+ 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03,
+ 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65,
+ 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
+ 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61,
+ 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70,
+ 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74,
+ 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c,
+ 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+ 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75,
+ 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74,
+ 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x40, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+ 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63,
+ 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63,
+ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61,
+ 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f,
+ 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x6f,
+ 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01,
+ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69,
+ 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69,
+ 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49,
+ 0x64, 0x12, 0x2c, 0x0a, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20,
+ 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63,
+ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x08, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22,
+ 0x64, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44, 0x12, 0x27, 0x0a,
+ 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72,
+ 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65,
+ 0x72, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
+ 0x52, 0x03, 0x69, 0x64, 0x73, 0x22, 0x6f, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+ 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69,
+ 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12,
+ 0x1b, 0x0a, 0x09, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x08, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07,
+ 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61,
+ 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x32, 0xea, 0x01, 0x0a, 0x0c, 0x41, 0x63, 0x63, 0x6f, 0x75,
+ 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x44, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x63,
+ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x49, 0x64, 0x73, 0x12, 0x13, 0x2e, 0x61, 0x63,
+ 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x49, 0x44,
+ 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75,
+ 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a,
+ 0x14, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x41, 0x6c,
+ 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e,
+ 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x1a, 0x19, 0x2e,
+ 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0e, 0x55, 0x70,
+ 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52,
- 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x61,
- 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+ 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x22, 0x00, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+ 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -731,7 +741,7 @@ func file_account_account_proto_rawDescGZIP() []byte {
}
var file_account_account_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
-var file_account_account_proto_goTypes = []interface{}{
+var file_account_account_proto_goTypes = []any{
(*Balance)(nil), // 0: account.Balance
(*Metadata)(nil), // 1: account.Metadata
(*Status)(nil), // 2: account.Status
@@ -768,7 +778,7 @@ func file_account_account_proto_init() {
return
}
if !protoimpl.UnsafeEnabled {
- file_account_account_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Balance); i {
case 0:
return &v.state
@@ -780,7 +790,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*Metadata); i {
case 0:
return &v.state
@@ -792,7 +802,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*Status); i {
case 0:
return &v.state
@@ -804,7 +814,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*Account); i {
case 0:
return &v.state
@@ -816,7 +826,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*AccountsResponse); i {
case 0:
return &v.state
@@ -828,7 +838,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*AccountsRequest); i {
case 0:
return &v.state
@@ -840,7 +850,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*AccountsID); i {
case 0:
return &v.state
@@ -852,7 +862,7 @@ func file_account_account_proto_init() {
return nil
}
}
- file_account_account_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+ file_account_account_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*AccountsAlias); i {
case 0:
return &v.state
diff --git a/pkg/mgrpc/account/account.proto b/pkg/mgrpc/account/account.proto
index 47ca487b..f3a3cca5 100644
--- a/pkg/mgrpc/account/account.proto
+++ b/pkg/mgrpc/account/account.proto
@@ -34,10 +34,11 @@ message Account {
bool allow_receiving = 13;
string alias = 14;
string type = 15;
- string created_at = 16;
- string updated_at = 17;
- string deleted_at = 18;
- Metadata metadata = 19;
+ int64 version = 16;
+ string created_at = 17;
+ string updated_at = 18;
+ string deleted_at = 19;
+ Metadata metadata = 20;
}
message AccountsResponse {
diff --git a/pkg/mgrpc/account/account_grpc.pb.go b/pkg/mgrpc/account/account_grpc.pb.go
index bbee49d0..7d24d0a2 100644
--- a/pkg/mgrpc/account/account_grpc.pb.go
+++ b/pkg/mgrpc/account/account_grpc.pb.go
@@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.5.1
+// - protoc v5.27.0
+// source: account/account.proto
package account
@@ -11,8 +15,14 @@ import (
// 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
+// Requires gRPC-Go v1.64.0 or later.
+const _ = grpc.SupportPackageIsVersion9
+
+const (
+ AccountProto_GetAccountsByIds_FullMethodName = "/account.AccountProto/GetAccountsByIds"
+ AccountProto_GetAccountsByAliases_FullMethodName = "/account.AccountProto/GetAccountsByAliases"
+ AccountProto_UpdateAccounts_FullMethodName = "/account.AccountProto/UpdateAccounts"
+)
// AccountProtoClient is the client API for AccountProto service.
//
@@ -32,8 +42,9 @@ func NewAccountProtoClient(cc grpc.ClientConnInterface) AccountProtoClient {
}
func (c *accountProtoClient) GetAccountsByIds(ctx context.Context, in *AccountsID, opts ...grpc.CallOption) (*AccountsResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AccountsResponse)
- err := c.cc.Invoke(ctx, "/account.AccountProto/GetAccountsByIds", in, out, opts...)
+ err := c.cc.Invoke(ctx, AccountProto_GetAccountsByIds_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -41,8 +52,9 @@ func (c *accountProtoClient) GetAccountsByIds(ctx context.Context, in *AccountsI
}
func (c *accountProtoClient) GetAccountsByAliases(ctx context.Context, in *AccountsAlias, opts ...grpc.CallOption) (*AccountsResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AccountsResponse)
- err := c.cc.Invoke(ctx, "/account.AccountProto/GetAccountsByAliases", in, out, opts...)
+ err := c.cc.Invoke(ctx, AccountProto_GetAccountsByAliases_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -50,8 +62,9 @@ func (c *accountProtoClient) GetAccountsByAliases(ctx context.Context, in *Accou
}
func (c *accountProtoClient) UpdateAccounts(ctx context.Context, in *AccountsRequest, opts ...grpc.CallOption) (*AccountsResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AccountsResponse)
- err := c.cc.Invoke(ctx, "/account.AccountProto/UpdateAccounts", in, out, opts...)
+ err := c.cc.Invoke(ctx, AccountProto_UpdateAccounts_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -60,7 +73,7 @@ func (c *accountProtoClient) UpdateAccounts(ctx context.Context, in *AccountsReq
// AccountProtoServer is the server API for AccountProto service.
// All implementations must embed UnimplementedAccountProtoServer
-// for forward compatibility
+// for forward compatibility.
type AccountProtoServer interface {
GetAccountsByIds(context.Context, *AccountsID) (*AccountsResponse, error)
GetAccountsByAliases(context.Context, *AccountsAlias) (*AccountsResponse, error)
@@ -68,9 +81,12 @@ type AccountProtoServer interface {
mustEmbedUnimplementedAccountProtoServer()
}
-// UnimplementedAccountProtoServer must be embedded to have forward compatible implementations.
-type UnimplementedAccountProtoServer struct {
-}
+// UnimplementedAccountProtoServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedAccountProtoServer struct{}
func (UnimplementedAccountProtoServer) GetAccountsByIds(context.Context, *AccountsID) (*AccountsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAccountsByIds not implemented")
@@ -82,6 +98,7 @@ func (UnimplementedAccountProtoServer) UpdateAccounts(context.Context, *Accounts
return nil, status.Errorf(codes.Unimplemented, "method UpdateAccounts not implemented")
}
func (UnimplementedAccountProtoServer) mustEmbedUnimplementedAccountProtoServer() {}
+func (UnimplementedAccountProtoServer) testEmbeddedByValue() {}
// UnsafeAccountProtoServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AccountProtoServer will
@@ -91,6 +108,13 @@ type UnsafeAccountProtoServer interface {
}
func RegisterAccountProtoServer(s grpc.ServiceRegistrar, srv AccountProtoServer) {
+ // If the following call pancis, it indicates UnimplementedAccountProtoServer was
+ // embedded by pointer and is nil. This will cause panics if an
+ // unimplemented method is ever invoked, so we test this at initialization
+ // time to prevent it from happening at runtime later due to I/O.
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+ t.testEmbeddedByValue()
+ }
s.RegisterService(&AccountProto_ServiceDesc, srv)
}
@@ -104,7 +128,7 @@ func _AccountProto_GetAccountsByIds_Handler(srv interface{}, ctx context.Context
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: "/account.AccountProto/GetAccountsByIds",
+ FullMethod: AccountProto_GetAccountsByIds_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountProtoServer).GetAccountsByIds(ctx, req.(*AccountsID))
@@ -122,7 +146,7 @@ func _AccountProto_GetAccountsByAliases_Handler(srv interface{}, ctx context.Con
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: "/account.AccountProto/GetAccountsByAliases",
+ FullMethod: AccountProto_GetAccountsByAliases_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountProtoServer).GetAccountsByAliases(ctx, req.(*AccountsAlias))
@@ -140,7 +164,7 @@ func _AccountProto_UpdateAccounts_Handler(srv interface{}, ctx context.Context,
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: "/account.AccountProto/UpdateAccounts",
+ FullMethod: AccountProto_UpdateAccounts_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountProtoServer).UpdateAccounts(ctx, req.(*AccountsRequest))
diff --git a/pkg/mmodel/account.go b/pkg/mmodel/account.go
index 127a42bd..d45214c4 100644
--- a/pkg/mmodel/account.go
+++ b/pkg/mmodel/account.go
@@ -59,6 +59,7 @@ type Account struct {
AllowReceiving *bool `json:"allowReceiving" example:"true"`
Alias *string `json:"alias" example:"@person1"`
Type string `json:"type" example:"creditCard"`
+ Version int64 `json:"-"`
CreatedAt time.Time `json:"createdAt" example:"2021-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updatedAt" example:"2021-01-01T00:00:00Z"`
DeletedAt *time.Time `json:"deletedAt" example:"2021-01-01T00:00:00Z"`
@@ -117,6 +118,7 @@ func (e *Account) ToProto() *proto.Account {
AllowSending: *e.AllowSending,
AllowReceiving: *e.AllowReceiving,
Type: e.Type,
+ Version: e.Version,
}
if e.ParentAccountID != nil {
diff --git a/pkg/mpostgres/pagination.go b/pkg/mpostgres/pagination.go
index d22eb173..0e6727ea 100644
--- a/pkg/mpostgres/pagination.go
+++ b/pkg/mpostgres/pagination.go
@@ -15,7 +15,6 @@ type Pagination struct {
SortOrder string `json:"-" example:"asc"`
StartDate time.Time `json:"-" example:"2021-01-01"`
EndDate time.Time `json:"-" example:"2021-12-31"`
- Alias string `json:"-" example:"@wallet_12345123"`
} // @name Pagination
// SetItems set an array of any struct in items.
diff --git a/pkg/mpostgres/postgres.go b/pkg/mpostgres/postgres.go
index 6527fb2e..e81f06ff 100644
--- a/pkg/mpostgres/postgres.go
+++ b/pkg/mpostgres/postgres.go
@@ -6,6 +6,8 @@ import (
"go.uber.org/zap"
"net/url"
"path/filepath"
+ "time"
+
// File system migration source. We need to import it to be able to use it as source in migrate.NewWithSourceInstance
"github.com/LerianStudio/midaz/pkg/mlog"
@@ -39,12 +41,20 @@ func (pc *PostgresConnection) Connect() error {
return nil
}
+ dbPrimary.SetMaxOpenConns(100)
+ dbPrimary.SetMaxIdleConns(100)
+ dbPrimary.SetConnMaxLifetime(time.Minute * 5)
+
dbReadOnlyReplica, err := sql.Open("pgx", pc.ConnectionStringReplica)
if err != nil {
pc.Logger.Fatal("failed to open connect to replica database", zap.Error(err))
return nil
}
+ dbReadOnlyReplica.SetMaxOpenConns(100)
+ dbReadOnlyReplica.SetMaxIdleConns(100)
+ dbReadOnlyReplica.SetConnMaxLifetime(time.Minute * 5)
+
connectionDB := dbresolver.New(
dbresolver.WithPrimaryDBs(dbPrimary),
dbresolver.WithReplicaDBs(dbReadOnlyReplica),
diff --git a/pkg/net/http/httputils.go b/pkg/net/http/httputils.go
index c1f550f1..3aecb305 100644
--- a/pkg/net/http/httputils.go
+++ b/pkg/net/http/httputils.go
@@ -28,7 +28,6 @@ type QueryHeader struct {
SortOrder string
StartDate time.Time
EndDate time.Time
- Alias string
UseMetadata bool
PortfolioID string
ToAssetCodes []string
@@ -42,7 +41,6 @@ type Pagination struct {
SortOrder string
StartDate time.Time
EndDate time.Time
- Alias string
}
// ValidateParameters validate and return struct of default parameters
@@ -54,7 +52,6 @@ func ValidateParameters(params map[string]string) (*QueryHeader, error) {
startDate time.Time
endDate time.Time
cursor string
- alias string
limit = 10
page = 1
sortOrder = "desc"
@@ -82,8 +79,6 @@ func ValidateParameters(params map[string]string) (*QueryHeader, error) {
portfolioID = value
case strings.Contains(key, "to"):
toAssetCodes = strings.Split(value, ",")
- case key == "alias":
- alias = value
}
}
@@ -112,7 +107,6 @@ func ValidateParameters(params map[string]string) (*QueryHeader, error) {
SortOrder: sortOrder,
StartDate: startDate,
EndDate: endDate,
- Alias: alias,
UseMetadata: useMetadata,
PortfolioID: portfolioID,
ToAssetCodes: toAssetCodes,
@@ -276,7 +270,6 @@ func (qh *QueryHeader) ToOffsetPagination() Pagination {
SortOrder: qh.SortOrder,
StartDate: qh.StartDate,
EndDate: qh.EndDate,
- Alias: qh.Alias,
}
}
diff --git a/pkg/utils.go b/pkg/utils.go
index 00b342fd..195f14bc 100644
--- a/pkg/utils.go
+++ b/pkg/utils.go
@@ -278,3 +278,21 @@ func Reverse[T any](s []T) []T {
return s
}
+
+func InternalKey(organizationID, ledgerID uuid.UUID, key string) string {
+ internalKey := organizationID.String() + ":" + ledgerID.String() + ":" + key
+
+ return internalKey
+}
+
+func LockInternalKey(organizationID, ledgerID uuid.UUID, key string) string {
+ lockInternalKey := "lock:" + InternalKey(organizationID, ledgerID, key)
+
+ return lockInternalKey
+}
+
+func LockVersionInternalKey(organizationID, ledgerID uuid.UUID, key, version string) string {
+ lockVersionInternalKey := LockInternalKey(organizationID, ledgerID, key) + ":" + version
+
+ return lockVersionInternalKey
+}
diff --git a/postman/MIDAZ.postman_collection.json b/postman/MIDAZ.postman_collection.json
index e6233720..b9888fac 100644
--- a/postman/MIDAZ.postman_collection.json
+++ b/postman/MIDAZ.postman_collection.json
@@ -1,6 +1,6 @@
{
"info": {
- "_postman_id": "04ac9f89-2f0e-4aa7-80db-92644332f965",
+ "_postman_id": "011947ce-a59a-4f67-a335-cf486cbe8d06",
"name": "MIDAZ",
"description": "## **How generate token to use on MIDAZ**\n\n
\n\n
\n\n
\n\n
\n\n
",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
@@ -2308,7 +2308,7 @@
],
"body": {
"mode": "raw",
- "raw": "{\n \"chartOfAccountsGroupName\": \"PAG_CONTAS_CODE_1\",\n \"description\": \"description for the transaction person1 to person2 value of 100 reais\",\n \"metadata\": {\n \"mensagem\": \"pagamento\",\n \"valor\": \"100\"\n },\n \"send\": {\n \"asset\": \"BRL\",\n \"value\": 1000,\n \"scale\": 1,\n \"source\": {\n \"from\": [\n {\n \"account\": \"@external/BRL\",\n \"amount\": {\n \"asset\": \"BRL\",\n \"value\": 1000,\n \"scale\": 1\n },\n \"description\": \"Loan payment person1\",\n \"metadata\": {\n \"1\": \"m\",\n \"Cpf\": \"43049498x\"\n }\n }\n ]\n },\n \"distribute\": {\n \"to\": [\n {\n \"account\": \"0193f2d4-629f-702d-aeec-7cb80bb4097c\",\n \"amount\": {\n \"asset\": \"BRL\",\n \"value\": 1000,\n \"scale\": 1\n },\n \"metadata\": {\n \"mensagem\": \"tks\"\n }\n }\n ]\n }\n }\n}",
+ "raw": "{\n \"chartOfAccountsGroupName\": \"PAG_CONTAS_CODE_1\",\n \"description\": \"description for the transaction person1 to person2 value of 100 reais\",\n \"metadata\": {\n \"mensagem\": \"pagamento\",\n \"valor\": \"200\"\n },\n \"send\": {\n \"asset\": \"BRL\",\n \"value\": 200,\n \"scale\": 2,\n \"source\": {\n \"from\": [\n {\n \"account\": \"@external/BRL\",\n \"amount\": {\n \"asset\": \"BRL\",\n \"value\": 200,\n \"scale\": 2\n },\n \"description\": \"Loan payment person1\",\n \"metadata\": {\n \"1\": \"m\",\n \"Cpf\": \"43049498x\"\n }\n }\n ]\n },\n \"distribute\": {\n \"to\": [\n {\n \"account\": \"@mcgregor_brl\",\n \"amount\": {\n \"asset\": \"BRL\",\n \"value\": 200,\n \"scale\": 2\n },\n \"metadata\": {\n \"mensagem\": \"tks\"\n }\n }\n ]\n }\n }\n}",
"options": {
"raw": {
"language": "json"
@@ -2483,7 +2483,7 @@
"response": []
},
{
- "name": "Transactions Revert",
+ "name": "Transactions Commit",
"event": [
{
"listen": "test",
@@ -2542,7 +2542,7 @@
]
},
"url": {
- "raw": "{{url_transaction}}/v1/organizations/{{organization_id}}/ledgers/{{ledger_id}}/transactions/{{transaction_id}}/revert",
+ "raw": "{{url_transaction}}/v1/organizations/{{organization_id}}/ledgers/{{ledger_id}}/transactions/{{transaction_id}}/commit",
"host": [
"{{url_transaction}}"
],
@@ -2554,7 +2554,7 @@
"{{ledger_id}}",
"transactions",
"{{transaction_id}}",
- "revert"
+ "commit"
]
},
"description": "This is a POST request, submitting data to an API via the request body. This request submits JSON data, and the data is reflected in the response.\n\nA successful POST request typically returns a `200 OK` or `201 Created` response code."
@@ -2562,7 +2562,7 @@
"response": []
},
{
- "name": "Transactions Commit",
+ "name": "Transactions Cancel",
"event": [
{
"listen": "test",
@@ -2621,7 +2621,7 @@
]
},
"url": {
- "raw": "{{url_transaction}}/v1/organizations/{{organization_id}}/ledgers/{{ledger_id}}/transactions/{{transaction_id}}/commit",
+ "raw": "{{url_transaction}}/v1/organizations/{{organization_id}}/ledgers/{{ledger_id}}/transactions/{{transaction_id}}/cancel",
"host": [
"{{url_transaction}}"
],
@@ -2633,7 +2633,7 @@
"{{ledger_id}}",
"transactions",
"{{transaction_id}}",
- "commit"
+ "cancel"
]
},
"description": "This is a POST request, submitting data to an API via the request body. This request submits JSON data, and the data is reflected in the response.\n\nA successful POST request typically returns a `200 OK` or `201 Created` response code."
@@ -2784,6 +2784,58 @@
}
},
"response": []
+ },
+ {
+ "name": "Transactions Revert",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "const jsonData = JSON.parse(responseBody);",
+ "if (jsonData.hasOwnProperty('id')) {",
+ " console.log(\"parent_transaction_id before: \" + pm.collectionVariables.get(\"parent_transaction_id\"));",
+ " pm.collectionVariables.set(\"parent_transaction_id\", jsonData.id);",
+ " console.log(\"parent_transaction_id after: \" + pm.collectionVariables.get(\"parent_transaction_id\"));",
+ "}"
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ },
+ {
+ "listen": "prerequest",
+ "script": {
+ "exec": [
+ ""
+ ],
+ "type": "text/javascript",
+ "packages": {}
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [],
+ "url": {
+ "raw": "{{url_transaction}}/v1/organizations/{{organization_id}}/ledgers/{{ledger_id}}/transactions/{{transaction_id}}/revert",
+ "host": [
+ "{{url_transaction}}"
+ ],
+ "path": [
+ "v1",
+ "organizations",
+ "{{organization_id}}",
+ "ledgers",
+ "{{ledger_id}}",
+ "transactions",
+ "{{transaction_id}}",
+ "revert"
+ ]
+ },
+ "description": "This is a POST request, submitting data to an API via the request body. This request submits JSON data, and the data is reflected in the response.\n\nA successful POST request typically returns a `200 OK` or `201 Created` response code."
+ },
+ "response": []
}
]
},
@@ -3652,6 +3704,11 @@
"key": "asset_rate_id",
"value": "",
"type": "string"
+ },
+ {
+ "key": "parent_transaction_id",
+ "value": "",
+ "type": "string"
}
]
}
\ No newline at end of file