Compare commits
25 Commits
357af99564
...
44446c6ad4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44446c6ad4 | ||
|
|
48ccbb1c82 | ||
|
|
68f0a1048f | ||
|
|
be913bf96c | ||
|
|
8e1d4bef59 | ||
|
|
c6da138184 | ||
|
|
d78619bccf | ||
|
|
85b780b57e | ||
|
|
1f31fedc3a | ||
|
|
bdf3a01f80 | ||
|
|
d126d5d5de | ||
|
|
26a1e284b2 | ||
|
|
fc0600d6c4 | ||
|
|
b855404999 | ||
|
|
e3a8fb4f2d | ||
|
|
d65e442cb6 | ||
|
|
b4f6f63871 | ||
|
|
803683be7c | ||
|
|
72271cfc9a | ||
|
|
cd79355e69 | ||
|
|
3f84f8c609 | ||
|
|
ae15e1887b | ||
|
|
8a41785b1d | ||
|
|
56abc10dce | ||
| d8a3a5550d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
*.pb.gw.go
|
||||
pubspec.lock
|
||||
.DS_Store
|
||||
update_dep.sh
|
||||
@@ -32,14 +32,14 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -49,6 +49,6 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -141,8 +141,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
@@ -73,7 +73,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
@@ -86,5 +86,5 @@ require (
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
)
|
||||
|
||||
@@ -209,8 +209,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -283,8 +283,8 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
@@ -362,8 +362,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -32,13 +32,13 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.6 // indirect
|
||||
@@ -49,7 +49,7 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -141,8 +141,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -34,13 +34,13 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -50,5 +50,5 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
)
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -141,8 +141,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
|
||||
@@ -109,8 +109,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
|
||||
// Client exposes typed helpers around the ledger gRPC API.
|
||||
type Client interface {
|
||||
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error)
|
||||
PostDebitWithCharges(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error)
|
||||
TransferInternal(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error)
|
||||
@@ -29,6 +31,8 @@ type Client interface {
|
||||
}
|
||||
|
||||
type grpcLedgerClient interface {
|
||||
CreateAccount(ctx context.Context, in *ledgerv1.CreateAccountRequest, opts ...grpc.CallOption) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccounts(ctx context.Context, in *ledgerv1.ListAccountsRequest, opts ...grpc.CallOption) (*ledgerv1.ListAccountsResponse, error)
|
||||
PostCreditWithCharges(ctx context.Context, in *ledgerv1.PostCreditRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||
PostDebitWithCharges(ctx context.Context, in *ledgerv1.PostDebitRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||
TransferInternal(ctx context.Context, in *ledgerv1.TransferRequest, opts ...grpc.CallOption) (*ledgerv1.PostResponse, error)
|
||||
@@ -91,6 +95,18 @@ func (c *ledgerClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ledgerClient) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||
ctx, cancel := c.callContext(ctx)
|
||||
defer cancel()
|
||||
return c.client.CreateAccount(ctx, req)
|
||||
}
|
||||
|
||||
func (c *ledgerClient) ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error) {
|
||||
ctx, cancel := c.callContext(ctx)
|
||||
defer cancel()
|
||||
return c.client.ListAccounts(ctx, req)
|
||||
}
|
||||
|
||||
func (c *ledgerClient) PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error) {
|
||||
ctx, cancel := c.callContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -34,14 +34,14 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -51,5 +51,5 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
)
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -143,8 +143,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -214,8 +214,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
47
api/ledger/internal/service/ledger/list_accounts.go
Normal file
47
api/ledger/internal/service/ledger/list_accounts.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package ledger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *Service) listAccountsResponder(_ context.Context, req *ledgerv1.ListAccountsRequest) gsresponse.Responder[ledgerv1.ListAccountsResponse] {
|
||||
return func(ctx context.Context) (*ledgerv1.ListAccountsResponse, error) {
|
||||
if s.storage == nil {
|
||||
return nil, errStorageNotInitialized
|
||||
}
|
||||
if req == nil {
|
||||
return nil, merrors.InvalidArgument("request is required")
|
||||
}
|
||||
|
||||
orgRefStr := strings.TrimSpace(req.GetOrganizationRef())
|
||||
if orgRefStr == "" {
|
||||
return nil, merrors.InvalidArgument("organization_ref is required")
|
||||
}
|
||||
orgRef, err := parseObjectID(orgRefStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No pagination requested; return all accounts for the organization.
|
||||
accounts, err := s.storage.Accounts().ListByOrganization(ctx, orgRef, 0, 0)
|
||||
if err != nil {
|
||||
s.logger.Warn("failed to list ledger accounts", zap.Error(err), zap.String("organizationRef", orgRef.Hex()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &ledgerv1.ListAccountsResponse{
|
||||
Accounts: make([]*ledgerv1.LedgerAccount, 0, len(accounts)),
|
||||
}
|
||||
for _, acc := range accounts {
|
||||
resp.Accounts = append(resp.Accounts, toProtoAccount(acc))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,12 @@ func (s *Service) Register(router routers.GRPC) error {
|
||||
})
|
||||
}
|
||||
|
||||
// ListAccounts lists ledger accounts for an organization.
|
||||
func (s *Service) ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error) {
|
||||
responder := s.listAccountsResponder(ctx, req)
|
||||
return responder(ctx)
|
||||
}
|
||||
|
||||
// CreateAccount provisions a new ledger account scoped to an organization.
|
||||
func (s *Service) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||
responder := s.createAccountResponder(ctx, req)
|
||||
|
||||
@@ -51,7 +51,7 @@ api:
|
||||
settings:
|
||||
username_env: MAIL_USER
|
||||
password_env: MAIL_SECRET
|
||||
host: "smtp.mail.ru"
|
||||
host: "mail.sendico.io"
|
||||
port: 465
|
||||
from: "Sendico Tech"
|
||||
network_timeout: 10
|
||||
@@ -65,7 +65,7 @@ api:
|
||||
|
||||
localizer:
|
||||
path: "./i18n"
|
||||
languages: ["en", "ru", "uk"]
|
||||
languages: ["en", "ru"]
|
||||
service_name: "Sendico"
|
||||
support: "support@sendico.io"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
@@ -43,7 +43,7 @@ require (
|
||||
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -52,7 +52,7 @@ require (
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
@@ -101,8 +101,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||
@@ -154,8 +154,8 @@ github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817 h1:q0hKh5a5FRkhuTb5
|
||||
github.com/toorop/go-dkim v0.0.0-20250226130143-9025cce95817/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||
@@ -227,8 +227,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -46,7 +46,7 @@ func (m *EmailNotificationTemplate) prepareUnsubscribe(msg mmail.Message) error
|
||||
return err
|
||||
}
|
||||
localization.AddLocData(d, "UnsubscribeLink", unsLink)
|
||||
if block, err = m.l.LocalizeTemplate("mail.template.unsubscribe.block", d, nil, msg.Locale()); err != nil {
|
||||
if block, err = renderUnsubscribeBlock(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (m *EmailNotificationTemplate) prepareButton(msg mmail.Message) error {
|
||||
var block string
|
||||
if m.hasButton {
|
||||
var err error
|
||||
if block, err = m.l.LocalizeTemplate("mail.template.btn.block", m.data, nil, msg.Locale()); err != nil {
|
||||
if block, err = renderButtonBlock(m.data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (m *EmailNotificationTemplate) SignatureData(msg mmail.Message, content, su
|
||||
return "", err
|
||||
}
|
||||
|
||||
return m.l.LocalizeTemplate("mail.template.one_button", m.data, nil, msg.Locale())
|
||||
return renderOneButtonEmail(m.data)
|
||||
}
|
||||
|
||||
func (m *EmailNotificationTemplate) putOnHTMLTemplate(msg mmail.Message, content, subj string) (string, error) {
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package mailimp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
unsubscribeBlockTpl = template.Must(template.New("unsubscribeBlock").Parse(`
|
||||
<a
|
||||
target="_blank"
|
||||
style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;text-decoration:none;color:#2D3142;font-size:13px"
|
||||
href=""
|
||||
>
|
||||
</a>
|
||||
•
|
||||
<a
|
||||
target="_blank"
|
||||
style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;text-decoration:none;color:#2D3142;font-size:14px"
|
||||
href="{{.UnsubscribeLink}}"
|
||||
>
|
||||
{{.Unsubscribe}}
|
||||
</a>`))
|
||||
|
||||
buttonBlockTpl = template.Must(template.New("buttonBlock").Parse(`
|
||||
<tr>
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<!--[if mso]>
|
||||
<a href="" target="_blank" hidden>
|
||||
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" esdevVmlButton href="{{.ButtonLink}}"
|
||||
style="height:56px; v-text-anchor:middle; width:520px" arcsize="14%" stroke="f" fillcolor="#0b58ff">
|
||||
<w:anchorlock></w:anchorlock>
|
||||
<center style='color:#ffffff; font-family:montserrat, roboto; font-size:22px; font-weight:700; line-height:22px; mso-text-raise:1px'>{{.ButtonText}}</center>
|
||||
</v:roundrect>
|
||||
</a>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-- -->
|
||||
<span class="msohide es-button-border" style="border-style:solid;border-color:#0b58ff;background:#0b58ff;border-width:0px;display:block;border-radius:8px;width:auto;mso-border-alt:10px;mso-hide:all;width:520px">
|
||||
<a
|
||||
href="{{.ButtonLink}}"
|
||||
class="es-button msohide"
|
||||
target="_blank"
|
||||
style="mso-style-priority:100 !important;text-decoration:none;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;color:#ffffff;font-size:22px;padding:15px 20px 15px 20px;display:block;background:#0b58ff;border-radius:8px;font-family:montserrat, roboto;font-weight:bold;font-style:normal;line-height:26px;width:auto;text-align:center;border-color:#0b58ff;mso-hide:all;padding-left:5px;padding-right:5px"
|
||||
>
|
||||
{{.ButtonText}}
|
||||
</a>
|
||||
</span>
|
||||
<!--<![endif]-->
|
||||
</td>
|
||||
</tr>`))
|
||||
|
||||
oneButtonEmailTpl = template.Must(template.New("oneButtonEmail").Parse(`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" style="font-family:montserrat, roboto">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="telephone=no" name="format-detection">
|
||||
<title>{{.MessageTitle}}</title>
|
||||
<!--[if (mso 16)]>
|
||||
<style type="text/css">
|
||||
a {text-decoration: none;}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if gte mso 9]><style>sup { font-size: 100% !important; }</style><![endif]-->
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG></o:AllowPNG>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-- -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Imprima&display=swap" rel="stylesheet">
|
||||
<!--<![endif]-->
|
||||
<!--[if !mso]><!-- -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:100,300,400,500,700,900">
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
#outlook a { padding:0; }
|
||||
.es-button { mso-style-priority:100!important; text-decoration:none!important; }
|
||||
a[x-apple-data-detectors] {
|
||||
color:inherit!important;
|
||||
text-decoration:none!important;
|
||||
font-size:inherit!important;
|
||||
font-family:inherit!important;
|
||||
font-weight:inherit!important;
|
||||
line-height:inherit!important;
|
||||
}
|
||||
.es-desk-hidden { display:none; float:left; overflow:hidden; width:0; max-height:0; line-height:0; mso-hide:all; }
|
||||
@media only screen and (max-width:600px) {
|
||||
p, ul li, ol li, a { line-height:150%!important }
|
||||
h1, h2, h3, h1 a, h2 a, h3 a { line-height:120% }
|
||||
h1 { font-size:30px!important; text-align:left }
|
||||
h2 { font-size:24px!important; text-align:left }
|
||||
h3 { font-size:20px!important; text-align:left }
|
||||
.es-header-body h1 a, .es-content-body h1 a, .es-footer-body h1 a { font-size:30px!important; text-align:left }
|
||||
.es-header-body h2 a, .es-content-body h2 a, .es-footer-body h2 a { font-size:24px!important; text-align:left }
|
||||
.es-header-body h3 a, .es-content-body h3 a, .es-footer-body h3 a { font-size:20px!important; text-align:left }
|
||||
.es-menu td a { font-size:14px!important }
|
||||
.es-header-body p, .es-header-body ul li, .es-header-body ol li, .es-header-body a { font-size:14px!important }
|
||||
.es-content-body p, .es-content-body ul li, .es-content-body ol li, .es-content-body a { font-size:14px!important }
|
||||
.es-footer-body p, .es-footer-body ul li, .es-footer-body ol li, .es-footer-body a { font-size:14px!important }
|
||||
.es-infoblock p, .es-infoblock ul li, .es-infoblock ol li, .es-infoblock a { font-size:12px!important }
|
||||
*[class="gmail-fix"] { display:none!important }
|
||||
.es-m-txt-c, .es-m-txt-c h1, .es-m-txt-c h2, .es-m-txt-c h3 { text-align:center!important }
|
||||
.es-m-txt-r, .es-m-txt-r h1, .es-m-txt-r h2, .es-m-txt-r h3 { text-align:right!important }
|
||||
.es-m-txt-l, .es-m-txt-l h1, .es-m-txt-l h2, .es-m-txt-l h3 { text-align:left!important }
|
||||
.es-m-txt-r img, .es-m-txt-c img, .es-m-txt-l img { display:inline!important }
|
||||
.es-button-border { display:block!important }
|
||||
a.es-button, button.es-button { font-size:18px!important; display:block!important; border-right-width:0px!important; border-left-width:0px!important; border-top-width:15px!important; border-bottom-width:15px!important; padding-left:0px!important; padding-right:0px!important }
|
||||
.es-adaptive table, .es-left, .es-right { width:100%!important }
|
||||
.es-content table, .es-header table, .es-footer table, .es-content, .es-footer, .es-header { width:100%!important; max-width:600px!important }
|
||||
.es-adapt-td { display:block!important; width:100%!important }
|
||||
.adapt-img { width:100%!important; height:auto!important }
|
||||
.es-m-p0 { padding:0px!important }
|
||||
.es-m-p0r { padding-right:0px!important }
|
||||
.es-m-p0l { padding-left:0px!important }
|
||||
.es-m-p0t { padding-top:0px!important }
|
||||
.es-m-p0b { padding-bottom:0!important }
|
||||
.es-m-p20b { padding-bottom:20px!important }
|
||||
.es-mobile-hidden, .es-hidden { display:none!important }
|
||||
tr.es-desk-hidden, td.es-desk-hidden, table.es-desk-hidden { width:auto!important; overflow:visible!important; float:none!important; max-height:inherit!important; line-height:inherit!important }
|
||||
tr.es-desk-hidden { display:table-row!important }
|
||||
table.es-desk-hidden { display:table!important }
|
||||
td.es-desk-menu-hidden { display:table-cell!important }
|
||||
.es-menu td { width:1%!important }
|
||||
table.es-table-not-adapt, .esd-block-html table { width:auto!important }
|
||||
table.es-social { display:inline-block!important }
|
||||
table.es-social td { display:inline-block!important }
|
||||
.es-desk-hidden { display:table-row!important; width:auto!important; overflow:visible!important; max-height:inherit!important }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="width:100%;font-family:montserrat, roboto;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;padding:0;Margin:0">
|
||||
<div class="es-wrapper-color" style="background-color:#FFFFFF">
|
||||
<!--[if gte mso 9]>
|
||||
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
|
||||
<v:fill type="tile" color="#ffffff"></v:fill>
|
||||
</v:background>
|
||||
<![endif]-->
|
||||
<table class="es-wrapper" width="100%" cellspacing="0" cellpadding="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;padding:0;Margin:0;width:100%;height:100%;background-repeat:repeat;background-position:center top;background-color:#FFFFFF">
|
||||
<tr>
|
||||
<td valign="top" style="padding:0;Margin:0">
|
||||
<table cellpadding="0" cellspacing="0" class="es-content" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
|
||||
<tr>
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<table bgcolor="#ffffff" class="es-content-body" align="center" cellpadding="0" cellspacing="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#FFFFFF;border-radius:20px 20px 0 0;width:600px">
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0;padding-top:20px;padding-left:40px;padding-right:40px;border-radius:8px 8px 0px 0px">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
|
||||
<tr>
|
||||
<td align="center" valign="top" style="padding:0;Margin:0;width:520px">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" bgcolor="#fafafa" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:separate;border-spacing:0px;background-color:#fafafa;border-radius:10px" role="presentation">
|
||||
<tr>
|
||||
<td align="left" style="padding:20px;Margin:0">
|
||||
<h3 style="Margin:0;line-height:34px;mso-line-height-rule:exactly;font-family:montserrat, roboto;font-size:28px;font-style:normal;font-weight:bold;color:#2D3142">
|
||||
{{.Greeting}}
|
||||
</h3>
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:27px;color:#2D3142;font-size:18px"><br></p>
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:27px;color:#2D3142;font-size:18px">
|
||||
{{.Content}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" class="es-content" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%">
|
||||
{{.ButtonBlock}}
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" class="es-footer" align="center" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;table-layout:fixed !important;width:100%;background-color:transparent;background-repeat:repeat;background-position:center top">
|
||||
<tr>
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<table bgcolor="#bcb8b1" class="es-footer-body" align="center" cellpadding="0" cellspacing="0" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;background-color:#FFFFFF;width:600px">
|
||||
<tr>
|
||||
<td align="left" style="Margin:0;padding-left:20px;padding-right:20px;padding-bottom:30px;padding-top:40px">
|
||||
<!--[if mso]><table style="width:560px" cellpadding="0" cellspacing="0"><tr><td style="width:82px" valign="top"><![endif]-->
|
||||
<table cellpadding="0" cellspacing="0" align="left" class="es-left" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;float:left">
|
||||
<tr>
|
||||
<td align="left" class="es-m-p20b" style="padding:0;Margin:0;width:82px">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="padding:0;Margin:0;padding-left:20px;font-size:0px">
|
||||
<img class="adapt-img"
|
||||
src="{{.LogoLink}}"
|
||||
alt
|
||||
style="display:block;border:0;outline:none;text-decoration:none;-ms-interpolation-mode:bicubic" width="62"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso]></td><td style="width:20px"></td><td style="width:458px" valign="top"><![endif]-->
|
||||
<table cellpadding="0" cellspacing="0" class="es-right" align="right" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px;float:right">
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0;width:458px">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" role="presentation" style="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;border-spacing:0px">
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:20px;color:#2D3142;font-size:13px">
|
||||
<a target="_blank" style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;text-decoration:none;color:#2D3142;font-size:14px" href=""></a>
|
||||
<a
|
||||
target="_blank"
|
||||
style="-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;text-decoration:none;color:#2D3142;font-size:14px"
|
||||
href="{{.PolicyLink}}"
|
||||
>
|
||||
{{.Privacy}}
|
||||
</a>
|
||||
{{.UnsubscribeBlock}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:20px;color:#2D3142;font-size:13px">
|
||||
{{.ServiceOwner}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:20px;color:#2D3142;font-size:13px">
|
||||
{{.OwnerAddress}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<p style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:montserrat, roboto;line-height:20px;color:#2D3142;font-size:13px">
|
||||
{{.OwnerPhone}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>`))
|
||||
)
|
||||
|
||||
func renderTemplate(tpl *template.Template, data any) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := tpl.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func renderUnsubscribeBlock(data any) (string, error) {
|
||||
return renderTemplate(unsubscribeBlockTpl, data)
|
||||
}
|
||||
|
||||
func renderButtonBlock(data any) (string, error) {
|
||||
return renderTemplate(buttonBlockTpl, data)
|
||||
}
|
||||
|
||||
func renderOneButtonEmail(data any) (string, error) {
|
||||
return renderTemplate(oneButtonEmailTpl, data)
|
||||
}
|
||||
@@ -43,13 +43,13 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
@@ -59,5 +59,5 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
)
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -144,8 +144,8 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -215,8 +215,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -47,6 +47,12 @@ func (n *Enforcer) Enforce(
|
||||
permissionRef, accountRef, organizationRef, objectRef primitive.ObjectID,
|
||||
action model.Action,
|
||||
) (bool, error) {
|
||||
if organizationRef.IsZero() {
|
||||
n.logger.Warn("Missing organization context", mzap.ObjRef("account_ref", accountRef),
|
||||
mzap.ObjRef("organization_ref", organizationRef), mzap.ObjRef("permission_ref", permissionRef),
|
||||
mzap.ObjRef("object", objectRef), zap.String("action", string(action)))
|
||||
return false, merrors.InvalidArgument("organization context missing", "organizationRef")
|
||||
}
|
||||
roleAssignments, err := n.rdb.Roles(ctx, accountRef, organizationRef)
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
n.logger.Debug("No roles defined for account", mzap.ObjRef("account_ref", accountRef))
|
||||
|
||||
@@ -2,7 +2,10 @@ package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/tech/sendico/pkg/auth"
|
||||
@@ -28,7 +31,6 @@ import (
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
mutil "github.com/tech/sendico/pkg/mutil/config"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -44,6 +46,13 @@ type Config struct {
|
||||
DatabaseEnv *string `mapstructure:"database_env"`
|
||||
Host *string `mapstructure:"host"`
|
||||
HostEnv *string `mapstructure:"host_env"`
|
||||
Hosts []string `mapstructure:"hosts,omitempty"`
|
||||
HostsEnvPrefix *string `mapstructure:"hosts_env_prefix,omitempty"`
|
||||
HostsEnvPrefixEnv *string `mapstructure:"hosts_env_prefix_env,omitempty"`
|
||||
PortsEnvPrefix *string `mapstructure:"ports_env_prefix,omitempty"`
|
||||
PortsEnvPrefixEnv *string `mapstructure:"ports_env_prefix_env,omitempty"`
|
||||
URI *string `mapstructure:"uri,omitempty"`
|
||||
URIEnv *string `mapstructure:"uri_env,omitempty"`
|
||||
AuthSource *string `mapstructure:"auth_source,omitempty"`
|
||||
AuthSourceEnv *string `mapstructure:"auth_source_env,omitempty"`
|
||||
AuthMechanism *string `mapstructure:"auth_mechanism,omitempty"`
|
||||
@@ -55,6 +64,7 @@ type Config struct {
|
||||
|
||||
type DBSettings struct {
|
||||
Host string
|
||||
Hosts []string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
@@ -62,6 +72,7 @@ type DBSettings struct {
|
||||
AuthSource string
|
||||
AuthMechanism string
|
||||
ReplicaSet string
|
||||
URI string
|
||||
}
|
||||
|
||||
func newProtectedDB[T any](
|
||||
@@ -87,6 +98,19 @@ func Config2DBSettings(logger mlogger.Logger, config *Config) *DBSettings {
|
||||
p.AuthSource = mutil.GetConfigValue(logger, "auth_source", "auth_source_env", config.AuthSource, config.AuthSourceEnv)
|
||||
p.AuthMechanism = mutil.GetConfigValue(logger, "auth_mechanism", "auth_mechanism_env", config.AuthMechanism, config.AuthMechanismEnv)
|
||||
p.ReplicaSet = mutil.GetConfigValue(logger, "replica_set", "replica_set_env", config.ReplicaSet, config.ReplicaSetEnv)
|
||||
p.URI = mutil.GetConfigValue(logger, "uri", "uri_env", config.URI, config.URIEnv)
|
||||
|
||||
hostPrefix := mutil.GetConfigValue(logger, "hosts_env_prefix", "hosts_env_prefix_env", config.HostsEnvPrefix, config.HostsEnvPrefixEnv)
|
||||
portPrefix := mutil.GetConfigValue(logger, "ports_env_prefix", "ports_env_prefix_env", config.PortsEnvPrefix, config.PortsEnvPrefixEnv)
|
||||
|
||||
if hostPrefix == "" && p.ReplicaSet != "" {
|
||||
hostPrefix = "MONGO_HOSTS_"
|
||||
}
|
||||
if portPrefix == "" && p.ReplicaSet != "" {
|
||||
portPrefix = "MONGO_PORTS_"
|
||||
}
|
||||
|
||||
p.Hosts = collectReplicaHosts(config.Hosts, p.ReplicaSet, p.Port, hostPrefix, portPrefix)
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -101,21 +125,19 @@ func decodeConfig(logger mlogger.Logger, settings model.SettingsT) (*Config, *DB
|
||||
}
|
||||
|
||||
func dialMongo(logger mlogger.Logger, dbSettings *DBSettings) (*mongo.Client, error) {
|
||||
cred := options.Credential{
|
||||
AuthMechanism: dbSettings.AuthMechanism,
|
||||
AuthSource: dbSettings.AuthSource,
|
||||
Username: dbSettings.User,
|
||||
Password: dbSettings.Password,
|
||||
}
|
||||
dbURI := buildURI(dbSettings)
|
||||
opts := buildOptions(dbSettings)
|
||||
|
||||
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(dbURI).SetAuth(cred))
|
||||
client, err := mongo.Connect(context.Background(), opts)
|
||||
if err != nil {
|
||||
logger.Error("Unable to connect to database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Connected successfully", zap.String("uri", dbURI))
|
||||
if dbSettings.URI != "" {
|
||||
logger.Info("Connected successfully", zap.Bool("uri_provided", true))
|
||||
} else {
|
||||
logger.Info("Connected successfully", zap.Strings("hosts", opts.Hosts), zap.String("replica_set", dbSettings.ReplicaSet))
|
||||
}
|
||||
|
||||
if err := client.Ping(context.Background(), readpref.Primary()); err != nil {
|
||||
logger.Error("Unable to ping database", zap.Error(err))
|
||||
@@ -199,6 +221,70 @@ func (db *DB) TransactionFactory() transaction.Factory {
|
||||
return transactionimp.CreateFactory(db.client)
|
||||
}
|
||||
|
||||
func collectReplicaHosts(configuredHosts []string, replicaSet, defaultPort, hostPrefix, portPrefix string) []string {
|
||||
normalize := func(host, port string) string {
|
||||
host = strings.TrimSpace(host)
|
||||
port = strings.TrimSpace(port)
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
// If host already has a port, keep it; otherwise apply provided/default port.
|
||||
if _, _, err := net.SplitHostPort(host); err == nil {
|
||||
return host
|
||||
}
|
||||
if port != "" {
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
if defaultPort != "" {
|
||||
return net.JoinHostPort(host, defaultPort)
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
appendHost := func(list []string, host, port string) []string {
|
||||
if normalized := normalize(host, port); normalized != "" {
|
||||
return append(list, normalized)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
var hosts []string
|
||||
for _, h := range configuredHosts {
|
||||
hosts = appendHost(hosts, h, "")
|
||||
}
|
||||
|
||||
if replicaSet == "" || hostPrefix == "" {
|
||||
return hosts
|
||||
}
|
||||
|
||||
index := 0
|
||||
for {
|
||||
hostEnv := os.Getenv(fmt.Sprintf("%s%d", hostPrefix, index))
|
||||
portEnv := ""
|
||||
if portPrefix != "" {
|
||||
portEnv = os.Getenv(fmt.Sprintf("%s%d", portPrefix, index))
|
||||
}
|
||||
|
||||
if hostEnv == "" && index == 0 {
|
||||
hostEnv = os.Getenv(fmt.Sprintf("%s%d", hostPrefix, 1))
|
||||
if portPrefix != "" {
|
||||
portEnv = os.Getenv(fmt.Sprintf("%s%d", portPrefix, 1))
|
||||
}
|
||||
if hostEnv == "" {
|
||||
break
|
||||
}
|
||||
index = 1
|
||||
} else if hostEnv == "" {
|
||||
break
|
||||
}
|
||||
|
||||
hosts = appendHost(hosts, hostEnv, portEnv)
|
||||
index++
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (db *DB) Permissions() auth.Provider {
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -1,22 +1,49 @@
|
||||
package mongo
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func buildURI(s *DBSettings) string {
|
||||
u := &url.URL{
|
||||
Scheme: "mongodb",
|
||||
Host: s.Host,
|
||||
Path: "/" + url.PathEscape(s.Database), // /my%20db
|
||||
func buildOptions(s *DBSettings) *options.ClientOptions {
|
||||
opts := options.Client()
|
||||
|
||||
if s.URI != "" {
|
||||
return opts.ApplyURI(s.URI)
|
||||
}
|
||||
|
||||
hosts := make([]string, 0, len(s.Hosts)+1)
|
||||
for _, h := range s.Hosts {
|
||||
if trimmed := strings.TrimSpace(h); trimmed != "" {
|
||||
hosts = append(hosts, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
if len(hosts) == 0 && s.Host != "" {
|
||||
host := s.Host
|
||||
if _, _, err := net.SplitHostPort(host); err != nil && s.Port != "" {
|
||||
host = net.JoinHostPort(host, s.Port)
|
||||
}
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
if len(hosts) > 0 {
|
||||
opts.SetHosts(hosts)
|
||||
}
|
||||
|
||||
q := url.Values{}
|
||||
if s.ReplicaSet != "" {
|
||||
q.Set("replicaSet", s.ReplicaSet)
|
||||
opts.SetReplicaSet(s.ReplicaSet)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
return u.String()
|
||||
cred := options.Credential{
|
||||
AuthMechanism: s.AuthMechanism,
|
||||
AuthSource: s.AuthSource,
|
||||
Username: s.User,
|
||||
Password: s.Password,
|
||||
}
|
||||
opts.SetAuth(cred)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
@@ -76,7 +76,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
@@ -93,6 +93,6 @@ require (
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -108,8 +108,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -156,8 +156,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -269,8 +269,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -3,6 +3,7 @@ package model
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
@@ -16,8 +17,9 @@ const (
|
||||
|
||||
// ConfirmationCode stores verification codes for operations like login or payouts.
|
||||
type ConfirmationCode struct {
|
||||
AccountBoundBase `bson:",inline" json:",inline"`
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
|
||||
AccountRef primitive.ObjectID `bson:"accountRef" json:"accountRef"`
|
||||
Destination string `bson:"destination" json:"destination"`
|
||||
Target ConfirmationTarget `bson:"target" json:"target"`
|
||||
CodeHash []byte `bson:"codeHash" json:"-"`
|
||||
@@ -34,10 +36,3 @@ type ConfirmationCode struct {
|
||||
func (c *ConfirmationCode) Collection() string {
|
||||
return mservice.Confirmations
|
||||
}
|
||||
|
||||
func NewConfirmationCode(accountRef primitive.ObjectID) *ConfirmationCode {
|
||||
cc := &ConfirmationCode{}
|
||||
cc.SetID(primitive.NewObjectID())
|
||||
cc.AccountRef = &accountRef
|
||||
return cc
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ type UserDataBase struct {
|
||||
|
||||
type LoginData struct {
|
||||
UserDataBase `bson:",inline" json:",inline"`
|
||||
Password string `json:"password"`
|
||||
Password string ` bson:"-" json:"password"`
|
||||
}
|
||||
|
||||
type AccountData struct {
|
||||
@@ -21,6 +21,7 @@ func (ad *AccountData) ToAccount() *Account {
|
||||
AccountPublic: AccountPublic{
|
||||
AccountBase: AccountBase{
|
||||
Describable: ad.Describable,
|
||||
LastName: ad.LastName,
|
||||
},
|
||||
UserDataBase: ad.UserDataBase,
|
||||
},
|
||||
|
||||
@@ -40,6 +40,7 @@ const (
|
||||
Roles Type = "roles" // Represents roles in access control
|
||||
Storage Type = "storage" // Represents statuses of tasks or projects
|
||||
Tenants Type = "tenants" // Represents tenants managed in the system
|
||||
Wallets Type = "wallets" // Represents workflows for tasks or projects
|
||||
Workflows Type = "workflows" // Represents workflows for tasks or projects
|
||||
)
|
||||
|
||||
|
||||
@@ -77,6 +77,9 @@ service LedgerService {
|
||||
rpc GetBalance (GetBalanceRequest) returns (BalanceResponse);
|
||||
rpc GetJournalEntry (GetEntryRequest) returns (JournalEntryResponse);
|
||||
rpc GetStatement (GetStatementRequest) returns (StatementResponse);
|
||||
|
||||
// Lists ledger accounts for an organization.
|
||||
rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse);
|
||||
}
|
||||
|
||||
message CreateAccountRequest {
|
||||
@@ -192,3 +195,11 @@ message StatementResponse {
|
||||
repeated JournalEntryResponse entries = 1;
|
||||
string next_cursor = 2;
|
||||
}
|
||||
|
||||
message ListAccountsRequest {
|
||||
string organization_ref = 1;
|
||||
}
|
||||
|
||||
message ListAccountsResponse {
|
||||
repeated LedgerAccount accounts = 1;
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ api:
|
||||
chain: ARBITRUM_ONE
|
||||
token_symbol: USDT
|
||||
contract_address: ""
|
||||
ledger:
|
||||
address: sendico_ledger:50052
|
||||
address_env: LEDGER_ADDRESS
|
||||
dial_timeout_seconds: 5
|
||||
call_timeout_seconds: 5
|
||||
insecure: true
|
||||
|
||||
app:
|
||||
|
||||
|
||||
@@ -4,13 +4,15 @@ go 1.25.3
|
||||
|
||||
replace github.com/tech/sendico/pkg => ../pkg
|
||||
|
||||
replace github.com/tech/sendico/ledger => ../ledger
|
||||
|
||||
replace github.com/tech/sendico/chain/gateway => ../chain/gateway
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.2
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
@@ -19,12 +21,14 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tech/sendico/chain/gateway v0.1.0
|
||||
github.com/tech/sendico/ledger v0.0.0-00010101000000-000000000000
|
||||
github.com/tech/sendico/pkg v0.1.0
|
||||
github.com/testcontainers/testcontainers-go v0.33.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
||||
go.mongodb.org/mongo-driver v1.17.6
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/net v0.47.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/chizap v1.0.3
|
||||
)
|
||||
@@ -49,10 +53,10 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 // indirect
|
||||
github.com/aws/smithy-go v1.23.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
|
||||
@@ -96,7 +100,7 @@ require (
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
||||
github.com/nats-io/nkeys v0.4.11 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
@@ -114,7 +118,7 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/scram v1.2.0 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
@@ -131,7 +135,6 @@ require (
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
@@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrK
|
||||
github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I=
|
||||
@@ -32,16 +32,16 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0 h1:8FshVvnV2sr9kOSAbOnc/vwVmmAwMjOedKH6JW2ddPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.0/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 h1:OgQy/+0+Kc3khtqiEOk23xQAglXi3Tj0y5doOxbi5tg=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
||||
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM=
|
||||
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
@@ -177,8 +177,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -236,8 +236,8 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
|
||||
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -359,8 +359,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -239,11 +239,6 @@ func (s *service) JoinOrganization(
|
||||
s.logger.Debug("Account is already a member", mzap.StorableRef(org), mzap.StorableRef(account))
|
||||
return nil
|
||||
}
|
||||
org.Members = append(org.Members, account.ID)
|
||||
if err := s.orgDB.Update(ctx, *account.GetID(), org); err != nil {
|
||||
s.logger.Warn("Failed to update organization members list", zap.Error(err), mzap.StorableRef(account))
|
||||
return err
|
||||
}
|
||||
|
||||
role := &model.Role{
|
||||
DescriptionRef: roleDescID,
|
||||
@@ -251,7 +246,15 @@ func (s *service) JoinOrganization(
|
||||
AccountRef: account.ID,
|
||||
}
|
||||
if err := s.roleManager.Assign(ctx, role); err != nil {
|
||||
s.logger.Warn("Failed to assign role to account", zap.Error(err), mzap.StorableRef(account))
|
||||
s.logger.Warn("Failed to assign role to account", zap.Error(err), mzap.StorableRef(account),
|
||||
mzap.StorableRef(org), mzap.ObjRef("role_description_ref", roleDescID))
|
||||
return err
|
||||
}
|
||||
|
||||
org.Members = append(org.Members, account.ID)
|
||||
if err := s.orgDB.Update(ctx, *account.GetID(), org); err != nil {
|
||||
s.logger.Warn("Failed to update organization members list",
|
||||
zap.Error(err), mzap.StorableRef(account), mzap.StorableRef(org))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -9,6 +9,7 @@ type Config struct {
|
||||
Mw *mwa.Config `yaml:"middleware"`
|
||||
Storage *fsc.Config `yaml:"storage"`
|
||||
ChainGateway *ChainGatewayConfig `yaml:"chain_gateway"`
|
||||
Ledger *LedgerConfig `yaml:"ledger"`
|
||||
}
|
||||
|
||||
type ChainGatewayConfig struct {
|
||||
@@ -25,3 +26,11 @@ type ChainGatewayAssetConfig struct {
|
||||
TokenSymbol string `yaml:"token_symbol"`
|
||||
ContractAddress string `yaml:"contract_address"`
|
||||
}
|
||||
|
||||
type LedgerConfig struct {
|
||||
Address string `yaml:"address"`
|
||||
AddressEnv string `yaml:"address_env"`
|
||||
DialTimeoutSeconds int `yaml:"dial_timeout_seconds"`
|
||||
CallTimeoutSeconds int `yaml:"call_timeout_seconds"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
}
|
||||
|
||||
106
api/server/interface/api/sresponse/ledger.go
Normal file
106
api/server/interface/api/sresponse/ledger.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
)
|
||||
|
||||
type ledgerAccount struct {
|
||||
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||
OrganizationRef string `json:"organizationRef"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
AccountType string `json:"accountType"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
AllowNegative bool `json:"allowNegative"`
|
||||
IsSettlement bool `json:"isSettlement"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type ledgerAccountsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Accounts []ledgerAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
type ledgerMoney struct {
|
||||
Amount string `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
type ledgerBalance struct {
|
||||
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||
Balance *ledgerMoney `json:"balance,omitempty"`
|
||||
Version int64 `json:"version"`
|
||||
LastUpdated time.Time `json:"lastUpdated,omitempty"`
|
||||
}
|
||||
|
||||
type ledgerBalanceResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Balance ledgerBalance `json:"balance"`
|
||||
}
|
||||
|
||||
func LedgerAccounts(logger mlogger.Logger, accounts []*ledgerv1.LedgerAccount, accessToken *TokenData) http.HandlerFunc {
|
||||
dto := make([]ledgerAccount, 0, len(accounts))
|
||||
for _, acc := range accounts {
|
||||
dto = append(dto, toLedgerAccount(acc))
|
||||
}
|
||||
return response.Ok(logger, ledgerAccountsResponse{
|
||||
Accounts: dto,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func LedgerBalance(logger mlogger.Logger, resp *ledgerv1.BalanceResponse, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, ledgerBalanceResponse{
|
||||
Balance: toLedgerBalance(resp),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func toLedgerAccount(acc *ledgerv1.LedgerAccount) ledgerAccount {
|
||||
if acc == nil {
|
||||
return ledgerAccount{}
|
||||
}
|
||||
return ledgerAccount{
|
||||
LedgerAccountRef: acc.GetLedgerAccountRef(),
|
||||
OrganizationRef: acc.GetOrganizationRef(),
|
||||
AccountCode: acc.GetAccountCode(),
|
||||
AccountType: acc.GetAccountType().String(),
|
||||
Currency: acc.GetCurrency(),
|
||||
Status: acc.GetStatus().String(),
|
||||
AllowNegative: acc.GetAllowNegative(),
|
||||
IsSettlement: acc.GetIsSettlement(),
|
||||
Metadata: acc.GetMetadata(),
|
||||
CreatedAt: acc.GetCreatedAt().AsTime(),
|
||||
UpdatedAt: acc.GetUpdatedAt().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func toLedgerBalance(resp *ledgerv1.BalanceResponse) ledgerBalance {
|
||||
if resp == nil {
|
||||
return ledgerBalance{}
|
||||
}
|
||||
return ledgerBalance{
|
||||
LedgerAccountRef: resp.GetLedgerAccountRef(),
|
||||
Balance: toLedgerMoney(resp.GetBalance()),
|
||||
Version: resp.GetVersion(),
|
||||
LastUpdated: resp.GetLastUpdated().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func toLedgerMoney(m *moneyv1.Money) *ledgerMoney {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return &ledgerMoney{
|
||||
Amount: m.GetAmount(),
|
||||
Currency: m.GetCurrency(),
|
||||
}
|
||||
}
|
||||
132
api/server/interface/api/sresponse/wallet.go
Normal file
132
api/server/interface/api/sresponse/wallet.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type walletAsset struct {
|
||||
Chain string `json:"chain"`
|
||||
TokenSymbol string `json:"tokenSymbol"`
|
||||
ContractAddress string `json:"contractAddress"`
|
||||
}
|
||||
|
||||
type wallet struct {
|
||||
WalletRef string `json:"walletRef"`
|
||||
OrganizationRef string `json:"organizationRef"`
|
||||
OwnerRef string `json:"ownerRef"`
|
||||
Asset walletAsset `json:"asset"`
|
||||
DepositAddress string `json:"depositAddress"`
|
||||
Status string `json:"status"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type walletsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Wallets []wallet `json:"wallets"`
|
||||
Page *paginationv1.CursorPageResponse `json:"page,omitempty"`
|
||||
}
|
||||
|
||||
type walletMoney struct {
|
||||
Amount string `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
type walletBalance struct {
|
||||
Available *walletMoney `json:"available,omitempty"`
|
||||
PendingInbound *walletMoney `json:"pendingInbound,omitempty"`
|
||||
PendingOutbound *walletMoney `json:"pendingOutbound,omitempty"`
|
||||
CalculatedAt string `json:"calculatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type walletBalanceResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Balance walletBalance `json:"balance"`
|
||||
}
|
||||
|
||||
func Wallets(logger mlogger.Logger, resp *gatewayv1.ListManagedWalletsResponse, accessToken *TokenData) http.HandlerFunc {
|
||||
dto := walletsResponse{
|
||||
Page: resp.GetPage(),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
}
|
||||
dto.Wallets = make([]wallet, 0, len(resp.GetWallets()))
|
||||
for _, w := range resp.GetWallets() {
|
||||
dto.Wallets = append(dto.Wallets, toWallet(w))
|
||||
}
|
||||
return response.Ok(logger, dto)
|
||||
}
|
||||
|
||||
func WalletBalance(logger mlogger.Logger, bal *gatewayv1.WalletBalance, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, walletBalanceResponse{
|
||||
Balance: toWalletBalance(bal),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func toWallet(w *gatewayv1.ManagedWallet) wallet {
|
||||
if w == nil {
|
||||
return wallet{}
|
||||
}
|
||||
asset := w.GetAsset()
|
||||
chain := ""
|
||||
token := ""
|
||||
contract := ""
|
||||
if asset != nil {
|
||||
chain = asset.GetChain().String()
|
||||
token = asset.GetTokenSymbol()
|
||||
contract = asset.GetContractAddress()
|
||||
}
|
||||
return wallet{
|
||||
WalletRef: w.GetWalletRef(),
|
||||
OrganizationRef: w.GetOrganizationRef(),
|
||||
OwnerRef: w.GetOwnerRef(),
|
||||
Asset: walletAsset{
|
||||
Chain: chain,
|
||||
TokenSymbol: token,
|
||||
ContractAddress: contract,
|
||||
},
|
||||
DepositAddress: w.GetDepositAddress(),
|
||||
Status: w.GetStatus().String(),
|
||||
Metadata: w.GetMetadata(),
|
||||
CreatedAt: tsToString(w.GetCreatedAt()),
|
||||
UpdatedAt: tsToString(w.GetUpdatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func toWalletBalance(b *gatewayv1.WalletBalance) walletBalance {
|
||||
if b == nil {
|
||||
return walletBalance{}
|
||||
}
|
||||
return walletBalance{
|
||||
Available: toMoney(b.GetAvailable()),
|
||||
PendingInbound: toMoney(b.GetPendingInbound()),
|
||||
PendingOutbound: toMoney(b.GetPendingOutbound()),
|
||||
CalculatedAt: tsToString(b.GetCalculatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func toMoney(m *moneyv1.Money) *walletMoney {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return &walletMoney{
|
||||
Amount: m.GetAmount(),
|
||||
Currency: m.GetCurrency(),
|
||||
}
|
||||
}
|
||||
|
||||
func tsToString(ts *timestamppb.Timestamp) string {
|
||||
if ts == nil {
|
||||
return ""
|
||||
}
|
||||
return ts.AsTime().UTC().Format(time.RFC3339)
|
||||
}
|
||||
11
api/server/interface/services/ledger/ledger.go
Normal file
11
api/server/interface/services/ledger/ledger.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package ledger
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/server/interface/api"
|
||||
"github.com/tech/sendico/server/internal/server/ledgerapiimp"
|
||||
)
|
||||
|
||||
func Create(a api.API) (mservice.MicroService, error) {
|
||||
return ledgerapiimp.CreateAPI(a)
|
||||
}
|
||||
11
api/server/interface/services/wallet/wallet.go
Normal file
11
api/server/interface/services/wallet/wallet.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/server/interface/api"
|
||||
"github.com/tech/sendico/server/internal/server/walletapiimp"
|
||||
)
|
||||
|
||||
func Create(a api.API) (mservice.MicroService, error) {
|
||||
return walletapiimp.CreateAPI(a)
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"github.com/tech/sendico/server/interface/services/organization"
|
||||
"github.com/tech/sendico/server/interface/services/permission"
|
||||
"github.com/tech/sendico/server/interface/services/site"
|
||||
"github.com/tech/sendico/server/interface/services/wallet"
|
||||
"github.com/tech/sendico/server/interface/services/ledger"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -83,6 +85,8 @@ func (a *APIImp) installServices() error {
|
||||
srvf = append(srvf, logo.Create)
|
||||
srvf = append(srvf, permission.Create)
|
||||
srvf = append(srvf, site.Create)
|
||||
srvf = append(srvf, wallet.Create)
|
||||
srvf = append(srvf, ledger.Create)
|
||||
|
||||
for _, v := range srvf {
|
||||
if err := a.addMicroservice(v); err != nil {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
const pendingLoginTTLMinutes = 10
|
||||
|
||||
func (pr *PublicRouter) logUserIn(ctx context.Context, r *http.Request, req *srequest.Login) http.HandlerFunc {
|
||||
func (pr *PublicRouter) logUserIn(ctx context.Context, _ *http.Request, req *srequest.Login) http.HandlerFunc {
|
||||
// Get the account database entry
|
||||
trimmedLogin := strings.TrimSpace(req.Login)
|
||||
account, err := pr.db.GetByEmail(ctx, strings.ToLower(trimmedLogin))
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -31,10 +32,20 @@ func (a *AccountAPI) createOrg(ctx context.Context, sr *srequest.Signup, permiss
|
||||
return nil, merrors.DataConflict(fmt.Sprintf("invalid time zone '%s' provided, error %s", sr.OrganizationTimeZone, err.Error()))
|
||||
}
|
||||
|
||||
// explicitly set org ref for permission related checks as unprotected template implementation
|
||||
// is not aware of permisssions and won't set org
|
||||
orgRef := primitive.NewObjectID()
|
||||
org := &model.Organization{
|
||||
OrganizationBase: model.OrganizationBase{
|
||||
PermissionBound: model.PermissionBound{
|
||||
Base: storable.Base{
|
||||
ID: orgRef,
|
||||
CreatedAt: time.Now(),
|
||||
},
|
||||
PermissionRef: permissionRef,
|
||||
OrganizationBoundBase: model.OrganizationBoundBase{
|
||||
OrganizationRef: orgRef,
|
||||
},
|
||||
},
|
||||
Describable: model.Describable{
|
||||
Name: name,
|
||||
@@ -134,25 +145,30 @@ func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Sig
|
||||
a.logger.Warn("Failed to create organization", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization created", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
if err := a.openOrgWallet(ctx, org, sr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization wallet created", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
roleDescription, err := a.pmanager.Role().Create(ctx, org.ID, &sr.OwnerRole)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create owner role", zap.Error(err), zap.String("login", newAccount.Login))
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization owner role created", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
if err := a.grantAllPermissions(ctx, org.ID, roleDescription.ID, newAccount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization owner role permissions granted", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
if err := a.accService.CreateAccount(ctx, org, newAccount, roleDescription.ID); err != nil {
|
||||
a.logger.Warn("Failed to create account", zap.Error(err), zap.String("login", newAccount.Login))
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization owner account registered", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -84,9 +84,9 @@ func CreateAPI(a eapi.API) (*ConfirmationAPI, error) {
|
||||
signature: middleware.SignatureConf(a.Config().Mw),
|
||||
}
|
||||
|
||||
a.Register().PendingAccountHandler(p.Name(), "/confirmations", api.Post, p.requestCode)
|
||||
a.Register().PendingAccountHandler(p.Name(), "/confirmations/resend", api.Post, p.resendCode)
|
||||
a.Register().PendingAccountHandler(p.Name(), "/confirmations/verify", api.Post, p.verifyCode)
|
||||
a.Register().PendingAccountHandler(p.Name(), "/", api.Post, p.requestCode)
|
||||
a.Register().PendingAccountHandler(p.Name(), "/resend", api.Post, p.resendCode)
|
||||
a.Register().PendingAccountHandler(p.Name(), "/verify", api.Post, p.verifyCode)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (a *ConfirmationAPI) sendCode(account *model.Account, target model.Confirma
|
||||
if err := a.producer.SendMessage(cnotifications.Code(a.Name(), account.ID, destination, target, code)); err != nil {
|
||||
a.logger.Warn("Failed to send confirmation code notification", zap.Error(err), mzap.ObjRef("account_ref", account.ID))
|
||||
}
|
||||
a.logger.Debug("Confirmation code debug dump (do not log in production)", zap.String("code", code))
|
||||
a.logger.Debug("Confirmation code debug dump", zap.String("code", code))
|
||||
}
|
||||
|
||||
func maskEmail(email string) string {
|
||||
|
||||
@@ -147,20 +147,20 @@ func (s *ConfirmationStore) buildRecord(
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
rec := model.NewConfirmationCode(accountRef)
|
||||
rec.Destination = destination
|
||||
rec.Target = target
|
||||
rec.CodeHash = hashCode(salt, code)
|
||||
rec.Salt = salt
|
||||
rec.ExpiresAt = now.Add(cfg.TTL)
|
||||
rec.MaxAttempts = cfg.MaxAttempts
|
||||
rec.ResendLimit = cfg.ResendLimit
|
||||
rec.CooldownUntil = now.Add(cfg.Cooldown)
|
||||
rec.Used = false
|
||||
rec.Attempts = 0
|
||||
rec.ResendCount = 0
|
||||
rec.CreatedAt = now
|
||||
rec.UpdatedAt = now
|
||||
rec := &model.ConfirmationCode{
|
||||
AccountRef: accountRef,
|
||||
Destination: destination,
|
||||
Target: target,
|
||||
CodeHash: hashCode(salt, code),
|
||||
Salt: salt,
|
||||
ExpiresAt: now.Add(cfg.TTL),
|
||||
MaxAttempts: cfg.MaxAttempts,
|
||||
ResendLimit: cfg.ResendLimit,
|
||||
CooldownUntil: now.Add(cfg.Cooldown),
|
||||
Used: false,
|
||||
Attempts: 0,
|
||||
ResendCount: 0,
|
||||
}
|
||||
|
||||
return code, salt, rec, nil
|
||||
}
|
||||
|
||||
52
api/server/internal/server/ledgerapiimp/balance.go
Normal file
52
api/server/internal/server/ledgerapiimp/balance.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package ledgerapiimp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *LedgerAPI) getBalance(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for ledger balance", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
accountRef := strings.TrimSpace(a.aph.GetID(r))
|
||||
if accountRef == "" {
|
||||
return response.BadReference(a.logger, a.Name(), a.aph.Name(), a.aph.GetID(r), errors.New("ledger account reference is required"))
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
res, err := a.enf.Enforce(ctx, a.balancePerm, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check ledger balance access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()), zap.String("ledger_account_ref", accountRef))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !res {
|
||||
a.logger.Debug("Access denied when reading ledger balance", zap.String(a.oph.Name(), orgRef.Hex()), zap.String("ledger_account_ref", accountRef))
|
||||
return response.AccessDenied(a.logger, a.Name(), "ledger balance read permission denied")
|
||||
}
|
||||
if a.client == nil {
|
||||
return response.Internal(a.logger, mservice.Ledger, errors.New("ledger client is not configured"))
|
||||
}
|
||||
|
||||
resp, err := a.client.GetBalance(ctx, &ledgerv1.GetBalanceRequest{
|
||||
LedgerAccountRef: accountRef,
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to fetch ledger balance", zap.Error(err), zap.String("ledger_account_ref", accountRef))
|
||||
return response.Auto(a.logger, mservice.Ledger, err)
|
||||
}
|
||||
|
||||
return sresponse.LedgerBalance(a.logger, resp, token)
|
||||
}
|
||||
46
api/server/internal/server/ledgerapiimp/list.go
Normal file
46
api/server/internal/server/ledgerapiimp/list.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package ledgerapiimp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *LedgerAPI) listAccounts(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for ledger account list", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
res, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check ledger accounts access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !res {
|
||||
a.logger.Debug("Access denied when listing ledger accounts", zap.String(a.oph.Name(), orgRef.Hex()))
|
||||
return response.AccessDenied(a.logger, a.Name(), "ledger accounts read permission denied")
|
||||
}
|
||||
if a.client == nil {
|
||||
return response.Internal(a.logger, mservice.Ledger, errors.New("ledger client is not configured"))
|
||||
}
|
||||
|
||||
resp, err := a.client.ListAccounts(ctx, &ledgerv1.ListAccountsRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to list ledger accounts", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||
return response.Auto(a.logger, mservice.Ledger, err)
|
||||
}
|
||||
|
||||
return sresponse.LedgerAccounts(a.logger, resp.GetAccounts(), token)
|
||||
}
|
||||
110
api/server/internal/server/ledgerapiimp/service.go
Normal file
110
api/server/internal/server/ledgerapiimp/service.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package ledgerapiimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||
api "github.com/tech/sendico/pkg/api/http"
|
||||
"github.com/tech/sendico/pkg/auth"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
eapi "github.com/tech/sendico/server/interface/api"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ledgerClient interface {
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type LedgerAPI struct {
|
||||
logger mlogger.Logger
|
||||
client ledgerClient
|
||||
enf auth.Enforcer
|
||||
oph mutil.ParamHelper
|
||||
aph mutil.ParamHelper
|
||||
permissionRef primitive.ObjectID
|
||||
balancePerm primitive.ObjectID
|
||||
}
|
||||
|
||||
func (a *LedgerAPI) Name() mservice.Type { return mservice.LedgerAccounts }
|
||||
|
||||
func (a *LedgerAPI) Finish(ctx context.Context) error {
|
||||
if a.client != nil {
|
||||
if err := a.client.Close(); err != nil {
|
||||
a.logger.Warn("Failed to close ledger client", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateAPI(apiCtx eapi.API) (*LedgerAPI, error) {
|
||||
p := &LedgerAPI{
|
||||
logger: apiCtx.Logger().Named(mservice.LedgerAccounts),
|
||||
enf: apiCtx.Permissions().Enforcer(),
|
||||
oph: mutil.CreatePH(mservice.Organizations),
|
||||
aph: mutil.CreatePH("ledger_account"),
|
||||
}
|
||||
|
||||
desc, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.LedgerAccounts)
|
||||
if err != nil {
|
||||
p.logger.Warn("Failed to fetch ledger accounts permission description", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
p.permissionRef = desc.ID
|
||||
|
||||
bdesc, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.LedgerBalances)
|
||||
if err != nil {
|
||||
p.logger.Warn("Failed to fetch ledger balances permission description", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
p.balancePerm = bdesc.ID
|
||||
|
||||
if err := p.initLedgerClient(apiCtx.Config().Ledger); err != nil {
|
||||
p.logger.Error("Failed to initialize ledger client", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listAccounts)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.aph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getBalance)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (a *LedgerAPI) initLedgerClient(cfg *eapi.LedgerConfig) error {
|
||||
if cfg == nil {
|
||||
return merrors.InvalidArgument("ledger configuration is not provided")
|
||||
}
|
||||
|
||||
address := strings.TrimSpace(cfg.Address)
|
||||
if address == "" {
|
||||
address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
|
||||
}
|
||||
if address == "" {
|
||||
return merrors.InvalidArgument(fmt.Sprintf("ledger address is not specified and address env %s is empty", cfg.AddressEnv))
|
||||
}
|
||||
|
||||
clientCfg := ledgerclient.Config{
|
||||
Address: address,
|
||||
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
||||
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
||||
Insecure: cfg.Insecure,
|
||||
}
|
||||
|
||||
client, err := ledgerclient.New(context.Background(), clientCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.client = client
|
||||
return nil
|
||||
}
|
||||
55
api/server/internal/server/walletapiimp/balance.go
Normal file
55
api/server/internal/server/walletapiimp/balance.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package walletapiimp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *WalletAPI) getWalletBalance(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for wallet balance", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
walletRef := strings.TrimSpace(a.wph.GetID(r))
|
||||
if walletRef == "" {
|
||||
return response.BadReference(a.logger, a.Name(), a.wph.Name(), a.wph.GetID(r), errors.New("wallet reference is required"))
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
res, err := a.enf.Enforce(ctx, a.balancesPermissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check wallet balance permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()), zap.String("wallet_ref", walletRef))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !res {
|
||||
a.logger.Debug("Access denied when reading wallet balance", zap.String(a.oph.Name(), orgRef.Hex()), zap.String("wallet_ref", walletRef))
|
||||
return response.AccessDenied(a.logger, a.Name(), "wallet balance read permission denied")
|
||||
}
|
||||
if a.chainGateway == nil {
|
||||
return response.Internal(a.logger, mservice.ChainGateway, errors.New("chain gateway client is not configured"))
|
||||
}
|
||||
|
||||
resp, err := a.chainGateway.GetWalletBalance(ctx, &gatewayv1.GetWalletBalanceRequest{WalletRef: walletRef})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to fetch wallet balance", zap.Error(err), zap.String("wallet_ref", walletRef))
|
||||
return response.Auto(a.logger, mservice.ChainGateway, err)
|
||||
}
|
||||
|
||||
bal := resp.GetBalance()
|
||||
if bal == nil {
|
||||
a.logger.Warn("Wallet balance missing in response", zap.String("wallet_ref", walletRef))
|
||||
return response.Auto(a.logger, mservice.ChainGateway, errors.New("wallet balance not available"))
|
||||
}
|
||||
|
||||
return sresponse.WalletBalance(a.logger, bal, token)
|
||||
}
|
||||
52
api/server/internal/server/walletapiimp/list.go
Normal file
52
api/server/internal/server/walletapiimp/list.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package walletapiimp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *WalletAPI) listWallets(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for wallet list", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
res, err := a.enf.Enforce(ctx, a.walletsPermissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionRead)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check chain wallet access permissions", zap.Error(err), zap.String(a.oph.Name(), orgRef.Hex()))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !res {
|
||||
a.logger.Debug("Access denied when listing organization wallets", zap.String(a.oph.Name(), orgRef.Hex()))
|
||||
return response.AccessDenied(a.logger, a.Name(), "wallets read permission denied")
|
||||
}
|
||||
if a.chainGateway == nil {
|
||||
return response.Internal(a.logger, mservice.ChainGateway, errors.New("chain gateway client is not configured"))
|
||||
}
|
||||
|
||||
req := &gatewayv1.ListManagedWalletsRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
}
|
||||
if owner := strings.TrimSpace(r.URL.Query().Get("owner_ref")); owner != "" {
|
||||
req.OwnerRef = owner
|
||||
}
|
||||
|
||||
resp, err := a.chainGateway.ListManagedWallets(ctx, req)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to list managed wallets", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||
return response.Auto(a.logger, mservice.ChainGateway, err)
|
||||
}
|
||||
|
||||
return sresponse.Wallets(a.logger, resp, token)
|
||||
}
|
||||
115
api/server/internal/server/walletapiimp/service.go
Normal file
115
api/server/internal/server/walletapiimp/service.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package walletapiimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
chaingatewayclient "github.com/tech/sendico/chain/gateway/client"
|
||||
api "github.com/tech/sendico/pkg/api/http"
|
||||
"github.com/tech/sendico/pkg/auth"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
|
||||
eapi "github.com/tech/sendico/server/interface/api"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type WalletAPI struct {
|
||||
logger mlogger.Logger
|
||||
chainGateway chainWalletClient
|
||||
enf auth.Enforcer
|
||||
oph mutil.ParamHelper
|
||||
wph mutil.ParamHelper
|
||||
walletsPermissionRef primitive.ObjectID
|
||||
balancesPermissionRef primitive.ObjectID
|
||||
}
|
||||
|
||||
type chainWalletClient interface {
|
||||
ListManagedWallets(ctx context.Context, req *gatewayv1.ListManagedWalletsRequest) (*gatewayv1.ListManagedWalletsResponse, error)
|
||||
GetWalletBalance(ctx context.Context, req *gatewayv1.GetWalletBalanceRequest) (*gatewayv1.GetWalletBalanceResponse, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
func (a *WalletAPI) Name() mservice.Type { return mservice.ChainWallets }
|
||||
|
||||
func (a *WalletAPI) Finish(ctx context.Context) error {
|
||||
if a.chainGateway != nil {
|
||||
if err := a.chainGateway.Close(); err != nil {
|
||||
a.logger.Warn("Failed to close chain gateway client", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
|
||||
p := &WalletAPI{
|
||||
logger: apiCtx.Logger().Named(mservice.Wallets),
|
||||
enf: apiCtx.Permissions().Enforcer(),
|
||||
oph: mutil.CreatePH(mservice.Organizations),
|
||||
wph: mutil.CreatePH(mservice.Wallets),
|
||||
}
|
||||
|
||||
walletsPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWallets)
|
||||
if err != nil {
|
||||
p.logger.Warn("Failed to fetch chain wallets permission policy description", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
p.walletsPermissionRef = walletsPolicy.ID
|
||||
|
||||
balancesPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWalletBalances)
|
||||
if err != nil {
|
||||
p.logger.Warn("Failed to fetch chain wallet balances permission policy description", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
p.balancesPermissionRef = balancesPolicy.ID
|
||||
|
||||
cfg := apiCtx.Config()
|
||||
if cfg == nil {
|
||||
p.logger.Error("Failed to fetch service configuration")
|
||||
return nil, merrors.InvalidArgument("No configuration provided")
|
||||
}
|
||||
if err := p.initChainGateway(cfg.ChainGateway); err != nil {
|
||||
p.logger.Error("Failed to initialize chain gateway client", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listWallets)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.wph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getWalletBalance)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (a *WalletAPI) initChainGateway(cfg *eapi.ChainGatewayConfig) error {
|
||||
if cfg == nil {
|
||||
return merrors.InvalidArgument("chain gateway configuration is not provided")
|
||||
}
|
||||
|
||||
cfg.Address = strings.TrimSpace(cfg.Address)
|
||||
if cfg.Address == "" {
|
||||
cfg.Address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
|
||||
}
|
||||
if cfg.Address == "" {
|
||||
return merrors.InvalidArgument(fmt.Sprintf("chain gateway address is not specified and address env %s is empty", cfg.AddressEnv))
|
||||
}
|
||||
|
||||
clientCfg := chaingatewayclient.Config{
|
||||
Address: cfg.Address,
|
||||
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
||||
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
||||
Insecure: cfg.Insecure,
|
||||
}
|
||||
|
||||
client, err := chaingatewayclient.New(context.Background(), clientCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.chainGateway = client
|
||||
return nil
|
||||
}
|
||||
@@ -8,6 +8,13 @@ MONGO_REPLICA_SET=sendico-rs
|
||||
MONGO_AUTH_SOURCE=admin
|
||||
MONGO_DATABASE=sendico
|
||||
MONGO_ARCH=linux/arm64
|
||||
MONGO_HOSTS_0=sendico_db1
|
||||
MONGO_PORTS_0=27017
|
||||
MONGO_HOSTS_1=sendico_db2
|
||||
MONGO_PORTS_1=27017
|
||||
MONGO_HOSTS_2=sendico_db3
|
||||
MONGO_PORTS_2=27017
|
||||
|
||||
PERMISSION_MODEL=/app/env/permissions_model.conf
|
||||
PERMISSION_COLLECTION=permissions
|
||||
PERMISSION_TIMEOUT=5
|
||||
@@ -81,6 +88,7 @@ LEDGER_COMPOSE_PROJECT=sendico-ledger
|
||||
LEDGER_SERVICE_NAME=sendico_ledger
|
||||
LEDGER_GRPC_PORT=50052
|
||||
LEDGER_METRICS_PORT=9401
|
||||
LEDGER_ADDRESS=sendico_ledger:50520
|
||||
|
||||
# Ledger Mongo settings
|
||||
LEDGER_MONGO_HOST=sendico_db1
|
||||
|
||||
@@ -29,7 +29,8 @@ services:
|
||||
NATS_PORT: ${NATS_PORT}
|
||||
NATS_USER: ${NATS_USER}
|
||||
NATS_PASSWORD: ${NATS_PASSWORD}
|
||||
CHAIN_GATEWAY_ADDRESS: ${CHAIN_GATEWAY_ADDRESS}
|
||||
CHAIN_GATEWAY_ADDRESS: ${CHAIN_GATEWAY_SERVICE_NAME}:${CHAIN_GATEWAY_GRPC_PORT}
|
||||
LEDGER_ADDRESS: ${LEDGER_SERVICE_NAME}:${LEDGER_GRPC_PORT}
|
||||
MONGO_HOST: ${MONGO_HOST}
|
||||
MONGO_PORT: ${MONGO_PORT}
|
||||
MONGO_DATABASE: ${MONGO_DATABASE}
|
||||
@@ -37,6 +38,12 @@ services:
|
||||
MONGO_PASSWORD: ${MONGO_PASSWORD}
|
||||
MONGO_AUTH_SOURCE: ${MONGO_AUTH_SOURCE}
|
||||
MONGO_REPLICA_SET: ${MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
PERMISSION_MODEL: ${PERMISSION_MODEL}
|
||||
PERMISSION_COLLECTION: ${PERMISSION_COLLECTION}
|
||||
PERMISSION_TIMEOUT: ${PERMISSION_TIMEOUT}
|
||||
|
||||
@@ -25,6 +25,12 @@ services:
|
||||
FEES_MONGO_PASSWORD: ${FEES_MONGO_PASSWORD}
|
||||
FEES_MONGO_AUTH_SOURCE: ${FEES_MONGO_AUTH_SOURCE}
|
||||
FEES_MONGO_REPLICA_SET: ${FEES_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
FEES_GRPC_PORT: ${FEES_GRPC_PORT}
|
||||
FEES_METRICS_PORT: ${FEES_METRICS_PORT}
|
||||
NATS_URL: ${NATS_URL}
|
||||
|
||||
@@ -33,6 +33,12 @@ services:
|
||||
CHAIN_GATEWAY_MONGO_PASSWORD: ${CHAIN_GATEWAY_MONGO_PASSWORD}
|
||||
CHAIN_GATEWAY_MONGO_AUTH_SOURCE: ${CHAIN_GATEWAY_MONGO_AUTH_SOURCE}
|
||||
CHAIN_GATEWAY_MONGO_REPLICA_SET: ${CHAIN_GATEWAY_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
NATS_URL: ${NATS_URL}
|
||||
NATS_HOST: ${NATS_HOST}
|
||||
NATS_PORT: ${NATS_PORT}
|
||||
|
||||
@@ -25,6 +25,12 @@ services:
|
||||
FX_MONGO_PASSWORD: ${FX_MONGO_PASSWORD}
|
||||
FX_MONGO_AUTH_SOURCE: ${FX_MONGO_AUTH_SOURCE}
|
||||
FX_MONGO_REPLICA_SET: ${FX_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
FX_INGESTOR_METRICS_PORT: ${FX_INGESTOR_METRICS_PORT}
|
||||
command: ["--config.file", "/app/config.yml"]
|
||||
ports:
|
||||
|
||||
@@ -25,6 +25,12 @@ services:
|
||||
FX_MONGO_PASSWORD: ${FX_MONGO_PASSWORD}
|
||||
FX_MONGO_AUTH_SOURCE: ${FX_MONGO_AUTH_SOURCE}
|
||||
FX_MONGO_REPLICA_SET: ${FX_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
FX_ORACLE_GRPC_PORT: ${FX_ORACLE_GRPC_PORT}
|
||||
FX_ORACLE_METRICS_PORT: ${FX_ORACLE_METRICS_PORT}
|
||||
NATS_URL: ${FX_NATS_URL}
|
||||
|
||||
@@ -25,6 +25,12 @@ services:
|
||||
LEDGER_MONGO_PASSWORD: ${LEDGER_MONGO_PASSWORD}
|
||||
LEDGER_MONGO_AUTH_SOURCE: ${LEDGER_MONGO_AUTH_SOURCE}
|
||||
LEDGER_MONGO_REPLICA_SET: ${LEDGER_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
LEDGER_GRPC_PORT: ${LEDGER_GRPC_PORT}
|
||||
LEDGER_METRICS_PORT: ${LEDGER_METRICS_PORT}
|
||||
NATS_URL: ${NATS_URL}
|
||||
|
||||
@@ -41,6 +41,12 @@ services:
|
||||
MONGO_PASSWORD: ${MONGO_PASSWORD}
|
||||
MONGO_AUTH_SOURCE: ${MONGO_AUTH_SOURCE}
|
||||
MONGO_REPLICA_SET: ${MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
PERMISSION_MODEL: ${PERMISSION_MODEL}
|
||||
PERMISSION_COLLECTION: ${PERMISSION_COLLECTION}
|
||||
PERMISSION_TIMEOUT: ${PERMISSION_TIMEOUT}
|
||||
|
||||
@@ -25,6 +25,12 @@ services:
|
||||
PAYMENTS_MONGO_PASSWORD: ${PAYMENTS_MONGO_PASSWORD}
|
||||
PAYMENTS_MONGO_AUTH_SOURCE: ${PAYMENTS_MONGO_AUTH_SOURCE}
|
||||
PAYMENTS_MONGO_REPLICA_SET: ${PAYMENTS_MONGO_REPLICA_SET}
|
||||
MONGO_HOSTS_0: ${MONGO_HOSTS_0}
|
||||
MONGO_PORTS_0: ${MONGO_PORTS_0}
|
||||
MONGO_HOSTS_1: ${MONGO_HOSTS_1}
|
||||
MONGO_PORTS_1: ${MONGO_PORTS_1}
|
||||
MONGO_HOSTS_2: ${MONGO_HOSTS_2}
|
||||
MONGO_PORTS_2: ${MONGO_PORTS_2}
|
||||
NATS_URL: ${NATS_URL}
|
||||
NATS_HOST: ${NATS_HOST}
|
||||
NATS_PORT: ${NATS_PORT}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/models/confirmation_target.dart';
|
||||
import 'package:pshared/api/requests/tokens/session_identifier.dart';
|
||||
|
||||
part 'login_confirmation.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class LoginConfirmationRequest {
|
||||
final ConfirmationTarget target;
|
||||
final String? destination;
|
||||
|
||||
const LoginConfirmationRequest({
|
||||
this.target = ConfirmationTarget.login,
|
||||
this.destination,
|
||||
});
|
||||
|
||||
factory LoginConfirmationRequest.fromJson(Map<String, dynamic> json) => _$LoginConfirmationRequestFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$LoginConfirmationRequestToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class LoginConfirmationVerifyRequest {
|
||||
final ConfirmationTarget target;
|
||||
final String code;
|
||||
final String? destination;
|
||||
final SessionIdentifierDto sessionIdentifier;
|
||||
|
||||
const LoginConfirmationVerifyRequest({
|
||||
this.target = ConfirmationTarget.login,
|
||||
required this.code,
|
||||
this.destination,
|
||||
required this.sessionIdentifier,
|
||||
});
|
||||
|
||||
factory LoginConfirmationVerifyRequest.fromJson(Map<String, dynamic> json) => _$LoginConfirmationVerifyRequestFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$LoginConfirmationVerifyRequestToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'session_identifier.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class SessionIdentifierDto {
|
||||
final String clientId;
|
||||
final String deviceId;
|
||||
|
||||
const SessionIdentifierDto({
|
||||
required this.clientId,
|
||||
required this.deviceId,
|
||||
});
|
||||
|
||||
factory SessionIdentifierDto.fromJson(Map<String, dynamic> json) => _$SessionIdentifierDtoFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$SessionIdentifierDtoToJson(this);
|
||||
}
|
||||
@@ -1,21 +1,13 @@
|
||||
|
||||
enum MessageType {
|
||||
success,
|
||||
processed,
|
||||
error,
|
||||
request
|
||||
}
|
||||
|
||||
extension MessageTypeExtension on MessageType {
|
||||
static String toJson(MessageType value) {
|
||||
switch (value) {
|
||||
case MessageType.success:
|
||||
return 'success';
|
||||
case MessageType.error:
|
||||
return 'error';
|
||||
case MessageType.request:
|
||||
return 'request';
|
||||
}
|
||||
}
|
||||
static String toJson(MessageType value) => value.name;
|
||||
|
||||
static MessageType fromJson(String json) {
|
||||
switch (json) {
|
||||
@@ -25,6 +17,8 @@ extension MessageTypeExtension on MessageType {
|
||||
return MessageType.error;
|
||||
case 'request':
|
||||
return MessageType.request;
|
||||
case 'processed':
|
||||
return MessageType.processed;
|
||||
default:
|
||||
throw ArgumentError('Unknown HTTPMType string: $json');
|
||||
}
|
||||
|
||||
16
frontend/pshared/lib/api/responses/wallet_balance.dart
Normal file
16
frontend/pshared/lib/api/responses/wallet_balance.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/wallet/balance.dart';
|
||||
|
||||
part 'wallet_balance.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class WalletBalanceResponse {
|
||||
final WalletBalanceDTO balance;
|
||||
|
||||
const WalletBalanceResponse({required this.balance});
|
||||
|
||||
factory WalletBalanceResponse.fromJson(Map<String, dynamic> json) => _$WalletBalanceResponseFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$WalletBalanceResponseToJson(this);
|
||||
}
|
||||
16
frontend/pshared/lib/api/responses/wallets.dart
Normal file
16
frontend/pshared/lib/api/responses/wallets.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/wallet/wallet.dart';
|
||||
|
||||
part 'wallets.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class WalletsResponse {
|
||||
final List<WalletDTO> wallets;
|
||||
|
||||
const WalletsResponse({required this.wallets});
|
||||
|
||||
factory WalletsResponse.fromJson(Map<String, dynamic> json) => _$WalletsResponseFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$WalletsResponseToJson(this);
|
||||
}
|
||||
20
frontend/pshared/lib/data/dto/wallet/asset.dart
Normal file
20
frontend/pshared/lib/data/dto/wallet/asset.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'asset.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class WalletAssetDTO {
|
||||
final String chain;
|
||||
final String tokenSymbol;
|
||||
final String contractAddress;
|
||||
|
||||
const WalletAssetDTO({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
required this.contractAddress,
|
||||
});
|
||||
|
||||
factory WalletAssetDTO.fromJson(Map<String, dynamic> json) => _$WalletAssetDTOFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$WalletAssetDTOToJson(this);
|
||||
}
|
||||
24
frontend/pshared/lib/data/dto/wallet/balance.dart
Normal file
24
frontend/pshared/lib/data/dto/wallet/balance.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/wallet/money.dart';
|
||||
|
||||
part 'balance.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class WalletBalanceDTO {
|
||||
final MoneyDTO? available;
|
||||
final MoneyDTO? pendingInbound;
|
||||
final MoneyDTO? pendingOutbound;
|
||||
final String? calculatedAt;
|
||||
|
||||
const WalletBalanceDTO({
|
||||
required this.available,
|
||||
required this.pendingInbound,
|
||||
required this.pendingOutbound,
|
||||
required this.calculatedAt,
|
||||
});
|
||||
|
||||
factory WalletBalanceDTO.fromJson(Map<String, dynamic> json) => _$WalletBalanceDTOFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$WalletBalanceDTOToJson(this);
|
||||
}
|
||||
18
frontend/pshared/lib/data/dto/wallet/money.dart
Normal file
18
frontend/pshared/lib/data/dto/wallet/money.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'money.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class MoneyDTO {
|
||||
final String amount;
|
||||
final String currency;
|
||||
|
||||
const MoneyDTO({
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
factory MoneyDTO.fromJson(Map<String, dynamic> json) => _$MoneyDTOFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$MoneyDTOToJson(this);
|
||||
}
|
||||
34
frontend/pshared/lib/data/dto/wallet/wallet.dart
Normal file
34
frontend/pshared/lib/data/dto/wallet/wallet.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/data/dto/wallet/asset.dart';
|
||||
|
||||
part 'wallet.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable()
|
||||
class WalletDTO {
|
||||
final String walletRef;
|
||||
final String organizationRef;
|
||||
final String ownerRef;
|
||||
final WalletAssetDTO asset;
|
||||
final String depositAddress;
|
||||
final String status;
|
||||
final Map<String, String>? metadata;
|
||||
final String? createdAt;
|
||||
final String? updatedAt;
|
||||
|
||||
const WalletDTO({
|
||||
required this.walletRef,
|
||||
required this.organizationRef,
|
||||
required this.ownerRef,
|
||||
required this.asset,
|
||||
required this.depositAddress,
|
||||
required this.status,
|
||||
this.metadata,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory WalletDTO.fromJson(Map<String, dynamic> json) => _$WalletDTOFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$WalletDTOToJson(this);
|
||||
}
|
||||
16
frontend/pshared/lib/data/mapper/session_identifier.dart
Normal file
16
frontend/pshared/lib/data/mapper/session_identifier.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:pshared/api/requests/tokens/session_identifier.dart';
|
||||
import 'package:pshared/models/session_identifier.dart';
|
||||
|
||||
extension SessionIdentifierMapper on SessionIdentifier {
|
||||
SessionIdentifierDto toDTO() => SessionIdentifierDto(
|
||||
clientId: clientId,
|
||||
deviceId: deviceId,
|
||||
);
|
||||
}
|
||||
|
||||
extension SessionIdentifierDtoMapper on SessionIdentifierDto {
|
||||
SessionIdentifier toDomain() => SessionIdentifier(
|
||||
clientId: clientId,
|
||||
deviceId: deviceId,
|
||||
);
|
||||
}
|
||||
15
frontend/pshared/lib/data/mapper/wallet/balance.dart
Normal file
15
frontend/pshared/lib/data/mapper/wallet/balance.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:pshared/data/dto/wallet/balance.dart';
|
||||
import 'package:pshared/data/mapper/wallet/money.dart';
|
||||
import 'package:pshared/models/wallet/balance.dart';
|
||||
|
||||
|
||||
extension WalletBalanceDTOMapper on WalletBalanceDTO {
|
||||
WalletBalance toDomain() => WalletBalance(
|
||||
available: available?.toDomain(),
|
||||
pendingInbound: pendingInbound?.toDomain(),
|
||||
pendingOutbound: pendingOutbound?.toDomain(),
|
||||
calculatedAt: (calculatedAt == null || calculatedAt!.isEmpty)
|
||||
? null
|
||||
: DateTime.tryParse(calculatedAt!),
|
||||
);
|
||||
}
|
||||
10
frontend/pshared/lib/data/mapper/wallet/money.dart
Normal file
10
frontend/pshared/lib/data/mapper/wallet/money.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:pshared/data/dto/wallet/money.dart';
|
||||
import 'package:pshared/models/wallet/money.dart';
|
||||
|
||||
|
||||
extension MoneyDTOMapper on MoneyDTO {
|
||||
WalletMoney toDomain() => WalletMoney(
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
);
|
||||
}
|
||||
14
frontend/pshared/lib/data/mapper/wallet/response.dart
Normal file
14
frontend/pshared/lib/data/mapper/wallet/response.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:pshared/api/responses/wallet_balance.dart';
|
||||
import 'package:pshared/api/responses/wallets.dart';
|
||||
import 'package:pshared/data/mapper/wallet/balance.dart';
|
||||
import 'package:pshared/data/mapper/wallet/wallet.dart';
|
||||
import 'package:pshared/models/wallet/balance.dart';
|
||||
import 'package:pshared/models/wallet/wallet.dart';
|
||||
|
||||
extension WalletsResponseMapper on WalletsResponse {
|
||||
List<WalletModel> toDomain() => wallets.map((w) => w.toDomain()).toList();
|
||||
}
|
||||
|
||||
extension WalletBalanceResponseMapper on WalletBalanceResponse {
|
||||
WalletBalance toDomain() => balance.toDomain();
|
||||
}
|
||||
26
frontend/pshared/lib/data/mapper/wallet/wallet.dart
Normal file
26
frontend/pshared/lib/data/mapper/wallet/wallet.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:pshared/data/dto/wallet/balance.dart';
|
||||
import 'package:pshared/data/dto/wallet/wallet.dart';
|
||||
import 'package:pshared/data/mapper/wallet/balance.dart';
|
||||
import 'package:pshared/data/mapper/wallet/money.dart';
|
||||
import 'package:pshared/models/wallet/wallet.dart';
|
||||
|
||||
|
||||
extension WalletDTOMapper on WalletDTO {
|
||||
WalletModel toDomain({WalletBalanceDTO? balance}) => WalletModel(
|
||||
walletRef: walletRef,
|
||||
organizationRef: organizationRef,
|
||||
ownerRef: ownerRef,
|
||||
asset: WalletAsset(
|
||||
chain: asset.chain,
|
||||
tokenSymbol: asset.tokenSymbol,
|
||||
contractAddress: asset.contractAddress,
|
||||
),
|
||||
depositAddress: depositAddress,
|
||||
status: status,
|
||||
metadata: metadata,
|
||||
createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!),
|
||||
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!),
|
||||
balance: balance?.toDomain(),
|
||||
availableMoney: balance?.available?.toDomain(),
|
||||
);
|
||||
}
|
||||
11
frontend/pshared/lib/models/confirmation_target.dart
Normal file
11
frontend/pshared/lib/models/confirmation_target.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
|
||||
/// Targets for confirmation codes.
|
||||
@JsonEnum(alwaysCreate: true)
|
||||
enum ConfirmationTarget {
|
||||
@JsonValue('login')
|
||||
login,
|
||||
@JsonValue('payout')
|
||||
payout,
|
||||
}
|
||||
@@ -22,10 +22,6 @@ enum ResourceType {
|
||||
@JsonValue('clients')
|
||||
clients,
|
||||
|
||||
/// Represents comments on tasks or other resources
|
||||
@JsonValue('comments')
|
||||
comments,
|
||||
|
||||
/// Represents invitations sent to users
|
||||
@JsonValue('invitations')
|
||||
invitations,
|
||||
@@ -46,6 +42,9 @@ enum ResourceType {
|
||||
@JsonValue('organizations')
|
||||
organizations,
|
||||
|
||||
@JsonValue('chain_wallets')
|
||||
chainWallets,
|
||||
|
||||
/// Represents permissions service
|
||||
@JsonValue('permissions')
|
||||
permissions,
|
||||
@@ -54,25 +53,6 @@ enum ResourceType {
|
||||
@JsonValue('policies')
|
||||
policies,
|
||||
|
||||
/// Represents task or project priorities
|
||||
@JsonValue('priorities')
|
||||
priorities,
|
||||
|
||||
/// Represents priority groups
|
||||
@JsonValue('priority_groups')
|
||||
priorityGroups,
|
||||
|
||||
/// Represents projects managed in the system
|
||||
@JsonValue('projects')
|
||||
projects,
|
||||
|
||||
@JsonValue('properties')
|
||||
properties,
|
||||
|
||||
/// Represents reactions
|
||||
@JsonValue('reactions')
|
||||
reactions,
|
||||
|
||||
/// Represents refresh tokens for authentication
|
||||
@JsonValue('refresh_tokens')
|
||||
refreshTokens,
|
||||
@@ -88,20 +68,4 @@ enum ResourceType {
|
||||
/// Represents steps in workflows or processes
|
||||
@JsonValue('steps')
|
||||
steps,
|
||||
|
||||
/// Represents tasks managed in the system
|
||||
@JsonValue('tasks')
|
||||
tasks,
|
||||
|
||||
/// Represents teams managed in the system
|
||||
@JsonValue('teams')
|
||||
teams,
|
||||
|
||||
/// Represents workflows for tasks or projects
|
||||
@JsonValue('workflows')
|
||||
workflows,
|
||||
|
||||
/// Represents workspaces containing projects and teams
|
||||
@JsonValue('workspaces')
|
||||
workspaces;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'session_identifier.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class SessionIdentifier {
|
||||
final String clientId;
|
||||
final String deviceId;
|
||||
@@ -11,8 +6,4 @@ class SessionIdentifier {
|
||||
required this.clientId,
|
||||
required this.deviceId,
|
||||
});
|
||||
|
||||
factory SessionIdentifier.fromJson(Map<String, dynamic> json) => _$SessionIdentifierFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SessionIdentifierToJson(this);
|
||||
}
|
||||
|
||||
16
frontend/pshared/lib/models/wallet/balance.dart
Normal file
16
frontend/pshared/lib/models/wallet/balance.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:pshared/models/wallet/money.dart';
|
||||
|
||||
|
||||
class WalletBalance {
|
||||
final WalletMoney? available;
|
||||
final WalletMoney? pendingInbound;
|
||||
final WalletMoney? pendingOutbound;
|
||||
final DateTime? calculatedAt;
|
||||
|
||||
const WalletBalance({
|
||||
required this.available,
|
||||
required this.pendingInbound,
|
||||
required this.pendingOutbound,
|
||||
required this.calculatedAt,
|
||||
});
|
||||
}
|
||||
9
frontend/pshared/lib/models/wallet/money.dart
Normal file
9
frontend/pshared/lib/models/wallet/money.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
class WalletMoney {
|
||||
final String amount;
|
||||
final String currency;
|
||||
|
||||
const WalletMoney({
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
});
|
||||
}
|
||||
62
frontend/pshared/lib/models/wallet/wallet.dart
Normal file
62
frontend/pshared/lib/models/wallet/wallet.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:pshared/models/wallet/balance.dart';
|
||||
import 'package:pshared/models/wallet/money.dart';
|
||||
|
||||
|
||||
class WalletAsset {
|
||||
final String chain;
|
||||
final String tokenSymbol;
|
||||
final String contractAddress;
|
||||
|
||||
const WalletAsset({
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
required this.contractAddress,
|
||||
});
|
||||
}
|
||||
|
||||
class WalletModel {
|
||||
final String walletRef;
|
||||
final String organizationRef;
|
||||
final String ownerRef;
|
||||
final WalletAsset asset;
|
||||
final String depositAddress;
|
||||
final String status;
|
||||
final Map<String, String>? metadata;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? updatedAt;
|
||||
final WalletBalance? balance;
|
||||
final WalletMoney? availableMoney;
|
||||
|
||||
const WalletModel({
|
||||
required this.walletRef,
|
||||
required this.organizationRef,
|
||||
required this.ownerRef,
|
||||
required this.asset,
|
||||
required this.depositAddress,
|
||||
required this.status,
|
||||
this.metadata,
|
||||
this.createdAt,
|
||||
this.updatedAt,
|
||||
this.balance,
|
||||
this.availableMoney,
|
||||
});
|
||||
|
||||
WalletModel copyWith({
|
||||
WalletBalance? balance,
|
||||
WalletMoney? availableMoney,
|
||||
}) {
|
||||
return WalletModel(
|
||||
walletRef: walletRef,
|
||||
organizationRef: organizationRef,
|
||||
ownerRef: ownerRef,
|
||||
asset: asset,
|
||||
depositAddress: depositAddress,
|
||||
status: status,
|
||||
metadata: metadata,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
balance: balance ?? this.balance,
|
||||
availableMoney: availableMoney ?? this.availableMoney,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import 'package:pshared/provider/locale.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/account.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/verification.dart';
|
||||
import 'package:pshared/utils/exception.dart';
|
||||
|
||||
|
||||
@@ -77,7 +78,12 @@ class AccountProvider extends ChangeNotifier {
|
||||
_setResource(Resource(data: outcome.account, isLoading: false));
|
||||
_pickupLocale(outcome.account!.locale);
|
||||
} else {
|
||||
_pendingLogin = outcome.pending;
|
||||
final pending = outcome.pending;
|
||||
if (pending == null) {
|
||||
throw Exception('Pending login data is missing');
|
||||
}
|
||||
await VerificationService.requestLoginCode(pending);
|
||||
_pendingLogin = pending;
|
||||
_setResource(_resource.copyWith(isLoading: false));
|
||||
}
|
||||
return outcome;
|
||||
|
||||
@@ -8,13 +8,10 @@ import 'package:pshared/api/requests/login_data.dart';
|
||||
import 'package:pshared/api/requests/password/change.dart';
|
||||
import 'package:pshared/api/requests/password/forgot.dart';
|
||||
import 'package:pshared/api/requests/password/reset.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/auth/login_outcome.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
import 'package:pshared/service/files.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/http/requests.dart';
|
||||
@@ -29,41 +26,6 @@ class AccountService {
|
||||
return AuthorizationService.login(_objectType, login);
|
||||
}
|
||||
|
||||
static Future<void> resendLoginCode(PendingLogin pending, {String? destination}) async {
|
||||
await getPOSTResponse(
|
||||
_objectType,
|
||||
'confirmations/resend',
|
||||
{
|
||||
'target': 'login',
|
||||
if (destination != null) 'destination': destination,
|
||||
},
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Account> confirmLoginCode({
|
||||
required PendingLogin pending,
|
||||
required String code,
|
||||
String? destination,
|
||||
}) async {
|
||||
final response = await getPOSTResponse(
|
||||
_objectType,
|
||||
'confirmations/verify',
|
||||
{
|
||||
'target': 'login',
|
||||
'code': code,
|
||||
if (destination != null) 'destination': destination,
|
||||
'sessionIdentifier': pending.session.toJson(),
|
||||
},
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
|
||||
final loginResponse = LoginResponse.fromJson(response);
|
||||
await AuthorizationStorage.updateToken(loginResponse.accessToken);
|
||||
await AuthorizationStorage.updateRefreshToken(loginResponse.refreshToken);
|
||||
return loginResponse.account.toDomain();
|
||||
}
|
||||
|
||||
static Future<Account> restore() async {
|
||||
return AuthorizationService.restore();
|
||||
}
|
||||
@@ -75,7 +37,7 @@ class AccountService {
|
||||
|
||||
static Future<void> logout() async {
|
||||
_logger.fine('Logging out');
|
||||
await AuthorizationService.logout();
|
||||
return AuthorizationService.logout();
|
||||
}
|
||||
|
||||
static Future<Account> _getAccount(Future<Map<String, dynamic>> future) async {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
class Services {
|
||||
static const String account = 'accounts';
|
||||
static const String authorization = 'authorization';
|
||||
static const String comments = 'comments';
|
||||
static const String confirmations = 'confirmations';
|
||||
static const String device = 'device';
|
||||
static const String invitations = 'invitations';
|
||||
static const String organization = 'organizations';
|
||||
static const String permission = 'permissions';
|
||||
static const String storage = 'storage';
|
||||
static const String chainWallets = 'chain_wallets';
|
||||
|
||||
static const String amplitude = 'amplitude';
|
||||
static const String clients = 'clients';
|
||||
|
||||
60
frontend/pshared/lib/service/verification.dart
Normal file
60
frontend/pshared/lib/service/verification.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/api/requests/confirmations/login_confirmation.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/data/mapper/session_identifier.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/http/requests.dart';
|
||||
|
||||
|
||||
class VerificationService {
|
||||
static final _logger = Logger('service.verification');
|
||||
static const String _objectType = Services.confirmations;
|
||||
|
||||
static Future<void> requestLoginCode(PendingLogin pending, {String? destination}) async {
|
||||
_logger.fine('Requesting login confirmation code');
|
||||
await getPOSTResponse(
|
||||
_objectType,
|
||||
'',
|
||||
LoginConfirmationRequest(destination: destination).toJson(),
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> resendLoginCode(PendingLogin pending, {String? destination}) async {
|
||||
_logger.fine('Resending login confirmation code');
|
||||
await getPOSTResponse(
|
||||
_objectType,
|
||||
'/resend',
|
||||
LoginConfirmationRequest(destination: destination).toJson(),
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Account> confirmLoginCode({
|
||||
required PendingLogin pending,
|
||||
required String code,
|
||||
String? destination,
|
||||
}) async {
|
||||
_logger.fine('Confirming login code');
|
||||
final response = await getPOSTResponse(
|
||||
_objectType,
|
||||
'/verify',
|
||||
LoginConfirmationVerifyRequest(
|
||||
code: code,
|
||||
destination: destination,
|
||||
sessionIdentifier: pending.session.toDTO(),
|
||||
).toJson(),
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
|
||||
final loginResponse = LoginResponse.fromJson(response);
|
||||
await AuthorizationStorage.updateToken(loginResponse.accessToken);
|
||||
await AuthorizationStorage.updateRefreshToken(loginResponse.refreshToken);
|
||||
return loginResponse.account.toDomain();
|
||||
}
|
||||
}
|
||||
31
frontend/pshared/lib/service/wallet.dart
Normal file
31
frontend/pshared/lib/service/wallet.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:pshared/api/responses/wallet_balance.dart';
|
||||
import 'package:pshared/api/responses/wallets.dart';
|
||||
import 'package:pshared/data/mapper/wallet/response.dart';
|
||||
import 'package:pshared/models/wallet/balance.dart';
|
||||
import 'package:pshared/models/wallet/wallet.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
|
||||
|
||||
class WalletService {
|
||||
static const String _objectType = Services.chainWallets;
|
||||
|
||||
static Future<List<WalletModel>> list(String organizationRef) async {
|
||||
final json = await AuthorizationService.getGETResponse(
|
||||
_objectType,
|
||||
'/$organizationRef',
|
||||
);
|
||||
return WalletsResponse.fromJson(json).toDomain();
|
||||
}
|
||||
|
||||
static Future<WalletBalance> getBalance({
|
||||
required String organizationRef,
|
||||
required String walletRef,
|
||||
}) async {
|
||||
final json = await AuthorizationService.getGETResponse(
|
||||
_objectType,
|
||||
'/$organizationRef/$walletRef/balance',
|
||||
);
|
||||
return WalletBalanceResponse.fromJson(json).toDomain();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
import 'package:pweb/app/router/page_params.dart';
|
||||
import 'package:pweb/pages/2fa/page.dart';
|
||||
import 'package:pweb/pages/signup/page.dart';
|
||||
import 'package:pweb/pages/verification/page.dart';
|
||||
import 'package:pweb/widgets/sidebar/page.dart';
|
||||
import 'package:pweb/pages/login/page.dart';
|
||||
import 'package:pweb/pages/errors/not_found.dart';
|
||||
@@ -29,28 +35,24 @@ GoRouter createRouter() => GoRouter(
|
||||
GoRoute(
|
||||
name: Pages.sfactor.name,
|
||||
path: routerPage(Pages.sfactor),
|
||||
builder: (context, state) {
|
||||
// Определяем откуда пришел пользователь
|
||||
final isFromSignup = state.uri.queryParameters['from'] == 'signup';
|
||||
|
||||
return TwoFactorCodePage(
|
||||
builder: (context, _) => TwoFactorCodePage(
|
||||
onVerificationSuccess: () {
|
||||
if (isFromSignup) {
|
||||
// После регистрации -> на страницу логина
|
||||
context.goNamed(Pages.login.name);
|
||||
} else {
|
||||
// После логина -> на дашборд
|
||||
// trigger organization load
|
||||
context.read<OrganizationsProvider>().load();
|
||||
context.goNamed(Pages.dashboard.name);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
name: Pages.signup.name,
|
||||
path: routerPage(Pages.signup),
|
||||
builder: (_, _) => const SignUpPage(),
|
||||
),
|
||||
GoRoute(
|
||||
name: Pages.verify.name,
|
||||
path: '${routerPage(Pages.verify)}${routerAddParam(PageParams.token)}',
|
||||
builder: (_, state) => AccountVerificationPage(token: state.pathParameters[PageParams.token.name]!),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
26
frontend/pweb/lib/data/mappers/wallet_ui.dart
Normal file
26
frontend/pweb/lib/data/mappers/wallet_ui.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
||||
|
||||
import 'package:pweb/models/currency.dart';
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
|
||||
|
||||
extension WalletUiMapper on domain.WalletModel {
|
||||
Wallet toUi() {
|
||||
final amountStr = availableMoney?.amount ?? balance?.available?.amount ?? '0';
|
||||
final currencyStr = availableMoney?.currency ?? balance?.available?.currency ?? Currency.usd.toString().toUpperCase();
|
||||
final parsedAmount = double.tryParse(amountStr) ?? 0;
|
||||
final currency = Currency.values.firstWhere(
|
||||
(c) => c.name.toUpperCase() == currencyStr.toUpperCase(),
|
||||
orElse: () => Currency.usd,
|
||||
);
|
||||
return Wallet(
|
||||
id: walletRef,
|
||||
walletUserID: walletRef,
|
||||
name: metadata?['name'] ?? walletRef,
|
||||
balance: parsedAmount,
|
||||
currency: currency,
|
||||
isHidden: true,
|
||||
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@
|
||||
"footerCompanyName": "Sibilla Solutions LTD",
|
||||
"footerAddress": "27, Pindarou Street, Alpha Business Centre, Block B 7th Floor, 1060 Nicosia, Cyprus",
|
||||
"footerSupport": "Support",
|
||||
"footerEmail": "Email TBD",
|
||||
"footerEmail": "support@sendico.io",
|
||||
"footerPhoneLabel": "Phone",
|
||||
"footerPhone": "+357 22 000 253",
|
||||
"footerTermsOfService": "Terms of Service",
|
||||
@@ -434,6 +434,14 @@
|
||||
"optional": "optional",
|
||||
"ownerRole": "Organization Owner",
|
||||
"ownerRoleDescription": "This role is granted to the organization’s creator, providing full administrative privileges",
|
||||
"accountVerificationFailed": "Oops! We failed to verify your account. Please, contact support",
|
||||
"verifyAccount": "Account Verification",
|
||||
"verificationFailed": "Verification Failed",
|
||||
"verificationStatusUnknown": "We couldn't determine the status of your verification. Please try again later.",
|
||||
"verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support",
|
||||
"accountVerified": "Account Verified!",
|
||||
"accountVerifiedDescription": "Your account has been successfully verified. You can now log in to access your account.",
|
||||
"retryVerification": "Retry Verification",
|
||||
"save": "Save",
|
||||
"editWallet": "Edit Wallet",
|
||||
"userNamePlaceholder": "User Name",
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"footerCompanyName": "Sibilla Solutions LTD",
|
||||
"footerAddress": "27, Pindarou Street, Alpha Business Centre, Block B 7th Floor, 1060 Nicosia, Cyprus",
|
||||
"footerSupport": "Поддержка",
|
||||
"footerEmail": "Email TBD",
|
||||
"footerEmail": "support@sendico.io",
|
||||
"footerPhoneLabel": "Телефон",
|
||||
"footerPhone": "+357 22 000 253",
|
||||
"footerTermsOfService": "Условия обслуживания",
|
||||
@@ -427,6 +427,14 @@
|
||||
|
||||
"ownerRole": "Владелец организации",
|
||||
"ownerRoleDescription": "Эта роль предоставляется создателю организации и даёт ему полные административные права",
|
||||
"accountVerificationFailed": "Упс! Не удалось подтвердить ваш аккаунт. Пожалуйста, свяжитесь с поддержкой.",
|
||||
"verifyAccount": "Подтвердить аккаунт",
|
||||
"verificationFailed": "Ошибка подтверждения",
|
||||
"verificationStatusUnknown": "Не удалось определить статус подтверждения. Попробуйте позже",
|
||||
"verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки",
|
||||
"accountVerified": "Аккаунт подтвержден!",
|
||||
"accountVerifiedDescription": "Ваш аккаунт успешно подтвержден. Теперь вы можете войти, чтобы получить доступ к своему аккаунту",
|
||||
"retryVerification": "Повторить подтверждение",
|
||||
"save": "Сохранить",
|
||||
"editWallet": "Редактировать кошелек",
|
||||
"userNamePlaceholder": "Имя пользователя",
|
||||
|
||||
@@ -9,15 +9,15 @@ import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/config/constants.dart';
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
import 'package:pshared/provider/permissions.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
|
||||
import 'package:pweb/app/app.dart';
|
||||
import 'package:pweb/app/timeago.dart';
|
||||
import 'package:pweb/providers/carousel.dart';
|
||||
import 'package:pweb/providers/mock_payment.dart';
|
||||
import 'package:pweb/providers/permissions.dart';
|
||||
import 'package:pweb/providers/operatioins.dart';
|
||||
import 'package:pweb/providers/account.dart';
|
||||
import 'package:pweb/providers/page_selector.dart';
|
||||
import 'package:pweb/providers/payment_methods.dart';
|
||||
import 'package:pweb/providers/recipient.dart';
|
||||
@@ -32,8 +32,6 @@ import 'package:pweb/services/payments/upload_history.dart';
|
||||
import 'package:pweb/services/recipient/recipient.dart';
|
||||
import 'package:pweb/services/wallet_transactions.dart';
|
||||
import 'package:pweb/services/wallets.dart';
|
||||
import 'package:pweb/services/accounts.dart';
|
||||
import 'package:pweb/services/permissions.dart';
|
||||
|
||||
|
||||
void _setupLogging() {
|
||||
@@ -58,12 +56,17 @@ void main() async {
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
||||
ChangeNotifierProvider(create: (_) => PermissionsProvider(service: PermissionsService())),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => AccountProvider(
|
||||
accountsService: AccountsService(),
|
||||
permissionsProvider: context.read<PermissionsProvider>(),
|
||||
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
||||
create: (_) => AccountProvider(),
|
||||
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
||||
),
|
||||
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
||||
create: (_) => TwoFactorProvider(),
|
||||
update: (context, accountProvider, provider) => provider!..update(accountProvider),
|
||||
),
|
||||
ChangeNotifierProxyProvider<OrganizationsProvider, PermissionsProvider>(
|
||||
create: (_) => PermissionsProvider(),
|
||||
update: (context, orgnization, provider) => provider!..update(orgnization),
|
||||
),
|
||||
ChangeNotifierProvider(create: (_) => TwoFactorProvider()),
|
||||
ChangeNotifierProvider(create: (_) => OrganizationsProvider()),
|
||||
@@ -75,8 +78,9 @@ void main() async {
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => PaymentMethodsProvider(service: MockPaymentMethodsService())..loadMethods(),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => WalletsProvider(MockWalletsService())..loadData(),
|
||||
ChangeNotifierProxyProvider<OrganizationsProvider, WalletsProvider>(
|
||||
create: (_) => WalletsProvider(ApiWalletsService()),
|
||||
update: (context, organizations, provider) => provider!..update(organizations),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(),
|
||||
|
||||
@@ -8,6 +8,7 @@ class Wallet {
|
||||
final double balance;
|
||||
final Currency currency;
|
||||
final bool isHidden;
|
||||
final DateTime calculatedAt;
|
||||
|
||||
Wallet({
|
||||
required this.id,
|
||||
@@ -15,6 +16,7 @@ class Wallet {
|
||||
required this.name,
|
||||
required this.balance,
|
||||
required this.currency,
|
||||
required this.calculatedAt,
|
||||
this.isHidden = true,
|
||||
});
|
||||
|
||||
@@ -25,14 +27,13 @@ class Wallet {
|
||||
Currency? currency,
|
||||
String? walletUserID,
|
||||
bool? isHidden,
|
||||
}) {
|
||||
return Wallet(
|
||||
}) => Wallet(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
balance: balance ?? this.balance,
|
||||
currency: currency ?? this.currency,
|
||||
walletUserID: walletUserID ?? this.walletUserID,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
calculatedAt: calculatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/pages/2fa/error_message.dart';
|
||||
import 'package:pweb/pages/2fa/input.dart';
|
||||
import 'package:pweb/pages/2fa/prompt.dart';
|
||||
import 'package:pweb/pages/2fa/resend.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pweb/providers/two_factor.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class TwoFactorCodePage extends StatelessWidget {
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/account.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
import 'package:pweb/widgets/error/snackbar.dart';
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/account.dart';
|
||||
import 'package:pweb/providers/permissions.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/permissions.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
import 'package:pweb/widgets/error/snackbar.dart';
|
||||
@@ -29,9 +29,9 @@ class PermissionsLoader extends StatelessWidget {
|
||||
);
|
||||
navigateAndReplace(context, Pages.login);
|
||||
}
|
||||
if (provider.error == null && !provider.hasLoaded && accountProvider.account != null) {
|
||||
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
provider.loadForAccount(accountProvider.account!.id);
|
||||
provider.load();
|
||||
});
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
import 'package:pweb/providers/account.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
import 'package:pweb/pages/login/buttons.dart';
|
||||
@@ -44,6 +44,7 @@ class _LoginFormState extends State<LoginForm> {
|
||||
locale: context.read<LocaleProvider>().locale.languageCode,
|
||||
);
|
||||
if (outcome.isPending) {
|
||||
// TODO: fix context usage
|
||||
navigateAndReplace(context, Pages.sfactor);
|
||||
} else {
|
||||
onLogin();
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/account.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/widgets/vspacer.dart';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user