Compare commits
7 Commits
SEND003
...
cecaebfc5e
| Author | SHA1 | Date | |
|---|---|---|---|
| cecaebfc5e | |||
| e16f11d48a | |||
|
|
0804ad71f7 | ||
| 7a2f921de9 | |||
|
|
999f0684cb | ||
| 602b77ddc7 | |||
|
|
64ad8c8b38 |
@@ -46,9 +46,9 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/protobuf v1.36.10
|
google.golang.org/protobuf v1.36.10
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -187,24 +187,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ market:
|
|||||||
- driver: COINGECKO
|
- driver: COINGECKO
|
||||||
settings:
|
settings:
|
||||||
base_url: "https://api.coingecko.com/api/v3"
|
base_url: "https://api.coingecko.com/api/v3"
|
||||||
|
- driver: CBR
|
||||||
|
settings:
|
||||||
|
base_url: "https://www.cbr.ru"
|
||||||
pairs:
|
pairs:
|
||||||
BINANCE:
|
BINANCE:
|
||||||
- base: "USDT"
|
- base: "USDT"
|
||||||
@@ -26,6 +29,15 @@ market:
|
|||||||
- base: "USDT"
|
- base: "USDT"
|
||||||
quote: "RUB"
|
quote: "RUB"
|
||||||
symbol: "tether:rub"
|
symbol: "tether:rub"
|
||||||
|
CBR:
|
||||||
|
- base: "USD"
|
||||||
|
quote: "RUB"
|
||||||
|
symbol: "USD"
|
||||||
|
provider: "cbr"
|
||||||
|
- base: "EUR"
|
||||||
|
quote: "RUB"
|
||||||
|
symbol: "EUR"
|
||||||
|
provider: "cbr"
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ require (
|
|||||||
github.com/tech/sendico/fx/storage v0.0.0
|
github.com/tech/sendico/fx/storage v0.0.0
|
||||||
github.com/tech/sendico/pkg v0.1.0
|
github.com/tech/sendico/pkg v0.1.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
|
golang.org/x/net v0.47.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,10 +46,9 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
|||||||
@@ -187,24 +187,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/appversion"
|
"github.com/tech/sendico/fx/ingestor/internal/appversion"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/config"
|
"github.com/tech/sendico/fx/ingestor/internal/config"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/ingestor"
|
"github.com/tech/sendico/fx/ingestor/internal/ingestor"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/metrics"
|
"github.com/tech/sendico/fx/ingestor/internal/metrics"
|
||||||
mongostorage "github.com/tech/sendico/fx/storage/mongo"
|
mongostorage "github.com/tech/sendico/fx/storage/mongo"
|
||||||
"github.com/tech/sendico/pkg/api/routers/health"
|
"github.com/tech/sendico/pkg/api/routers/health"
|
||||||
"github.com/tech/sendico/pkg/db"
|
"github.com/tech/sendico/pkg/db"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -26,7 +26,7 @@ type App struct {
|
|||||||
|
|
||||||
func New(logger mlogger.Logger, cfgPath string) (*App, error) {
|
func New(logger mlogger.Logger, cfgPath string) (*App, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, fmerrors.New("app: logger is nil")
|
return nil, merrors.InvalidArgument("app: logger is nil")
|
||||||
}
|
}
|
||||||
path := strings.TrimSpace(cfgPath)
|
path := strings.TrimSpace(cfgPath)
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
"github.com/tech/sendico/pkg/db"
|
"github.com/tech/sendico/pkg/db"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,33 +25,33 @@ type Config struct {
|
|||||||
|
|
||||||
func Load(path string) (*Config, error) {
|
func Load(path string) (*Config, error) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil, fmerrors.New("config: path is empty")
|
return nil, merrors.InvalidArgument("config: path is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("config: failed to read file", err)
|
return nil, merrors.InternalWrap(err, "config: failed to read file")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := &Config{}
|
cfg := &Config{}
|
||||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||||
return nil, fmerrors.Wrap("config: failed to parse yaml", err)
|
return nil, merrors.InternalWrap(err, "config: failed to parse yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Market.Sources) == 0 {
|
if len(cfg.Market.Sources) == 0 {
|
||||||
return nil, fmerrors.New("config: no market sources configured")
|
return nil, merrors.InvalidArgument("config: no market sources configured")
|
||||||
}
|
}
|
||||||
sourceSet := make(map[mmodel.Driver]struct{}, len(cfg.Market.Sources))
|
sourceSet := make(map[mmodel.Driver]struct{}, len(cfg.Market.Sources))
|
||||||
for idx := range cfg.Market.Sources {
|
for idx := range cfg.Market.Sources {
|
||||||
src := &cfg.Market.Sources[idx]
|
src := &cfg.Market.Sources[idx]
|
||||||
if src.Driver.IsEmpty() {
|
if src.Driver.IsEmpty() {
|
||||||
return nil, fmerrors.New("config: market source driver is empty")
|
return nil, merrors.InvalidArgument("config: market source driver is empty")
|
||||||
}
|
}
|
||||||
sourceSet[src.Driver] = struct{}{}
|
sourceSet[src.Driver] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Market.Pairs) == 0 {
|
if len(cfg.Market.Pairs) == 0 {
|
||||||
return nil, fmerrors.New("config: no pairs configured")
|
return nil, merrors.InvalidArgument("config: no pairs configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedPairs := make(map[string][]PairConfig, len(cfg.Market.Pairs))
|
normalizedPairs := make(map[string][]PairConfig, len(cfg.Market.Pairs))
|
||||||
@@ -61,10 +61,10 @@ func Load(path string) (*Config, error) {
|
|||||||
for rawSource, pairList := range cfg.Market.Pairs {
|
for rawSource, pairList := range cfg.Market.Pairs {
|
||||||
driver := mmodel.Driver(rawSource)
|
driver := mmodel.Driver(rawSource)
|
||||||
if driver.IsEmpty() {
|
if driver.IsEmpty() {
|
||||||
return nil, fmerrors.New("config: pair source is empty")
|
return nil, merrors.InvalidArgument("config: pair source is empty")
|
||||||
}
|
}
|
||||||
if _, ok := sourceSet[driver]; !ok {
|
if _, ok := sourceSet[driver]; !ok {
|
||||||
return nil, fmerrors.New("config: pair references unknown source: " + driver.String())
|
return nil, merrors.InvalidArgument("config: pair references unknown source: "+driver.String(), "pairs."+driver.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
processed := make([]PairConfig, len(pairList))
|
processed := make([]PairConfig, len(pairList))
|
||||||
@@ -74,7 +74,7 @@ func Load(path string) (*Config, error) {
|
|||||||
pair.Quote = strings.ToUpper(strings.TrimSpace(pair.Quote))
|
pair.Quote = strings.ToUpper(strings.TrimSpace(pair.Quote))
|
||||||
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
||||||
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
||||||
return nil, fmerrors.New("config: pair entries must define base, quote, and symbol")
|
return nil, merrors.InvalidArgument("config: pair entries must define base, quote, and symbol", "pairs."+driver.String())
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(pair.Provider) == "" {
|
if strings.TrimSpace(pair.Provider) == "" {
|
||||||
pair.Provider = strings.ToLower(driver.String())
|
pair.Provider = strings.ToLower(driver.String())
|
||||||
@@ -93,7 +93,7 @@ func Load(path string) (*Config, error) {
|
|||||||
cfg.pairsBySource = pairsBySource
|
cfg.pairsBySource = pairsBySource
|
||||||
cfg.pairs = flattened
|
cfg.pairs = flattened
|
||||||
if cfg.Database == nil {
|
if cfg.Database == nil {
|
||||||
return nil, fmerrors.New("config: database configuration is required")
|
return nil, merrors.InvalidArgument("config: database configuration is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Metrics != nil && cfg.Metrics.Enabled {
|
if cfg.Metrics != nil && cfg.Metrics.Enabled {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package fmerrors
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
message string
|
|
||||||
cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Error() string {
|
|
||||||
if e == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if e.cause == nil {
|
|
||||||
return e.message
|
|
||||||
}
|
|
||||||
return e.message + ": " + e.cause.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Error) Unwrap() error {
|
|
||||||
if e == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return e.cause
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(message string) error {
|
|
||||||
return &Error{message: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Wrap(message string, cause error) error {
|
|
||||||
return &Error{message: message, cause: cause}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDecimal(value string) error {
|
|
||||||
return &Error{message: "invalid decimal \"" + value + "\""}
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/config"
|
"github.com/tech/sendico/fx/ingestor/internal/config"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/market"
|
"github.com/tech/sendico/fx/ingestor/internal/market"
|
||||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
"github.com/tech/sendico/fx/storage"
|
"github.com/tech/sendico/fx/storage"
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -26,18 +26,18 @@ type Service struct {
|
|||||||
|
|
||||||
func New(logger mlogger.Logger, cfg *config.Config, repo storage.Repository) (*Service, error) {
|
func New(logger mlogger.Logger, cfg *config.Config, repo storage.Repository) (*Service, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, fmerrors.New("ingestor: nil logger")
|
return nil, merrors.InvalidArgument("ingestor: nil logger")
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, fmerrors.New("ingestor: nil config")
|
return nil, merrors.InvalidArgument("ingestor: nil config")
|
||||||
}
|
}
|
||||||
if repo == nil {
|
if repo == nil {
|
||||||
return nil, fmerrors.New("ingestor: nil repository")
|
return nil, merrors.InvalidArgument("ingestor: nil repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
connectors, err := market.BuildConnectors(logger, cfg.Market.Sources)
|
connectors, err := market.BuildConnectors(logger, cfg.Market.Sources)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("build connectors", err)
|
return nil, merrors.InternalWrap(err, "build connectors")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
@@ -110,21 +110,21 @@ func (s *Service) pollOnce(ctx context.Context) error {
|
|||||||
func (s *Service) upsertPair(ctx context.Context, pair config.Pair) error {
|
func (s *Service) upsertPair(ctx context.Context, pair config.Pair) error {
|
||||||
connector, ok := s.connectors[pair.Source]
|
connector, ok := s.connectors[pair.Source]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmerrors.Wrap("connector not configured for source "+pair.Source.String(), nil)
|
return merrors.InvalidArgument("connector not configured for source "+pair.Source.String(), "source")
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker, err := connector.FetchTicker(ctx, pair.Symbol)
|
ticker, err := connector.FetchTicker(ctx, pair.Symbol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmerrors.Wrap("fetch ticker", err)
|
return merrors.InternalWrap(err, "fetch ticker")
|
||||||
}
|
}
|
||||||
|
|
||||||
bid, err := parseDecimal(ticker.BidPrice)
|
bid, err := parseDecimal(ticker.BidPrice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmerrors.Wrap("parse bid price", err)
|
return merrors.InvalidArgumentWrap(err, "parse bid price", "bid")
|
||||||
}
|
}
|
||||||
ask, err := parseDecimal(ticker.AskPrice)
|
ask, err := parseDecimal(ticker.AskPrice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmerrors.Wrap("parse ask price", err)
|
return merrors.InvalidArgumentWrap(err, "parse ask price", "ask")
|
||||||
}
|
}
|
||||||
|
|
||||||
if pair.Invert {
|
if pair.Invert {
|
||||||
@@ -166,7 +166,7 @@ func (s *Service) upsertPair(ctx context.Context, pair config.Pair) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.rates.UpsertSnapshot(ctx, snapshot); err != nil {
|
if err := s.rates.UpsertSnapshot(ctx, snapshot); err != nil {
|
||||||
return fmerrors.Wrap("upsert snapshot", err)
|
return merrors.InternalWrap(err, "upsert snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Debug("Snapshot ingested",
|
s.logger.Debug("Snapshot ingested",
|
||||||
@@ -183,7 +183,7 @@ func (s *Service) upsertPair(ctx context.Context, pair config.Pair) error {
|
|||||||
func parseDecimal(value string) (*big.Rat, error) {
|
func parseDecimal(value string) (*big.Rat, error) {
|
||||||
r := new(big.Rat)
|
r := new(big.Rat)
|
||||||
if _, ok := r.SetString(value); !ok {
|
if _, ok := r.SetString(value); !ok {
|
||||||
return nil, fmerrors.NewDecimal(value)
|
return nil, merrors.InvalidArgument("invalid decimal \""+value+"\"", "value")
|
||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/config"
|
"github.com/tech/sendico/fx/ingestor/internal/config"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
mmarket "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmarket "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
"github.com/tech/sendico/fx/storage"
|
"github.com/tech/sendico/fx/storage"
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ func TestServiceUpsertPairInvertsPrices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServicePollOnceReturnsFirstError(t *testing.T) {
|
func TestServicePollOnceReturnsFirstError(t *testing.T) {
|
||||||
errFetch := fmerrors.New("fetch failed")
|
errFetch := merrors.Internal("fetch failed")
|
||||||
connectorSuccess := &connectorStub{
|
connectorSuccess := &connectorStub{
|
||||||
id: mmarket.DriverBinance,
|
id: mmarket.DriverBinance,
|
||||||
ticker: &mmarket.Ticker{
|
ticker: &mmarket.Ticker{
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/market/common"
|
"github.com/tech/sendico/fx/ingestor/internal/market/common"
|
||||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -60,7 +60,7 @@ func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Conne
|
|||||||
|
|
||||||
parsed, err := url.Parse(baseURL)
|
parsed, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("binance: invalid base url", err)
|
return nil, merrors.InvalidArgumentWrap(err, "binance: invalid base url", "base_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
@@ -89,12 +89,12 @@ func (c *binanceConnector) ID() mmodel.Driver {
|
|||||||
|
|
||||||
func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmodel.Ticker, error) {
|
func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmodel.Ticker, error) {
|
||||||
if strings.TrimSpace(symbol) == "" {
|
if strings.TrimSpace(symbol) == "" {
|
||||||
return nil, fmerrors.New("binance: symbol is empty")
|
return nil, merrors.InvalidArgument("binance: symbol is empty", "symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, err := url.Parse(c.base)
|
endpoint, err := url.Parse(c.base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("binance: parse base url", err)
|
return nil, merrors.InternalWrap(err, "binance: parse base url")
|
||||||
}
|
}
|
||||||
endpoint.Path = "/api/v3/ticker/bookTicker"
|
endpoint.Path = "/api/v3/ticker/bookTicker"
|
||||||
query := endpoint.Query()
|
query := endpoint.Query()
|
||||||
@@ -103,19 +103,19 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
|||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("binance: build request", err)
|
return nil, merrors.InternalWrap(err, "binance: build request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("Binance request failed", zap.String("symbol", symbol), zap.Error(err))
|
c.logger.Warn("Binance request failed", zap.String("symbol", symbol), zap.Error(err))
|
||||||
return nil, fmerrors.Wrap("binance: request failed", err)
|
return nil, merrors.InternalWrap(err, "binance: request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||||
return nil, fmerrors.New("binance: unexpected status " + strconv.Itoa(resp.StatusCode))
|
return nil, merrors.Internal("binance: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload struct {
|
var payload struct {
|
||||||
@@ -126,7 +126,7 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
|||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||||
c.logger.Warn("Binance decode failed", zap.String("symbol", symbol), zap.Error(err))
|
c.logger.Warn("Binance decode failed", zap.String("symbol", symbol), zap.Error(err))
|
||||||
return nil, fmerrors.Wrap("binance: decode response", err)
|
return nil, merrors.InternalWrap(err, "binance: decode response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &mmodel.Ticker{
|
return &mmodel.Ticker{
|
||||||
|
|||||||
537
api/fx/ingestor/internal/market/cbr/connector.go
Normal file
537
api/fx/ingestor/internal/market/cbr/connector.go
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
package cbr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/fx/ingestor/internal/market/common"
|
||||||
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/net/html/charset"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cbrConnector struct {
|
||||||
|
id mmodel.Driver
|
||||||
|
provider string
|
||||||
|
client *http.Client
|
||||||
|
base string
|
||||||
|
dailyPath string
|
||||||
|
directoryPath string
|
||||||
|
dynamicPath string
|
||||||
|
logger mlogger.Logger
|
||||||
|
|
||||||
|
byISO map[string]valuteInfo
|
||||||
|
byID map[string]valuteInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCBRBaseURL = "https://www.cbr.ru"
|
||||||
|
const (
|
||||||
|
defaultDirectoryPath = "/scripts/XML_valFull.asp"
|
||||||
|
defaultDailyPath = "/scripts/XML_daily.asp"
|
||||||
|
defaultDynamicPath = "/scripts/XML_dynamic.asp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDialTimeoutSeconds = 5 * time.Second
|
||||||
|
defaultDialKeepAliveSeconds = 30 * time.Second
|
||||||
|
defaultTLSHandshakeTimeoutSeconds = 5 * time.Second
|
||||||
|
defaultResponseHeaderTimeoutSeconds = 10 * time.Second
|
||||||
|
defaultRequestTimeoutSeconds = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) {
|
||||||
|
baseURL := defaultCBRBaseURL
|
||||||
|
provider := strings.ToLower(mmodel.DriverCBR.String())
|
||||||
|
dialTimeout := defaultDialTimeoutSeconds
|
||||||
|
dialKeepAlive := defaultDialKeepAliveSeconds
|
||||||
|
tlsHandshakeTimeout := defaultTLSHandshakeTimeoutSeconds
|
||||||
|
responseHeaderTimeout := defaultResponseHeaderTimeoutSeconds
|
||||||
|
requestTimeout := defaultRequestTimeoutSeconds
|
||||||
|
directoryPath := defaultDirectoryPath
|
||||||
|
dailyPath := defaultDailyPath
|
||||||
|
dynamicPath := defaultDynamicPath
|
||||||
|
|
||||||
|
if settings != nil {
|
||||||
|
if value, ok := settings["base_url"].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
baseURL = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
if value, ok := settings["provider"].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
provider = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
if value, ok := settings["directory_path"].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
directoryPath = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
if value, ok := settings["daily_path"].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
dailyPath = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
if value, ok := settings["dynamic_path"].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
dynamicPath = strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
dialTimeout = common.DurationSetting(settings, "dial_timeout_seconds", dialTimeout)
|
||||||
|
dialKeepAlive = common.DurationSetting(settings, "dial_keep_alive_seconds", dialKeepAlive)
|
||||||
|
tlsHandshakeTimeout = common.DurationSetting(settings, "tls_handshake_timeout_seconds", tlsHandshakeTimeout)
|
||||||
|
responseHeaderTimeout = common.DurationSetting(settings, "response_header_timeout_seconds", responseHeaderTimeout)
|
||||||
|
requestTimeout = common.DurationSetting(settings, "request_timeout_seconds", requestTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := url.Parse(baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, merrors.InvalidArgumentWrap(err, "cbr: invalid base url", "base_url")
|
||||||
|
}
|
||||||
|
|
||||||
|
var transport http.RoundTripper = &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{Timeout: dialTimeout, KeepAlive: dialKeepAlive}).DialContext,
|
||||||
|
TLSHandshakeTimeout: tlsHandshakeTimeout,
|
||||||
|
ResponseHeaderTimeout: responseHeaderTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
if customTransport, ok := settings["http_round_tripper"].(http.RoundTripper); ok && customTransport != nil {
|
||||||
|
transport = customTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
connector := &cbrConnector{
|
||||||
|
id: mmodel.DriverCBR,
|
||||||
|
provider: provider,
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: requestTimeout,
|
||||||
|
Transport: transport,
|
||||||
|
},
|
||||||
|
base: strings.TrimRight(parsed.String(), "/"),
|
||||||
|
dailyPath: dailyPath,
|
||||||
|
directoryPath: directoryPath,
|
||||||
|
dynamicPath: dynamicPath,
|
||||||
|
logger: logger.Named("cbr"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := connector.refreshDirectory(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return connector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) ID() mmodel.Driver {
|
||||||
|
return c.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) FetchTicker(ctx context.Context, symbol string) (*mmodel.Ticker, error) {
|
||||||
|
isoCode, asOfDate, err := parseSymbol(symbol)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
valute, ok := c.byISO[isoCode]
|
||||||
|
if !ok {
|
||||||
|
return nil, merrors.InvalidArgument("cbr: unknown currency "+isoCode, "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
var price string
|
||||||
|
if asOfDate != nil {
|
||||||
|
price, err = c.fetchHistoricalRate(ctx, valute, *asOfDate)
|
||||||
|
} else {
|
||||||
|
price, err = c.fetchDailyRate(ctx, valute)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
return &mmodel.Ticker{
|
||||||
|
Symbol: formatSymbol(isoCode, asOfDate),
|
||||||
|
BidPrice: price,
|
||||||
|
AskPrice: price,
|
||||||
|
Provider: c.provider,
|
||||||
|
Timestamp: now,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) refreshDirectory() error {
|
||||||
|
endpoint, err := c.buildURL(c.directoryPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return merrors.InternalWrap(err, "cbr: build directory request")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("CBR directory request failed", zap.Error(err))
|
||||||
|
return merrors.InternalWrap(err, "cbr: directory request failed")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
c.logger.Warn("CBR directory returned non-OK status", zap.Int("status", resp.StatusCode))
|
||||||
|
return merrors.Internal("cbr: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := xml.NewDecoder(resp.Body)
|
||||||
|
decoder.CharsetReader = charset.NewReaderLabel
|
||||||
|
|
||||||
|
var directory valuteDirectory
|
||||||
|
if err := decoder.Decode(&directory); err != nil {
|
||||||
|
c.logger.Warn("CBR directory decode failed", zap.Error(err))
|
||||||
|
return merrors.InternalWrap(err, "cbr: decode directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping, err := buildValuteMapping(directory.Items)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.byISO = mapping.byISO
|
||||||
|
c.byID = mapping.byID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) fetchDailyRate(ctx context.Context, valute valuteInfo) (string, error) {
|
||||||
|
endpoint, err := c.buildURL(c.dailyPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: build daily request")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("CBR daily request failed", zap.String("currency", valute.ISOCharCode), zap.Error(err))
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: daily request failed")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
c.logger.Warn("CBR daily returned non-OK status", zap.Int("status", resp.StatusCode))
|
||||||
|
return "", merrors.Internal("cbr: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := xml.NewDecoder(resp.Body)
|
||||||
|
decoder.CharsetReader = charset.NewReaderLabel
|
||||||
|
|
||||||
|
var payload dailyRates
|
||||||
|
if err := decoder.Decode(&payload); err != nil {
|
||||||
|
c.logger.Warn("CBR daily decode failed", zap.Error(err))
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: decode daily response")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := payload.find(valute.ID)
|
||||||
|
if entry == nil {
|
||||||
|
return "", merrors.NoData("cbr: currency not found in daily rates: " + valute.ISOCharCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateDailyEntry(valute, entry); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return computePrice(entry.Value, entry.Nominal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) fetchHistoricalRate(ctx context.Context, valute valuteInfo, date time.Time) (string, error) {
|
||||||
|
query := map[string]string{
|
||||||
|
"date_req1": date.Format("02/01/2006"),
|
||||||
|
"date_req2": date.Format("02/01/2006"),
|
||||||
|
"VAL_NM_RQ": valute.ID,
|
||||||
|
}
|
||||||
|
endpoint, err := c.buildURL(c.dynamicPath, query)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: build historical request")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn("CBR historical request failed", zap.String("currency", valute.ISOCharCode), zap.Error(err))
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: historical request failed")
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
c.logger.Warn("CBR historical returned non-OK status", zap.Int("status", resp.StatusCode))
|
||||||
|
return "", merrors.Internal("cbr: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := xml.NewDecoder(resp.Body)
|
||||||
|
decoder.CharsetReader = charset.NewReaderLabel
|
||||||
|
|
||||||
|
var payload dynamicRates
|
||||||
|
if err := decoder.Decode(&payload); err != nil {
|
||||||
|
c.logger.Warn("CBR historical decode failed", zap.Error(err))
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: decode historical response")
|
||||||
|
}
|
||||||
|
|
||||||
|
record := payload.find(valute.ID, date)
|
||||||
|
if record == nil {
|
||||||
|
return "", merrors.NoData("cbr: historical rate not found for " + valute.ISOCharCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.Nominal != "" {
|
||||||
|
nominal, err := parseNominal(record.Nominal)
|
||||||
|
if err != nil {
|
||||||
|
return "", merrors.InvalidDataType(err.Error())
|
||||||
|
}
|
||||||
|
if nominal != valute.Nominal {
|
||||||
|
return "", merrors.Internal("cbr: historical nominal mismatch for " + valute.ISOCharCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return computePrice(record.Value, strconv.FormatInt(valute.Nominal, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cbrConnector) buildURL(path string, query map[string]string) (string, error) {
|
||||||
|
base, err := url.Parse(c.base)
|
||||||
|
if err != nil {
|
||||||
|
return "", merrors.InternalWrap(err, "cbr: parse base url")
|
||||||
|
}
|
||||||
|
base.Path = strings.TrimRight(base.Path, "/") + path
|
||||||
|
q := base.Query()
|
||||||
|
for key, value := range query {
|
||||||
|
q.Set(key, value)
|
||||||
|
}
|
||||||
|
base.RawQuery = q.Encode()
|
||||||
|
return base.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type valuteDirectory struct {
|
||||||
|
Items []valuteItem `xml:"Item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type valuteItem struct {
|
||||||
|
ID string `xml:"ID,attr"`
|
||||||
|
ISOChar string `xml:"ISO_Char_Code"`
|
||||||
|
ISONum string `xml:"ISO_Num_Code"`
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
EngName string `xml:"EngName"`
|
||||||
|
NominalStr string `xml:"Nominal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type valuteInfo struct {
|
||||||
|
ID string
|
||||||
|
ISOCharCode string
|
||||||
|
ISONumCode string
|
||||||
|
Name string
|
||||||
|
EngName string
|
||||||
|
Nominal int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type valuteMapping struct {
|
||||||
|
byISO map[string]valuteInfo
|
||||||
|
byID map[string]valuteInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildValuteMapping(items []valuteItem) (*valuteMapping, error) {
|
||||||
|
byISO := make(map[string]valuteInfo, len(items))
|
||||||
|
byID := make(map[string]valuteInfo, len(items))
|
||||||
|
byNum := make(map[string]string, len(items))
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
id := strings.TrimSpace(item.ID)
|
||||||
|
isoChar := strings.ToUpper(strings.TrimSpace(item.ISOChar))
|
||||||
|
isoNum := strings.TrimSpace(item.ISONum)
|
||||||
|
name := strings.TrimSpace(item.Name)
|
||||||
|
engName := strings.TrimSpace(item.EngName)
|
||||||
|
nominal, err := parseNominal(item.NominalStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: parse directory nominal: " + err.Error())
|
||||||
|
}
|
||||||
|
if id == "" || isoChar == "" {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: directory contains entry with empty id or iso code")
|
||||||
|
}
|
||||||
|
|
||||||
|
info := valuteInfo{
|
||||||
|
ID: id,
|
||||||
|
ISOCharCode: isoChar,
|
||||||
|
ISONumCode: isoNum,
|
||||||
|
Name: name,
|
||||||
|
EngName: engName,
|
||||||
|
Nominal: nominal,
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing, ok := byISO[isoChar]; ok && existing.ID != id {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: duplicate ISO code " + isoChar)
|
||||||
|
}
|
||||||
|
if existing, ok := byID[id]; ok && existing.ISOCharCode != isoChar {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: duplicate valute id " + id)
|
||||||
|
}
|
||||||
|
if isoNum != "" {
|
||||||
|
if existingID, ok := byNum[isoNum]; ok && existingID != id {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: duplicate ISO numeric code " + isoNum)
|
||||||
|
}
|
||||||
|
byNum[isoNum] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
byISO[isoChar] = info
|
||||||
|
byID[id] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(byISO) == 0 {
|
||||||
|
return nil, merrors.InvalidDataType("cbr: empty directory received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &valuteMapping{
|
||||||
|
byISO: byISO,
|
||||||
|
byID: byID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dailyRates struct {
|
||||||
|
Valutes []dailyValute `xml:"Valute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dailyValute struct {
|
||||||
|
ID string `xml:"ID,attr"`
|
||||||
|
NumCode string `xml:"NumCode"`
|
||||||
|
CharCode string `xml:"CharCode"`
|
||||||
|
Nominal string `xml:"Nominal"`
|
||||||
|
Name string `xml:"Name"`
|
||||||
|
Value string `xml:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dailyRates) find(id string) *dailyValute {
|
||||||
|
if d == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for idx := range d.Valutes {
|
||||||
|
if strings.EqualFold(strings.TrimSpace(d.Valutes[idx].ID), id) {
|
||||||
|
return &d.Valutes[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicRates struct {
|
||||||
|
Records []dynamicRecord `xml:"Record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicRecord struct {
|
||||||
|
ID string `xml:"Id,attr"`
|
||||||
|
DateRaw string `xml:"Date,attr"`
|
||||||
|
Nominal string `xml:"Nominal"`
|
||||||
|
Value string `xml:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dynamicRates) find(id string, date time.Time) *dynamicRecord {
|
||||||
|
if d == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := date.Format("02.01.2006")
|
||||||
|
for idx := range d.Records {
|
||||||
|
rec := &d.Records[idx]
|
||||||
|
if !strings.EqualFold(strings.TrimSpace(rec.ID), id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(rec.DateRaw) == target {
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDailyEntry(expected valuteInfo, entry *dailyValute) error {
|
||||||
|
if entry == nil {
|
||||||
|
return merrors.NoData("cbr: missing daily entry")
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(strings.TrimSpace(entry.CharCode), expected.ISOCharCode) {
|
||||||
|
return merrors.Internal("cbr: char code mismatch for " + expected.ISOCharCode)
|
||||||
|
}
|
||||||
|
if expected.ISONumCode != "" && strings.TrimSpace(entry.NumCode) != expected.ISONumCode {
|
||||||
|
return merrors.Internal("cbr: iso numeric mismatch for " + expected.ISOCharCode)
|
||||||
|
}
|
||||||
|
if expected.Name != "" && strings.TrimSpace(entry.Name) != expected.Name {
|
||||||
|
return merrors.Internal("cbr: currency name mismatch for " + expected.ISOCharCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
nominal, err := parseNominal(entry.Nominal)
|
||||||
|
if err != nil {
|
||||||
|
return merrors.InvalidDataType("cbr: parse daily nominal: " + err.Error())
|
||||||
|
}
|
||||||
|
if nominal != expected.Nominal {
|
||||||
|
return merrors.Internal("cbr: nominal mismatch for " + expected.ISOCharCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSymbol(symbol string) (string, *time.Time, error) {
|
||||||
|
trimmed := strings.TrimSpace(symbol)
|
||||||
|
if trimmed == "" {
|
||||||
|
return "", nil, merrors.InvalidArgument("cbr: symbol is empty", "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(trimmed, "@")
|
||||||
|
if len(parts) > 2 {
|
||||||
|
return "", nil, merrors.InvalidArgument("cbr: invalid symbol format", "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
iso := strings.ToUpper(strings.TrimSpace(parts[0]))
|
||||||
|
if len(iso) != 3 {
|
||||||
|
return "", nil, merrors.InvalidArgument("cbr: symbol must be ISO currency code", "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return iso, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
datePart := strings.TrimSpace(parts[1])
|
||||||
|
if datePart == "" {
|
||||||
|
return "", nil, merrors.InvalidArgument("cbr: date component is empty", "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := time.Parse("2006-01-02", datePart)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, merrors.InvalidArgumentWrap(err, "cbr: invalid date component", "symbol")
|
||||||
|
}
|
||||||
|
|
||||||
|
return iso, &parsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNominal(value string) (int64, error) {
|
||||||
|
nominal, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
||||||
|
if err != nil || nominal <= 0 {
|
||||||
|
return 0, merrors.InvalidDataType("cbr: invalid nominal \"" + value + "\"")
|
||||||
|
}
|
||||||
|
return nominal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func computePrice(value string, nominalStr string) (string, error) {
|
||||||
|
raw := strings.ReplaceAll(strings.TrimSpace(value), " ", "")
|
||||||
|
raw = strings.ReplaceAll(raw, ",", ".")
|
||||||
|
|
||||||
|
r := new(big.Rat)
|
||||||
|
if _, ok := r.SetString(raw); !ok {
|
||||||
|
return "", merrors.InvalidDataType("invalid decimal \"" + value + "\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
nominal, err := parseNominal(nominalStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
den := big.NewRat(nominal, 1)
|
||||||
|
price := new(big.Rat).Quo(r, den)
|
||||||
|
return price.FloatString(8), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSymbol(iso string, asOf *time.Time) string {
|
||||||
|
if asOf == nil {
|
||||||
|
return iso
|
||||||
|
}
|
||||||
|
return iso + "@" + asOf.Format("2006-01-02")
|
||||||
|
}
|
||||||
226
api/fx/ingestor/internal/market/cbr/connector_test.go
Normal file
226
api/fx/ingestor/internal/market/cbr/connector_test.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package cbr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFetchTickerDaily(t *testing.T) {
|
||||||
|
transport := &stubRoundTripper{
|
||||||
|
responses: map[string]stubResponse{
|
||||||
|
"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
|
||||||
|
"/scripts/XML_daily.asp": {body: dailyRatesXML},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnector(zap.NewNop(), map[string]any{
|
||||||
|
"base_url": "http://cbr.test",
|
||||||
|
"http_round_tripper": transport,
|
||||||
|
"request_timeout_seconds": 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConnector returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker, err := conn.FetchTicker(context.Background(), "USD")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FetchTicker returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ticker.Provider != "cbr" {
|
||||||
|
t.Fatalf("unexpected provider: %s", ticker.Provider)
|
||||||
|
}
|
||||||
|
if ticker.BidPrice != "95.12340000" || ticker.AskPrice != "95.12340000" {
|
||||||
|
t.Fatalf("unexpected bid/ask: %s / %s", ticker.BidPrice, ticker.AskPrice)
|
||||||
|
}
|
||||||
|
if ticker.Symbol != "USD" {
|
||||||
|
t.Fatalf("unexpected symbol: %s", ticker.Symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchTickerValidatesDailyEntry(t *testing.T) {
|
||||||
|
transport := &stubRoundTripper{
|
||||||
|
responses: map[string]stubResponse{
|
||||||
|
"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
|
||||||
|
"/scripts/XML_daily.asp": {body: strings.ReplaceAll(dailyRatesXML, "<CharCode>USD</CharCode>", "<CharCode>XXX</CharCode>")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnector(zap.NewNop(), map[string]any{
|
||||||
|
"base_url": "http://cbr.test",
|
||||||
|
"http_round_tripper": transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConnector returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.FetchTicker(context.Background(), "USD"); err == nil {
|
||||||
|
t.Fatalf("FetchTicker expected to fail due to mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchTickerHistorical(t *testing.T) {
|
||||||
|
transport := &stubRoundTripper{
|
||||||
|
responses: map[string]stubResponse{
|
||||||
|
"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
|
||||||
|
"/scripts/XML_dynamic.asp": {
|
||||||
|
body: dynamicRatesXML,
|
||||||
|
check: func(r *http.Request) error {
|
||||||
|
if got := r.URL.Query().Get("VAL_NM_RQ"); got != "R01235" {
|
||||||
|
return fmt.Errorf("unexpected valute id: %s", got)
|
||||||
|
}
|
||||||
|
if got := r.URL.Query().Get("date_req1"); got != "05/01/2023" {
|
||||||
|
return fmt.Errorf("unexpected date_req1: %s", got)
|
||||||
|
}
|
||||||
|
if got := r.URL.Query().Get("date_req2"); got != "05/01/2023" {
|
||||||
|
return fmt.Errorf("unexpected date_req2: %s", got)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnector(zap.NewNop(), map[string]any{
|
||||||
|
"base_url": "http://cbr.test",
|
||||||
|
"http_round_tripper": transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConnector returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker, err := conn.FetchTicker(context.Background(), "USD@2023-01-05")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FetchTicker returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ticker.BidPrice != "70.10000000" || ticker.AskPrice != "70.10000000" {
|
||||||
|
t.Fatalf("unexpected bid/ask: %s / %s", ticker.BidPrice, ticker.AskPrice)
|
||||||
|
}
|
||||||
|
if ticker.Symbol != "USD@2023-01-05" {
|
||||||
|
t.Fatalf("unexpected symbol: %s", ticker.Symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchTickerUnknownCurrency(t *testing.T) {
|
||||||
|
transport := &stubRoundTripper{
|
||||||
|
responses: map[string]stubResponse{
|
||||||
|
"/scripts/XML_valFull.asp": {body: valuteDirectoryXML},
|
||||||
|
"/scripts/XML_daily.asp": {body: dailyRatesXML},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnector(zap.NewNop(), map[string]any{
|
||||||
|
"base_url": "http://cbr.test",
|
||||||
|
"http_round_tripper": transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConnector returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.FetchTicker(context.Background(), "ZZZ")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("FetchTicker expected to fail for unknown currency")
|
||||||
|
}
|
||||||
|
if !errors.Is(err, merrors.ErrInvalidArg) {
|
||||||
|
t.Fatalf("expected invalid argument error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchTickerRespectsCustomPaths(t *testing.T) {
|
||||||
|
transport := &stubRoundTripper{
|
||||||
|
responses: map[string]stubResponse{
|
||||||
|
"/dir.xml": {body: valuteDirectoryXML},
|
||||||
|
"/rates.xml": {body: dailyRatesXML},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := NewConnector(zap.NewNop(), map[string]any{
|
||||||
|
"base_url": "http://cbr.test",
|
||||||
|
"directory_path": "/dir.xml",
|
||||||
|
"daily_path": "/rates.xml",
|
||||||
|
"http_round_tripper": transport,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewConnector returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.FetchTicker(context.Background(), "USD"); err != nil {
|
||||||
|
t.Fatalf("FetchTicker returned error with custom paths: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuteDirectoryXML = `
|
||||||
|
<Valuta name="Foreign Currency Market">
|
||||||
|
<Item ID="R01235">
|
||||||
|
<ISO_Num_Code>840</ISO_Num_Code>
|
||||||
|
<ISO_Char_Code>USD</ISO_Char_Code>
|
||||||
|
<Nominal>1</Nominal>
|
||||||
|
<Name>US Dollar</Name>
|
||||||
|
<EngName>US Dollar</EngName>
|
||||||
|
</Item>
|
||||||
|
</Valuta>`
|
||||||
|
|
||||||
|
const dailyRatesXML = `
|
||||||
|
<ValCurs Date="02.09.2024" name="Foreign Currency Market">
|
||||||
|
<Valute ID="R01235">
|
||||||
|
<NumCode>840</NumCode>
|
||||||
|
<CharCode>USD</CharCode>
|
||||||
|
<Nominal>1</Nominal>
|
||||||
|
<Name>US Dollar</Name>
|
||||||
|
<Value>95,1234</Value>
|
||||||
|
</Valute>
|
||||||
|
</ValCurs>`
|
||||||
|
|
||||||
|
const dynamicRatesXML = `
|
||||||
|
<ValCurs ID="R01235" DateRange1="05/01/2023" DateRange2="05/01/2023" name="Foreign Currency Market Dynamic">
|
||||||
|
<Record Date="05.01.2023" Id="R01235">
|
||||||
|
<Nominal>1</Nominal>
|
||||||
|
<Value>70,1</Value>
|
||||||
|
</Record>
|
||||||
|
</ValCurs>`
|
||||||
|
|
||||||
|
type stubResponse struct {
|
||||||
|
status int
|
||||||
|
body string
|
||||||
|
check func(*http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type stubRoundTripper struct {
|
||||||
|
responses map[string]stubResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if s.responses == nil {
|
||||||
|
return nil, fmt.Errorf("no responses configured")
|
||||||
|
}
|
||||||
|
res, ok := s.responses[req.URL.Path]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path)
|
||||||
|
}
|
||||||
|
if res.check != nil {
|
||||||
|
if err := res.check(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status := res.status
|
||||||
|
if status == 0 {
|
||||||
|
status = http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: status,
|
||||||
|
Body: io.NopCloser(strings.NewReader(res.body)),
|
||||||
|
Header: http.Header{"Content-Type": []string{"text/xml"}},
|
||||||
|
Request: req,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/market/common"
|
"github.com/tech/sendico/fx/ingestor/internal/market/common"
|
||||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -61,7 +61,7 @@ func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Conne
|
|||||||
|
|
||||||
parsed, err := url.Parse(baseURL)
|
parsed, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("coingecko: invalid base url", err)
|
return nil, merrors.InvalidArgumentWrap(err, "coingecko: invalid base url", "base_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
@@ -96,7 +96,7 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
|||||||
|
|
||||||
endpoint, err := url.Parse(c.base)
|
endpoint, err := url.Parse(c.base)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("coingecko: parse base url", err)
|
return nil, merrors.InternalWrap(err, "coingecko: parse base url")
|
||||||
}
|
}
|
||||||
endpoint.Path = strings.TrimRight(endpoint.Path, "/") + "/simple/price"
|
endpoint.Path = strings.TrimRight(endpoint.Path, "/") + "/simple/price"
|
||||||
query := endpoint.Query()
|
query := endpoint.Query()
|
||||||
@@ -107,19 +107,19 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
|||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("coingecko: build request", err)
|
return nil, merrors.InternalWrap(err, "coingecko: build request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("CoinGecko request failed", zap.String("symbol", symbol), zap.Error(err))
|
c.logger.Warn("CoinGecko request failed", zap.String("symbol", symbol), zap.Error(err))
|
||||||
return nil, fmerrors.Wrap("coingecko: request failed", err)
|
return nil, merrors.InternalWrap(err, "coingecko: request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||||
return nil, fmerrors.New("coingecko: unexpected status " + strconv.Itoa(resp.StatusCode))
|
return nil, merrors.Internal("coingecko: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
@@ -128,21 +128,21 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
|||||||
var payload map[string]map[string]interface{}
|
var payload map[string]map[string]interface{}
|
||||||
if err := decoder.Decode(&payload); err != nil {
|
if err := decoder.Decode(&payload); err != nil {
|
||||||
c.logger.Warn("CoinGecko decode failed", zap.String("symbol", symbol), zap.Error(err))
|
c.logger.Warn("CoinGecko decode failed", zap.String("symbol", symbol), zap.Error(err))
|
||||||
return nil, fmerrors.Wrap("coingecko: decode response", err)
|
return nil, merrors.InternalWrap(err, "coingecko: decode response")
|
||||||
}
|
}
|
||||||
|
|
||||||
coinData, ok := payload[coinID]
|
coinData, ok := payload[coinID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmerrors.New("coingecko: coin id not found in response")
|
return nil, merrors.Internal("coingecko: coin id not found in response")
|
||||||
}
|
}
|
||||||
priceValue, ok := coinData[vsCurrency]
|
priceValue, ok := coinData[vsCurrency]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmerrors.New("coingecko: vs currency not found in response")
|
return nil, merrors.Internal("coingecko: vs currency not found in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
price, ok := toFloat(priceValue)
|
price, ok := toFloat(priceValue)
|
||||||
if !ok || price <= 0 {
|
if !ok || price <= 0 {
|
||||||
return nil, fmerrors.New("coingecko: invalid price value in response")
|
return nil, merrors.Internal("coingecko: invalid price value in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
priceStr := strconv.FormatFloat(price, 'f', -1, 64)
|
priceStr := strconv.FormatFloat(price, 'f', -1, 64)
|
||||||
@@ -171,7 +171,7 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
|||||||
func parseSymbol(symbol string) (string, string, error) {
|
func parseSymbol(symbol string) (string, string, error) {
|
||||||
trimmed := strings.TrimSpace(symbol)
|
trimmed := strings.TrimSpace(symbol)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
return "", "", fmerrors.New("coingecko: symbol is empty")
|
return "", "", merrors.InvalidArgument("coingecko: symbol is empty", "symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.FieldsFunc(strings.ToLower(trimmed), func(r rune) bool {
|
parts := strings.FieldsFunc(strings.ToLower(trimmed), func(r rune) bool {
|
||||||
@@ -183,13 +183,13 @@ func parseSymbol(symbol string) (string, string, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return "", "", fmerrors.New("coingecko: symbol must be <coin_id>/<vs_currency>")
|
return "", "", merrors.InvalidArgument("coingecko: symbol must be <coin_id>/<vs_currency>", "symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
coinID := strings.TrimSpace(parts[0])
|
coinID := strings.TrimSpace(parts[0])
|
||||||
vsCurrency := strings.TrimSpace(parts[1])
|
vsCurrency := strings.TrimSpace(parts[1])
|
||||||
if coinID == "" || vsCurrency == "" {
|
if coinID == "" || vsCurrency == "" {
|
||||||
return "", "", fmerrors.New("coingecko: symbol contains empty segments")
|
return "", "", merrors.InvalidArgument("coingecko: symbol contains empty segments", "symbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
return coinID, vsCurrency, nil
|
return coinID, vsCurrency, nil
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/market/binance"
|
"github.com/tech/sendico/fx/ingestor/internal/market/binance"
|
||||||
|
"github.com/tech/sendico/fx/ingestor/internal/market/cbr"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/market/coingecko"
|
"github.com/tech/sendico/fx/ingestor/internal/market/coingecko"
|
||||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
)
|
)
|
||||||
@@ -21,7 +22,7 @@ func BuildConnectors(logger mlogger.Logger, configs []model.DriverConfig[mmodel.
|
|||||||
for _, cfg := range configs {
|
for _, cfg := range configs {
|
||||||
driver := mmodel.NormalizeDriver(cfg.Driver)
|
driver := mmodel.NormalizeDriver(cfg.Driver)
|
||||||
if driver.IsEmpty() {
|
if driver.IsEmpty() {
|
||||||
return nil, fmerrors.New("market: connector driver is empty")
|
return nil, merrors.InvalidArgument("market: connector driver is empty", "driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -34,12 +35,14 @@ func BuildConnectors(logger mlogger.Logger, configs []model.DriverConfig[mmodel.
|
|||||||
conn, err = binance.NewConnector(logger, cfg.Settings)
|
conn, err = binance.NewConnector(logger, cfg.Settings)
|
||||||
case mmodel.DriverCoinGecko:
|
case mmodel.DriverCoinGecko:
|
||||||
conn, err = coingecko.NewConnector(logger, cfg.Settings)
|
conn, err = coingecko.NewConnector(logger, cfg.Settings)
|
||||||
|
case mmodel.DriverCBR:
|
||||||
|
conn, err = cbr.NewConnector(logger, cfg.Settings)
|
||||||
default:
|
default:
|
||||||
err = fmerrors.New("market: unsupported driver " + driver.String())
|
err = merrors.InvalidArgument("market: unsupported driver "+driver.String(), "driver")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmerrors.Wrap("market: build connector "+driver.String(), err)
|
return nil, merrors.InternalWrap(err, "market: build connector "+driver.String())
|
||||||
}
|
}
|
||||||
connectors[driver] = conn
|
connectors[driver] = conn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/config"
|
"github.com/tech/sendico/fx/ingestor/internal/config"
|
||||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
|
||||||
"github.com/tech/sendico/pkg/api/routers"
|
"github.com/tech/sendico/pkg/api/routers"
|
||||||
"github.com/tech/sendico/pkg/api/routers/health"
|
"github.com/tech/sendico/pkg/api/routers/health"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ type Server interface {
|
|||||||
|
|
||||||
func NewServer(logger mlogger.Logger, cfg *config.MetricsConfig) (Server, error) {
|
func NewServer(logger mlogger.Logger, cfg *config.MetricsConfig) (Server, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return nil, fmerrors.New("metrics: logger is nil")
|
return nil, merrors.InvalidArgument("metrics: logger is nil")
|
||||||
}
|
}
|
||||||
if cfg == nil || !cfg.Enabled {
|
if cfg == nil || !cfg.Enabled {
|
||||||
logger.Debug("Metrics disabled; using noop server")
|
logger.Debug("Metrics disabled; using noop server")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Driver string
|
|||||||
const (
|
const (
|
||||||
DriverBinance Driver = "BINANCE"
|
DriverBinance Driver = "BINANCE"
|
||||||
DriverCoinGecko Driver = "COINGECKO"
|
DriverCoinGecko Driver = "COINGECKO"
|
||||||
|
DriverCBR Driver = "CBR"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d Driver) String() string {
|
func (d Driver) String() string {
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -187,24 +187,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -162,8 +162,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251119083800-2aa1d4cc79d7 // indirect
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251208031133-be43a854e4be // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||||
@@ -82,9 +82,9 @@ require (
|
|||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
|||||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251119083800-2aa1d4cc79d7 h1:uups37roJCTtR/BrJa0WoMrxt3rzgV+Qrj+TxYyJoAo=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251208031133-be43a854e4be h1:1LtMLkGIqE5IQZ7Vdh4zv8A6LECInKF86/fTVxKxYLE=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251119083800-2aa1d4cc79d7/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251208031133-be43a854e4be/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -333,8 +333,8 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -343,16 +343,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -189,24 +189,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -189,24 +189,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/xhit/go-simple-mail/v2 v2.16.0
|
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||||
go.mongodb.org/mongo-driver v1.17.6
|
go.mongodb.org/mongo-driver v1.17.6
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/text v0.31.0
|
golang.org/x/text v0.32.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
|||||||
@@ -202,24 +202,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ require (
|
|||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -190,24 +190,24 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
|||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -223,8 +223,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -240,8 +240,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
@@ -250,8 +250,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -132,9 +132,9 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/sync v0.18.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -311,8 +311,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -328,8 +328,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
@@ -338,8 +338,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
@@ -17,6 +19,9 @@ class PayoutRoutes {
|
|||||||
static const editWallet = 'payout-edit-wallet';
|
static const editWallet = 'payout-edit-wallet';
|
||||||
static const walletTopUp = 'payout-wallet-top-up';
|
static const walletTopUp = 'payout-wallet-top-up';
|
||||||
|
|
||||||
|
static const paymentTypeQuery = 'paymentType';
|
||||||
|
static const returnToQuery = 'returnTo';
|
||||||
|
|
||||||
static const dashboardPath = '/dashboard';
|
static const dashboardPath = '/dashboard';
|
||||||
static const recipientsPath = '/dashboard/recipients';
|
static const recipientsPath = '/dashboard/recipients';
|
||||||
static const addRecipientPath = '/dashboard/recipients/add';
|
static const addRecipientPath = '/dashboard/recipients/add';
|
||||||
@@ -103,10 +108,70 @@ class PayoutRoutes {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, String> buildQueryParameters({
|
||||||
|
PaymentType? paymentType,
|
||||||
|
PayoutDestination? returnTo,
|
||||||
|
}) {
|
||||||
|
final params = <String, String>{
|
||||||
|
if (paymentType != null) paymentTypeQuery: paymentType.name,
|
||||||
|
if (returnTo != null) returnToQuery: nameFor(returnTo),
|
||||||
|
};
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PaymentType? paymentTypeFromState(GoRouterState state) =>
|
||||||
|
paymentTypeFromRaw(state.uri.queryParameters[paymentTypeQuery]);
|
||||||
|
|
||||||
|
static PaymentType? paymentTypeFromRaw(String? raw) => raw == null
|
||||||
|
? null
|
||||||
|
: PaymentType.values.firstWhereOrNull((type) => type.name == raw);
|
||||||
|
|
||||||
|
static PayoutDestination fallbackFromState(
|
||||||
|
GoRouterState state, {
|
||||||
|
PayoutDestination defaultDestination = PayoutDestination.dashboard,
|
||||||
|
}) {
|
||||||
|
final raw = state.uri.queryParameters[returnToQuery];
|
||||||
|
return destinationFor(raw) ?? defaultDestination;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PayoutNavigation on BuildContext {
|
extension PayoutNavigation on BuildContext {
|
||||||
void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination));
|
void goToPayout(PayoutDestination destination) => goNamed(PayoutRoutes.nameFor(destination));
|
||||||
|
|
||||||
void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination));
|
void pushToPayout(PayoutDestination destination) => pushNamed(PayoutRoutes.nameFor(destination));
|
||||||
}
|
|
||||||
|
void goToPayment({
|
||||||
|
PaymentType? paymentType,
|
||||||
|
PayoutDestination? returnTo,
|
||||||
|
}) =>
|
||||||
|
goNamed(
|
||||||
|
PayoutRoutes.payment,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
|
paymentType: paymentType,
|
||||||
|
returnTo: returnTo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void pushToPayment({
|
||||||
|
PaymentType? paymentType,
|
||||||
|
PayoutDestination? returnTo,
|
||||||
|
}) =>
|
||||||
|
pushNamed(
|
||||||
|
PayoutRoutes.payment,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(
|
||||||
|
paymentType: paymentType,
|
||||||
|
returnTo: returnTo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
void pushToWalletTopUp({PayoutDestination? returnTo}) => pushNamed(
|
||||||
|
PayoutRoutes.walletTopUp,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(returnTo: returnTo),
|
||||||
|
);
|
||||||
|
|
||||||
|
void pushToEditWallet({PayoutDestination? returnTo}) => pushNamed(
|
||||||
|
PayoutRoutes.editWallet,
|
||||||
|
queryParameters: PayoutRoutes.buildQueryParameters(returnTo: returnTo),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,10 +4,13 @@ import 'package:go_router/go_router.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
import 'package:pweb/app/router/payout_routes.dart';
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/pages/address_book/form/page.dart';
|
import 'package:pweb/pages/address_book/form/page.dart';
|
||||||
import 'package:pweb/pages/address_book/page/page.dart';
|
import 'package:pweb/pages/address_book/page/page.dart';
|
||||||
import 'package:pweb/pages/dashboard/dashboard.dart';
|
import 'package:pweb/pages/dashboard/dashboard.dart';
|
||||||
@@ -17,7 +20,7 @@ import 'package:pweb/pages/payout_page/wallet/edit/page.dart';
|
|||||||
import 'package:pweb/pages/report/page.dart';
|
import 'package:pweb/pages/report/page.dart';
|
||||||
import 'package:pweb/pages/settings/profile/page.dart';
|
import 'package:pweb/pages/settings/profile/page.dart';
|
||||||
import 'package:pweb/pages/wallet_top_up/page.dart';
|
import 'package:pweb/pages/wallet_top_up/page.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/widgets/sidebar/page.dart';
|
import 'package:pweb/widgets/sidebar/page.dart';
|
||||||
@@ -36,15 +39,22 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
path: routerPage(Pages.dashboard),
|
path: routerPage(Pages.dashboard),
|
||||||
pageBuilder: (context, _) => NoTransitionPage(
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
child: DashboardPage(
|
child: DashboardPage(
|
||||||
onRecipientSelected: (recipient) => context
|
onRecipientSelected: (recipient) => _startPayment(
|
||||||
.read<PageSelectorProvider>()
|
context,
|
||||||
.selectRecipient(context, recipient),
|
recipient: recipient,
|
||||||
onGoToPaymentWithoutRecipient: (type) => context
|
returnTo: PayoutDestination.dashboard,
|
||||||
.read<PageSelectorProvider>()
|
),
|
||||||
.startPaymentWithoutRecipient(context, type),
|
onGoToPaymentWithoutRecipient: (type) => _startPayment(
|
||||||
onTopUp: (wallet) => context
|
context,
|
||||||
.read<PageSelectorProvider>()
|
recipient: null,
|
||||||
.openWalletTopUp(context, wallet),
|
paymentType: type,
|
||||||
|
returnTo: PayoutDestination.dashboard,
|
||||||
|
),
|
||||||
|
onTopUp: (wallet) => _openWalletTopUp(
|
||||||
|
context,
|
||||||
|
wallet,
|
||||||
|
returnTo: PayoutDestination.dashboard,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -55,15 +65,16 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
return NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: RecipientAddressBookPage(
|
child: RecipientAddressBookPage(
|
||||||
onRecipientSelected: (recipient) => context
|
onRecipientSelected: (recipient) => _startPayment(
|
||||||
.read<PageSelectorProvider>()
|
context,
|
||||||
.selectRecipient(context, recipient, fromList: true),
|
recipient: recipient,
|
||||||
onAddRecipient: () => context
|
returnTo: PayoutDestination.recipients,
|
||||||
.read<PageSelectorProvider>()
|
),
|
||||||
.goToAddRecipient(context),
|
onAddRecipient: () => _openAddRecipient(context),
|
||||||
onEditRecipient: (recipient) => context
|
onEditRecipient: (recipient) => _openAddRecipient(
|
||||||
.read<PageSelectorProvider>()
|
context,
|
||||||
.editRecipient(context, recipient, fromList: true),
|
recipient: recipient,
|
||||||
|
),
|
||||||
onDeleteRecipient: (recipient) => executeActionWithNotification(
|
onDeleteRecipient: (recipient) => executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: () async =>
|
action: () async =>
|
||||||
@@ -79,15 +90,11 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
name: PayoutRoutes.addRecipient,
|
name: PayoutRoutes.addRecipient,
|
||||||
path: PayoutRoutes.addRecipientPath,
|
path: PayoutRoutes.addRecipientPath,
|
||||||
pageBuilder: (context, _) {
|
pageBuilder: (context, _) {
|
||||||
final selector = context.read<PageSelectorProvider>();
|
final recipient = context.read<RecipientsProvider>().currentObject;
|
||||||
final recipient = selector.recipientProvider.currentObject;
|
|
||||||
return NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: AdressBookRecipientForm(
|
child: AdressBookRecipientForm(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
onSaved: (_) => selector.selectPage(
|
onSaved: (_) => context.goToPayout(PayoutDestination.recipients),
|
||||||
context,
|
|
||||||
PayoutDestination.recipients,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -95,13 +102,20 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.payment,
|
name: PayoutRoutes.payment,
|
||||||
path: PayoutRoutes.paymentPath,
|
path: PayoutRoutes.paymentPath,
|
||||||
pageBuilder: (context, _) => NoTransitionPage(
|
pageBuilder: (context, state) {
|
||||||
child: PaymentPage(
|
final fallbackDestination = PayoutRoutes.fallbackFromState(
|
||||||
onBack: (_) => context
|
state,
|
||||||
.read<PageSelectorProvider>()
|
defaultDestination: PayoutDestination.dashboard,
|
||||||
.goBackFromPayment(context),
|
);
|
||||||
),
|
|
||||||
),
|
return NoTransitionPage(
|
||||||
|
child: PaymentPage(
|
||||||
|
onBack: (_) => _popOrGo(context, fallbackDestination),
|
||||||
|
initialPaymentType: PayoutRoutes.paymentTypeFromState(state),
|
||||||
|
fallbackDestination: fallbackDestination,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.settings,
|
name: PayoutRoutes.settings,
|
||||||
@@ -122,24 +136,30 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
path: PayoutRoutes.methodsPath,
|
path: PayoutRoutes.methodsPath,
|
||||||
pageBuilder: (context, _) => NoTransitionPage(
|
pageBuilder: (context, _) => NoTransitionPage(
|
||||||
child: PaymentConfigPage(
|
child: PaymentConfigPage(
|
||||||
onWalletTap: (wallet) => context
|
onWalletTap: (wallet) => _openWalletEdit(
|
||||||
.read<PageSelectorProvider>()
|
context,
|
||||||
.selectWallet(context, wallet),
|
wallet,
|
||||||
|
returnTo: PayoutDestination.methods,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.editWallet,
|
name: PayoutRoutes.editWallet,
|
||||||
path: PayoutRoutes.editWalletPath,
|
path: PayoutRoutes.editWalletPath,
|
||||||
pageBuilder: (context, _) {
|
pageBuilder: (context, state) {
|
||||||
final provider = context.read<PageSelectorProvider>();
|
final walletsProvider = context.read<WalletsProvider>();
|
||||||
final wallet = provider.walletsProvider.selectedWallet;
|
final wallet = walletsProvider.selectedWallet;
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final fallbackDestination = PayoutRoutes.fallbackFromState(
|
||||||
|
state,
|
||||||
|
defaultDestination: PayoutDestination.methods,
|
||||||
|
);
|
||||||
|
|
||||||
return NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: wallet != null
|
child: wallet != null
|
||||||
? WalletEditPage(
|
? WalletEditPage(
|
||||||
onBack: () => provider.goBackFromWalletEdit(context),
|
onBack: () => _popOrGo(context, fallbackDestination),
|
||||||
)
|
)
|
||||||
: Center(child: Text(loc.noWalletSelected)),
|
: Center(child: Text(loc.noWalletSelected)),
|
||||||
);
|
);
|
||||||
@@ -148,13 +168,65 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
name: PayoutRoutes.walletTopUp,
|
name: PayoutRoutes.walletTopUp,
|
||||||
path: PayoutRoutes.walletTopUpPath,
|
path: PayoutRoutes.walletTopUpPath,
|
||||||
pageBuilder: (context, _) => NoTransitionPage(
|
pageBuilder: (context, state) {
|
||||||
child: WalletTopUpPage(
|
final fallbackDestination = PayoutRoutes.fallbackFromState(
|
||||||
onBack: () => context
|
state,
|
||||||
.read<PageSelectorProvider>()
|
defaultDestination: PayoutDestination.dashboard,
|
||||||
.goBackFromWalletTopUp(context),
|
);
|
||||||
),
|
|
||||||
),
|
return NoTransitionPage(
|
||||||
|
child: WalletTopUpPage(
|
||||||
|
onBack: () => _popOrGo(context, fallbackDestination),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void _startPayment(
|
||||||
|
BuildContext context, {
|
||||||
|
Recipient? recipient,
|
||||||
|
PaymentType? paymentType,
|
||||||
|
required PayoutDestination returnTo,
|
||||||
|
}) {
|
||||||
|
context.read<RecipientsProvider>().setCurrentObject(recipient?.id);
|
||||||
|
context.pushToPayment(
|
||||||
|
paymentType: paymentType,
|
||||||
|
returnTo: returnTo,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openAddRecipient(
|
||||||
|
BuildContext context, {
|
||||||
|
Recipient? recipient,
|
||||||
|
}) {
|
||||||
|
context.read<RecipientsProvider>().setCurrentObject(recipient?.id);
|
||||||
|
context.pushNamed(PayoutRoutes.addRecipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openWalletEdit(
|
||||||
|
BuildContext context,
|
||||||
|
Wallet wallet, {
|
||||||
|
required PayoutDestination returnTo,
|
||||||
|
}) {
|
||||||
|
context.read<WalletsProvider>().selectWallet(wallet);
|
||||||
|
context.pushToEditWallet(returnTo: returnTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openWalletTopUp(
|
||||||
|
BuildContext context,
|
||||||
|
Wallet wallet, {
|
||||||
|
required PayoutDestination returnTo,
|
||||||
|
}) {
|
||||||
|
context.read<WalletsProvider>().selectWallet(wallet);
|
||||||
|
context.pushToWalletTopUp(returnTo: returnTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _popOrGo(BuildContext context, PayoutDestination destination) {
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
context.goToPayout(destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import 'package:pweb/app/timeago.dart';
|
|||||||
import 'package:pweb/providers/carousel.dart';
|
import 'package:pweb/providers/carousel.dart';
|
||||||
import 'package:pweb/providers/mock_payment.dart';
|
import 'package:pweb/providers/mock_payment.dart';
|
||||||
import 'package:pweb/providers/operatioins.dart';
|
import 'package:pweb/providers/operatioins.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
|
||||||
import 'package:pweb/providers/two_factor.dart';
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
import 'package:pweb/providers/upload_history.dart';
|
import 'package:pweb/providers/upload_history.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
@@ -94,12 +93,6 @@ void main() async {
|
|||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => MockPaymentProvider(),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProxyProvider3<RecipientsProvider, WalletsProvider, PaymentMethodsProvider, PageSelectorProvider>(
|
|
||||||
create: (context) => PageSelectorProvider(),
|
|
||||||
update: (context, recipientProv, walletsProv, methodsProv, previous) =>
|
|
||||||
previous ?? PageSelectorProvider()..update(recipientProv, walletsProv, methodsProv),
|
|
||||||
),
|
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
Future<bool> showDeleteConfirmationDialog(BuildContext context) async {
|
|
||||||
final l10n = AppLocalizations.of(context)!;
|
|
||||||
return await showDialog<bool>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => AlertDialog(
|
|
||||||
title: Text(l10n.delete),
|
|
||||||
content: Text(l10n.deletePaymentConfirmation),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
|
||||||
child: Text(l10n.cancel),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
|
||||||
child: Text(l10n.delete),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
) ?? false;
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,34 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/providers/payment_flow_provider.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/payment_page_body.dart';
|
import 'package:pweb/providers/payment_flow.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||||
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPage extends StatefulWidget {
|
class PaymentPage extends StatefulWidget {
|
||||||
final ValueChanged<Recipient?>? onBack;
|
final ValueChanged<Recipient?>? onBack;
|
||||||
|
final PaymentType? initialPaymentType;
|
||||||
|
final PayoutDestination fallbackDestination;
|
||||||
|
|
||||||
const PaymentPage({super.key, this.onBack});
|
const PaymentPage({
|
||||||
|
super.key,
|
||||||
|
this.onBack,
|
||||||
|
this.initialPaymentType,
|
||||||
|
this.fallbackDestination = PayoutDestination.dashboard,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PaymentPage> createState() => _PaymentPageState();
|
State<PaymentPage> createState() => _PaymentPageState();
|
||||||
@@ -29,9 +44,8 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_searchController = TextEditingController();
|
_searchController = TextEditingController();
|
||||||
_searchFocusNode = FocusNode();
|
_searchFocusNode = FocusNode();
|
||||||
final pageSelector = context.read<PageSelectorProvider>();
|
|
||||||
_flowProvider = PaymentFlowProvider(
|
_flowProvider = PaymentFlowProvider(
|
||||||
initialType: pageSelector.getDefaultPaymentType(),
|
initialType: widget.initialPaymentType ?? PaymentType.bankAccount,
|
||||||
);
|
);
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
|
||||||
@@ -46,11 +60,15 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _initializePaymentPage() {
|
void _initializePaymentPage() {
|
||||||
final pageSelector = context.read<PageSelectorProvider>();
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
|
_handleWalletAutoSelection(methodsProvider);
|
||||||
|
|
||||||
pageSelector.handleWalletAutoSelection();
|
final recipient = context.read<RecipientsProvider>().currentObject;
|
||||||
|
_syncFlowProvider(
|
||||||
_flowProvider.syncWithSelector(pageSelector);
|
recipient: recipient,
|
||||||
|
methodsProvider: methodsProvider,
|
||||||
|
preferredType: widget.initialPaymentType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSearchChanged(String query) {
|
void _handleSearchChanged(String query) {
|
||||||
@@ -58,22 +76,28 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleRecipientSelected(Recipient recipient) {
|
void _handleRecipientSelected(Recipient recipient) {
|
||||||
final pageSelector = context.read<PageSelectorProvider>();
|
|
||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(recipient.id);
|
recipientProvider.setCurrentObject(recipient.id);
|
||||||
pageSelector.selectRecipient(context, recipient);
|
_flowProvider.reset(
|
||||||
_flowProvider.reset(pageSelector);
|
recipient: recipient,
|
||||||
|
availableTypes: _availablePaymentTypes(recipient, methodsProvider),
|
||||||
|
preferredType: widget.initialPaymentType,
|
||||||
|
);
|
||||||
_clearSearchField();
|
_clearSearchField();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleRecipientCleared() {
|
void _handleRecipientCleared() {
|
||||||
final pageSelector = context.read<PageSelectorProvider>();
|
|
||||||
final recipientProvider = context.read<RecipientsProvider>();
|
final recipientProvider = context.read<RecipientsProvider>();
|
||||||
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
|
|
||||||
recipientProvider.setCurrentObject(null);
|
recipientProvider.setCurrentObject(null);
|
||||||
pageSelector.selectRecipient(context, null);
|
_flowProvider.reset(
|
||||||
_flowProvider.reset(pageSelector);
|
recipient: null,
|
||||||
|
availableTypes: _availablePaymentTypes(null, methodsProvider),
|
||||||
|
preferredType: widget.initialPaymentType,
|
||||||
|
);
|
||||||
_clearSearchField();
|
_clearSearchField();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +114,26 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final pageSelector = context.watch<PageSelectorProvider>();
|
final methodsProvider = context.watch<PaymentMethodsProvider>();
|
||||||
_flowProvider.syncWithSelector(pageSelector);
|
final recipientProvider = context.watch<RecipientsProvider>();
|
||||||
|
final recipient = recipientProvider.currentObject;
|
||||||
|
final availableTypes = _availablePaymentTypes(recipient, methodsProvider);
|
||||||
|
|
||||||
|
_syncFlowProvider(
|
||||||
|
recipient: recipient,
|
||||||
|
methodsProvider: methodsProvider,
|
||||||
|
preferredType: recipient != null ? widget.initialPaymentType : null,
|
||||||
|
);
|
||||||
|
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: _flowProvider,
|
value: _flowProvider,
|
||||||
child: PaymentPageBody(
|
child: PaymentPageBody(
|
||||||
onBack: widget.onBack,
|
onBack: widget.onBack,
|
||||||
|
fallbackDestination: widget.fallbackDestination,
|
||||||
|
recipient: recipient,
|
||||||
|
recipientProvider: recipientProvider,
|
||||||
|
methodsProvider: methodsProvider,
|
||||||
|
availablePaymentTypes: availableTypes,
|
||||||
searchController: _searchController,
|
searchController: _searchController,
|
||||||
searchFocusNode: _searchFocusNode,
|
searchFocusNode: _searchFocusNode,
|
||||||
onSearchChanged: _handleSearchChanged,
|
onSearchChanged: _handleSearchChanged,
|
||||||
@@ -106,4 +143,56 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void _handleWalletAutoSelection(PaymentMethodsProvider methodsProvider) {
|
||||||
|
final wallet = context.read<WalletsProvider>().selectedWallet;
|
||||||
|
if (wallet == null) return;
|
||||||
|
|
||||||
|
final matchingMethod = _getPaymentMethodForWallet(wallet, methodsProvider);
|
||||||
|
if (matchingMethod != null) {
|
||||||
|
methodsProvider.setCurrentObject(matchingMethod.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _syncFlowProvider({
|
||||||
|
required Recipient? recipient,
|
||||||
|
required PaymentMethodsProvider methodsProvider,
|
||||||
|
PaymentType? preferredType,
|
||||||
|
}) {
|
||||||
|
_flowProvider.sync(
|
||||||
|
recipient: recipient,
|
||||||
|
availableTypes: _availablePaymentTypes(recipient, methodsProvider),
|
||||||
|
preferredType: preferredType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodMap _availablePaymentTypes(
|
||||||
|
Recipient? recipient,
|
||||||
|
PaymentMethodsProvider methodsProvider,
|
||||||
|
) {
|
||||||
|
if (recipient == null || !methodsProvider.isReady) return {};
|
||||||
|
|
||||||
|
final methodsForRecipient = methodsProvider.methods.where(
|
||||||
|
(method) => !method.isArchived && method.recipientRef == recipient.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
for (final method in methodsForRecipient) method.type: method.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentMethod? _getPaymentMethodForWallet(
|
||||||
|
Wallet wallet,
|
||||||
|
PaymentMethodsProvider methodsProvider,
|
||||||
|
) {
|
||||||
|
if (methodsProvider.methods.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return methodsProvider.methods.firstWhereOrNull(
|
||||||
|
(method) =>
|
||||||
|
method.type == PaymentType.wallet &&
|
||||||
|
(method.description?.contains(wallet.walletUserID) ?? false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/models/payment/type.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentDetailsSection extends StatelessWidget {
|
|
||||||
final bool isFormVisible;
|
|
||||||
final VoidCallback? onToggle;
|
|
||||||
final PaymentType selectedType;
|
|
||||||
final Object? data;
|
|
||||||
final bool isEditable;
|
|
||||||
|
|
||||||
const PaymentDetailsSection({
|
|
||||||
super.key,
|
|
||||||
required this.isFormVisible,
|
|
||||||
this.onToggle,
|
|
||||||
required this.selectedType,
|
|
||||||
required this.data,
|
|
||||||
required this.isEditable,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
|
|
||||||
return PaymentDetailsSection(
|
|
||||||
isFormVisible: isFormVisible,
|
|
||||||
onToggle: onToggle,
|
|
||||||
selectedType: selectedType,
|
|
||||||
data: data,
|
|
||||||
isEditable: isEditable,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentBackButton extends StatelessWidget {
|
||||||
|
final ValueChanged<Recipient?>? onBack;
|
||||||
|
final Recipient? recipient;
|
||||||
|
final PayoutDestination fallbackDestination;
|
||||||
|
|
||||||
|
const PaymentBackButton({
|
||||||
|
super.key,
|
||||||
|
required this.onBack,
|
||||||
|
required this.recipient,
|
||||||
|
required this.fallbackDestination,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
if (onBack != null) {
|
||||||
|
onBack!(recipient);
|
||||||
|
} else {
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
context.goToPayout(fallbackDestination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/state_view.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/page.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentPageBody extends StatelessWidget {
|
||||||
|
final ValueChanged<Recipient?>? onBack;
|
||||||
|
final Recipient? recipient;
|
||||||
|
final RecipientsProvider recipientProvider;
|
||||||
|
final PaymentMethodsProvider methodsProvider;
|
||||||
|
final MethodMap availablePaymentTypes;
|
||||||
|
final PayoutDestination fallbackDestination;
|
||||||
|
final TextEditingController searchController;
|
||||||
|
final FocusNode searchFocusNode;
|
||||||
|
final ValueChanged<String> onSearchChanged;
|
||||||
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
|
final VoidCallback onRecipientCleared;
|
||||||
|
final VoidCallback onSend;
|
||||||
|
|
||||||
|
const PaymentPageBody({
|
||||||
|
super.key,
|
||||||
|
required this.onBack,
|
||||||
|
required this.recipient,
|
||||||
|
required this.recipientProvider,
|
||||||
|
required this.methodsProvider,
|
||||||
|
required this.availablePaymentTypes,
|
||||||
|
required this.fallbackDestination,
|
||||||
|
required this.searchController,
|
||||||
|
required this.searchFocusNode,
|
||||||
|
required this.onSearchChanged,
|
||||||
|
required this.onRecipientSelected,
|
||||||
|
required this.onRecipientCleared,
|
||||||
|
required this.onSend,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
if (methodsProvider.isLoading) {
|
||||||
|
return const PaymentMethodsLoadingView();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodsProvider.error != null) {
|
||||||
|
return PaymentMethodsErrorView(
|
||||||
|
message: loc.notificationError(methodsProvider.error ?? loc.noErrorInformation),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PaymentPageContent(
|
||||||
|
onBack: onBack,
|
||||||
|
recipient: recipient,
|
||||||
|
recipientProvider: recipientProvider,
|
||||||
|
methodsProvider: methodsProvider,
|
||||||
|
availablePaymentTypes: availablePaymentTypes,
|
||||||
|
fallbackDestination: fallbackDestination,
|
||||||
|
searchController: searchController,
|
||||||
|
searchFocusNode: searchFocusNode,
|
||||||
|
onSearchChanged: onSearchChanged,
|
||||||
|
onRecipientSelected: onRecipientSelected,
|
||||||
|
onRecipientCleared: onRecipientCleared,
|
||||||
|
onSend: onSend,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,26 +2,33 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payment_methods/header.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/back_button.dart';
|
||||||
import 'package:pweb/pages/payment_methods/method_selector.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/header.dart';
|
||||||
import 'package:pweb/pages/payment_methods/send_button.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/send_button.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
|
import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart';
|
import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
|
import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/payment_flow.dart';
|
||||||
import 'package:pweb/providers/payment_flow_provider.dart';
|
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPageBody extends StatelessWidget {
|
class PaymentPageContent extends StatelessWidget {
|
||||||
final ValueChanged<Recipient?>? onBack;
|
final ValueChanged<Recipient?>? onBack;
|
||||||
|
final Recipient? recipient;
|
||||||
|
final RecipientsProvider recipientProvider;
|
||||||
|
final PaymentMethodsProvider methodsProvider;
|
||||||
|
final MethodMap availablePaymentTypes;
|
||||||
|
final PayoutDestination fallbackDestination;
|
||||||
final TextEditingController searchController;
|
final TextEditingController searchController;
|
||||||
final FocusNode searchFocusNode;
|
final FocusNode searchFocusNode;
|
||||||
final ValueChanged<String> onSearchChanged;
|
final ValueChanged<String> onSearchChanged;
|
||||||
@@ -29,9 +36,14 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
final VoidCallback onRecipientCleared;
|
final VoidCallback onRecipientCleared;
|
||||||
final VoidCallback onSend;
|
final VoidCallback onSend;
|
||||||
|
|
||||||
const PaymentPageBody({
|
const PaymentPageContent({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onBack,
|
required this.onBack,
|
||||||
|
required this.recipient,
|
||||||
|
required this.recipientProvider,
|
||||||
|
required this.methodsProvider,
|
||||||
|
required this.availablePaymentTypes,
|
||||||
|
required this.fallbackDestination,
|
||||||
required this.searchController,
|
required this.searchController,
|
||||||
required this.searchFocusNode,
|
required this.searchFocusNode,
|
||||||
required this.onSearchChanged,
|
required this.onSearchChanged,
|
||||||
@@ -43,21 +55,9 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dimensions = AppDimensions();
|
final dimensions = AppDimensions();
|
||||||
final pageSelector = context.watch<PageSelectorProvider>();
|
|
||||||
final methodsProvider = context.watch<PaymentMethodsProvider>();
|
|
||||||
final recipientProvider = context.watch<RecipientsProvider>();
|
|
||||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
final recipient = pageSelector.selectedRecipient;
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (methodsProvider.isLoading) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodsProvider.error != null) {
|
|
||||||
return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
@@ -73,18 +73,20 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
PaymentBackButton(onBack: onBack, pageSelector: pageSelector),
|
PaymentBackButton(
|
||||||
|
onBack: onBack,
|
||||||
|
recipient: recipient,
|
||||||
|
fallbackDestination: fallbackDestination,
|
||||||
|
),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
PaymentHeader(),
|
PaymentHeader(),
|
||||||
SizedBox(height: dimensions.paddingXXLarge),
|
SizedBox(height: dimensions.paddingXXLarge),
|
||||||
|
|
||||||
SectionTitle(loc.sourceOfFunds),
|
SectionTitle(loc.sourceOfFunds),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
PaymentMethodSelector(
|
PaymentMethodSelector(
|
||||||
onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id),
|
onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id),
|
||||||
),
|
),
|
||||||
SizedBox(height: dimensions.paddingXLarge),
|
SizedBox(height: dimensions.paddingXLarge),
|
||||||
|
|
||||||
RecipientSection(
|
RecipientSection(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
@@ -95,19 +97,15 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
onRecipientSelected: onRecipientSelected,
|
onRecipientSelected: onRecipientSelected,
|
||||||
onRecipientCleared: onRecipientCleared,
|
onRecipientCleared: onRecipientCleared,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: dimensions.paddingXLarge),
|
SizedBox(height: dimensions.paddingXLarge),
|
||||||
|
|
||||||
PaymentInfoSection(
|
PaymentInfoSection(
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
pageSelector: pageSelector,
|
|
||||||
flowProvider: flowProvider,
|
flowProvider: flowProvider,
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
|
availableTypes: availablePaymentTypes,
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: dimensions.paddingLarge),
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
const PaymentFormWidget(),
|
const PaymentFormWidget(),
|
||||||
|
|
||||||
SizedBox(height: dimensions.paddingXXXLarge),
|
SizedBox(height: dimensions.paddingXXXLarge),
|
||||||
SendButton(onPressed: onSend),
|
SendButton(onPressed: onSend),
|
||||||
SizedBox(height: dimensions.paddingLarge),
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
@@ -120,31 +118,3 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaymentBackButton extends StatelessWidget {
|
|
||||||
final ValueChanged<Recipient?>? onBack;
|
|
||||||
final PageSelectorProvider pageSelector;
|
|
||||||
|
|
||||||
const PaymentBackButton({
|
|
||||||
super.key,
|
|
||||||
required this.onBack,
|
|
||||||
required this.pageSelector,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () {
|
|
||||||
if (onBack != null) {
|
|
||||||
onBack!(pageSelector.selectedRecipient);
|
|
||||||
} else {
|
|
||||||
pageSelector.goBackFromPayment(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
120
frontend/pweb/lib/pages/payment_methods/payment_page/page.dart
Normal file
120
frontend/pweb/lib/pages/payment_methods/payment_page/page.dart
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/back_button.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/header.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/payment_page/send_button.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/payment_info_section.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/recipient_section.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||||
|
import 'package:pweb/providers/payment_flow.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentPageContent extends StatelessWidget {
|
||||||
|
final ValueChanged<Recipient?>? onBack;
|
||||||
|
final Recipient? recipient;
|
||||||
|
final RecipientsProvider recipientProvider;
|
||||||
|
final PaymentMethodsProvider methodsProvider;
|
||||||
|
final MethodMap availablePaymentTypes;
|
||||||
|
final PayoutDestination fallbackDestination;
|
||||||
|
final TextEditingController searchController;
|
||||||
|
final FocusNode searchFocusNode;
|
||||||
|
final ValueChanged<String> onSearchChanged;
|
||||||
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
|
final VoidCallback onRecipientCleared;
|
||||||
|
final VoidCallback onSend;
|
||||||
|
|
||||||
|
const PaymentPageContent({
|
||||||
|
super.key,
|
||||||
|
required this.onBack,
|
||||||
|
required this.recipient,
|
||||||
|
required this.recipientProvider,
|
||||||
|
required this.methodsProvider,
|
||||||
|
required this.availablePaymentTypes,
|
||||||
|
required this.fallbackDestination,
|
||||||
|
required this.searchController,
|
||||||
|
required this.searchFocusNode,
|
||||||
|
required this.onSearchChanged,
|
||||||
|
required this.onRecipientSelected,
|
||||||
|
required this.onRecipientCleared,
|
||||||
|
required this.onSend,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final dimensions = AppDimensions();
|
||||||
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: dimensions.maxContentWidth),
|
||||||
|
child: Material(
|
||||||
|
elevation: dimensions.elevationSmall,
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium),
|
||||||
|
color: Theme.of(context).colorScheme.onSecondary,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingLarge),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
PaymentBackButton(
|
||||||
|
onBack: onBack,
|
||||||
|
recipient: recipient,
|
||||||
|
fallbackDestination: fallbackDestination,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
|
PaymentHeader(),
|
||||||
|
SizedBox(height: dimensions.paddingXXLarge),
|
||||||
|
SectionTitle(loc.sourceOfFunds),
|
||||||
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
|
PaymentMethodSelector(
|
||||||
|
onMethodChanged: (m) => methodsProvider.setCurrentObject(m.id),
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingXLarge),
|
||||||
|
RecipientSection(
|
||||||
|
recipient: recipient,
|
||||||
|
dimensions: dimensions,
|
||||||
|
recipientProvider: recipientProvider,
|
||||||
|
searchController: searchController,
|
||||||
|
searchFocusNode: searchFocusNode,
|
||||||
|
onSearchChanged: onSearchChanged,
|
||||||
|
onRecipientSelected: onRecipientSelected,
|
||||||
|
onRecipientCleared: onRecipientCleared,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingXLarge),
|
||||||
|
PaymentInfoSection(
|
||||||
|
dimensions: dimensions,
|
||||||
|
flowProvider: flowProvider,
|
||||||
|
recipient: recipient,
|
||||||
|
availableTypes: availablePaymentTypes,
|
||||||
|
),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
const PaymentFormWidget(),
|
||||||
|
SizedBox(height: dimensions.paddingXXXLarge),
|
||||||
|
SendButton(onPressed: onSend),
|
||||||
|
SizedBox(height: dimensions.paddingLarge),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,7 @@ import 'package:pshared/models/recipient/recipient.dart';
|
|||||||
|
|
||||||
import 'package:pweb/pages/payment_methods/form.dart';
|
import 'package:pweb/pages/payment_methods/form.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/payment_flow.dart';
|
||||||
import 'package:pweb/providers/payment_flow_provider.dart';
|
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
import 'package:pweb/utils/payment/selector_type.dart';
|
import 'package:pweb/utils/payment/selector_type.dart';
|
||||||
|
|
||||||
@@ -16,14 +15,14 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
|||||||
|
|
||||||
class PaymentInfoSection extends StatelessWidget {
|
class PaymentInfoSection extends StatelessWidget {
|
||||||
final AppDimensions dimensions;
|
final AppDimensions dimensions;
|
||||||
final PageSelectorProvider pageSelector;
|
final MethodMap availableTypes;
|
||||||
final PaymentFlowProvider flowProvider;
|
final PaymentFlowProvider flowProvider;
|
||||||
final Recipient? recipient;
|
final Recipient? recipient;
|
||||||
|
|
||||||
const PaymentInfoSection({
|
const PaymentInfoSection({
|
||||||
super.key,
|
super.key,
|
||||||
required this.dimensions,
|
required this.dimensions,
|
||||||
required this.pageSelector,
|
required this.availableTypes,
|
||||||
required this.flowProvider,
|
required this.flowProvider,
|
||||||
required this.recipient,
|
required this.recipient,
|
||||||
});
|
});
|
||||||
@@ -32,11 +31,11 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final hasRecipient = recipient != null;
|
final hasRecipient = recipient != null;
|
||||||
final MethodMap availableTypes = hasRecipient
|
final MethodMap resolvedAvailableTypes = hasRecipient
|
||||||
? pageSelector.getAvailablePaymentTypes()
|
? availableTypes
|
||||||
: {for (final type in PaymentType.values) type: null};
|
: {for (final type in PaymentType.values) type: null};
|
||||||
|
|
||||||
if (hasRecipient && availableTypes.isEmpty) {
|
if (hasRecipient && resolvedAvailableTypes.isEmpty) {
|
||||||
return Text(loc.recipientNoPaymentDetails);
|
return Text(loc.recipientNoPaymentDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
SectionTitle(loc.paymentInfo),
|
SectionTitle(loc.paymentInfo),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
PaymentTypeSelector(
|
PaymentTypeSelector(
|
||||||
availableTypes: availableTypes,
|
availableTypes: resolvedAvailableTypes,
|
||||||
selectedType: selectedType,
|
selectedType: selectedType,
|
||||||
onSelected: (type) => flowProvider.selectType(
|
onSelected: (type) => flowProvider.selectType(
|
||||||
type,
|
type,
|
||||||
@@ -63,7 +62,7 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
flowProvider.setManualPaymentData(data);
|
flowProvider.setManualPaymentData(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initialData: hasRecipient ? availableTypes[selectedType] : flowProvider.manualPaymentData,
|
initialData: hasRecipient ? resolvedAvailableTypes[selectedType] : flowProvider.manualPaymentData,
|
||||||
isEditable: !hasRecipient,
|
isEditable: !hasRecipient,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PaymentMethodsLoadingView extends StatelessWidget {
|
||||||
|
const PaymentMethodsLoadingView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaymentMethodsErrorView extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const PaymentMethodsErrorView({super.key, required this.message});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(child: Text(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -20,12 +23,14 @@ class SendPayoutButton extends StatelessWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final pageSelectorProvider = context.read<PageSelectorProvider>();
|
|
||||||
final walletsProvider = context.read<WalletsProvider>();
|
final walletsProvider = context.read<WalletsProvider>();
|
||||||
final wallet = walletsProvider.selectedWallet;
|
final wallet = walletsProvider.selectedWallet;
|
||||||
|
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
pageSelectorProvider.startPaymentFromWallet(context, wallet);
|
context.pushToPayment(
|
||||||
|
paymentType: PaymentType.wallet,
|
||||||
|
returnTo: PayoutDestination.editwallet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(loc.payoutNavSendPayout),
|
child: Text(loc.payoutNavSendPayout),
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/app/router/payout_routes.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class TopUpButton extends StatelessWidget{
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.read<PageSelectorProvider>().openWalletTopUp(context, wallet);
|
context.pushToWalletTopUp(returnTo: PayoutDestination.editwallet);
|
||||||
},
|
},
|
||||||
child: Text(loc.topUpBalance),
|
child: Text(loc.topUpBalance),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/models/payment/methods/type.dart';
|
|
||||||
import 'package:pshared/models/payment/type.dart';
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
|
||||||
import 'package:pweb/providers/wallets.dart';
|
|
||||||
//import 'package:pweb/services/amplitude.dart';
|
|
||||||
import 'package:pweb/app/router/payout_routes.dart';
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class PageSelectorProvider extends ChangeNotifier {
|
|
||||||
PayoutDestination _selected = PayoutDestination.dashboard;
|
|
||||||
PaymentType? _type;
|
|
||||||
bool _cameFromRecipientList = false;
|
|
||||||
PayoutDestination? _previousDestination;
|
|
||||||
|
|
||||||
late RecipientsProvider recipientProvider;
|
|
||||||
late WalletsProvider walletsProvider;
|
|
||||||
late PaymentMethodsProvider methodsProvider;
|
|
||||||
|
|
||||||
PayoutDestination get selected => _selected;
|
|
||||||
PaymentType? get type => _type;
|
|
||||||
bool get cameFromRecipientList => _cameFromRecipientList;
|
|
||||||
|
|
||||||
PageSelectorProvider();
|
|
||||||
|
|
||||||
void update(
|
|
||||||
RecipientsProvider recipientProv,
|
|
||||||
WalletsProvider walletsProv,
|
|
||||||
PaymentMethodsProvider methodsProv,
|
|
||||||
) {
|
|
||||||
recipientProvider = recipientProv;
|
|
||||||
walletsProvider = walletsProv;
|
|
||||||
methodsProvider = methodsProv;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncDestination(PayoutDestination destination) {
|
|
||||||
if (_selected == destination) return;
|
|
||||||
_selected = destination;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectPage(
|
|
||||||
BuildContext context,
|
|
||||||
PayoutDestination dest, {
|
|
||||||
bool replace = true,
|
|
||||||
}) {
|
|
||||||
_selected = dest;
|
|
||||||
notifyListeners();
|
|
||||||
_navigateTo(context, dest, replace: replace);
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectRecipient(
|
|
||||||
BuildContext context,
|
|
||||||
Recipient? recipient, {
|
|
||||||
bool fromList = false,
|
|
||||||
}) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
recipientProvider.setCurrentObject(recipient?.id);
|
|
||||||
_cameFromRecipientList = fromList;
|
|
||||||
_setPreviousDestination();
|
|
||||||
_selected = PayoutDestination.payment;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.payment) {
|
|
||||||
_navigateTo(context, PayoutDestination.payment, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void editRecipient(
|
|
||||||
BuildContext context,
|
|
||||||
Recipient? recipient, {
|
|
||||||
bool fromList = false,
|
|
||||||
}) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
recipientProvider.setCurrentObject(recipient?.id);
|
|
||||||
_cameFromRecipientList = fromList;
|
|
||||||
_selected = PayoutDestination.addrecipient;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.addrecipient) {
|
|
||||||
_navigateTo(context, PayoutDestination.addrecipient, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void goToAddRecipient(BuildContext context) {
|
|
||||||
// AmplitudeService.recipientAddStarted();
|
|
||||||
final previousDestination = _selected;
|
|
||||||
recipientProvider.setCurrentObject(null);
|
|
||||||
_selected = PayoutDestination.addrecipient;
|
|
||||||
_cameFromRecipientList = false;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.addrecipient) {
|
|
||||||
_navigateTo(context, PayoutDestination.addrecipient, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void startPaymentWithoutRecipient(
|
|
||||||
BuildContext context,
|
|
||||||
PaymentType type,
|
|
||||||
) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
recipientProvider.setCurrentObject(null);
|
|
||||||
_type = type;
|
|
||||||
_cameFromRecipientList = false;
|
|
||||||
_setPreviousDestination();
|
|
||||||
_selected = PayoutDestination.payment;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.payment) {
|
|
||||||
_navigateTo(context, PayoutDestination.payment, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBackFromPayment(BuildContext context) {
|
|
||||||
if (Navigator.of(context).canPop()) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else {
|
|
||||||
_navigateTo(
|
|
||||||
context,
|
|
||||||
_previousDestination ??
|
|
||||||
(_cameFromRecipientList
|
|
||||||
? PayoutDestination.recipients
|
|
||||||
: PayoutDestination.dashboard),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_selected = _previousDestination ??
|
|
||||||
(_cameFromRecipientList
|
|
||||||
? PayoutDestination.recipients
|
|
||||||
: PayoutDestination.dashboard);
|
|
||||||
_type = null;
|
|
||||||
_previousDestination = null;
|
|
||||||
_cameFromRecipientList = false;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBackFromWalletEdit(BuildContext context) {
|
|
||||||
selectPage(context, PayoutDestination.methods);
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectWallet(BuildContext context, Wallet wallet) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
walletsProvider.selectWallet(wallet);
|
|
||||||
_selected = PayoutDestination.editwallet;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.editwallet) {
|
|
||||||
_navigateTo(context, PayoutDestination.editwallet, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void startPaymentFromWallet(BuildContext context, Wallet wallet) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
_type = PaymentType.wallet;
|
|
||||||
_cameFromRecipientList = false;
|
|
||||||
_setPreviousDestination();
|
|
||||||
_selected = PayoutDestination.payment;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.payment) {
|
|
||||||
_navigateTo(context, PayoutDestination.payment, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void openWalletTopUp(BuildContext context, Wallet wallet) {
|
|
||||||
final previousDestination = _selected;
|
|
||||||
_setPreviousDestination();
|
|
||||||
walletsProvider.selectWallet(wallet);
|
|
||||||
_selected = PayoutDestination.walletTopUp;
|
|
||||||
notifyListeners();
|
|
||||||
if (previousDestination != PayoutDestination.walletTopUp) {
|
|
||||||
_navigateTo(context, PayoutDestination.walletTopUp, replace: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void goBackFromWalletTopUp(BuildContext context) {
|
|
||||||
if (Navigator.of(context).canPop()) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
} else {
|
|
||||||
_navigateTo(
|
|
||||||
context,
|
|
||||||
_previousDestination ?? PayoutDestination.dashboard,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_selected = _previousDestination ?? PayoutDestination.dashboard;
|
|
||||||
_previousDestination = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentMethod? getPaymentMethodForWallet(Wallet wallet) {
|
|
||||||
if (methodsProvider.methods.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return methodsProvider.methods.firstWhereOrNull(
|
|
||||||
(method) => method.type == PaymentType.wallet && (method.description?.contains(wallet.walletUserID) ?? false),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodMap getAvailablePaymentTypes() {
|
|
||||||
final recipient = selectedRecipient;
|
|
||||||
if ((recipient == null) || !methodsProvider.isReady) return {};
|
|
||||||
|
|
||||||
final methodsForRecipient = methodsProvider.methods.where(
|
|
||||||
(method) => !method.isArchived && method.recipientRef == recipient.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
for (final method in methodsForRecipient) method.type: method.data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentType getDefaultPaymentType() {
|
|
||||||
final availableTypes = getAvailablePaymentTypes();
|
|
||||||
final currentType = _type ?? PaymentType.bankAccount;
|
|
||||||
|
|
||||||
if (availableTypes.containsKey(currentType)) {
|
|
||||||
return currentType;
|
|
||||||
}
|
|
||||||
if (availableTypes.isNotEmpty) {
|
|
||||||
return availableTypes.keys.first;
|
|
||||||
}
|
|
||||||
return PaymentType.bankAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool shouldShowPaymentForm() {
|
|
||||||
return selectedRecipient == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleWalletAutoSelection() {
|
|
||||||
if (selectedWallet != null) {
|
|
||||||
final wallet = selectedWallet!;
|
|
||||||
final matchingMethod = getPaymentMethodForWallet(wallet);
|
|
||||||
if (matchingMethod != null) {
|
|
||||||
methodsProvider.setCurrentObject(matchingMethod.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setPreviousDestination() {
|
|
||||||
if (_selected != PayoutDestination.payment &&
|
|
||||||
_selected != PayoutDestination.walletTopUp) {
|
|
||||||
_previousDestination = _selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _navigateTo(
|
|
||||||
BuildContext context,
|
|
||||||
PayoutDestination destination, {
|
|
||||||
bool replace = true,
|
|
||||||
}) {
|
|
||||||
if (replace) {
|
|
||||||
context.goToPayout(destination);
|
|
||||||
} else {
|
|
||||||
context.pushToPayout(destination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Recipient? get selectedRecipient => recipientProvider.currentObject;
|
|
||||||
Wallet? get selectedWallet => walletsProvider.selectedWallet;
|
|
||||||
}
|
|
||||||
110
frontend/pweb/lib/providers/payment_flow.dart
Normal file
110
frontend/pweb/lib/providers/payment_flow.dart
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentFlowProvider extends ChangeNotifier {
|
||||||
|
PaymentType _selectedType;
|
||||||
|
PaymentMethodData? _manualPaymentData;
|
||||||
|
|
||||||
|
PaymentFlowProvider({
|
||||||
|
required PaymentType initialType,
|
||||||
|
}) : _selectedType = initialType;
|
||||||
|
|
||||||
|
PaymentType get selectedType => _selectedType;
|
||||||
|
PaymentMethodData? get manualPaymentData => _manualPaymentData;
|
||||||
|
|
||||||
|
void sync({
|
||||||
|
required Recipient? recipient,
|
||||||
|
required MethodMap availableTypes,
|
||||||
|
PaymentType? preferredType,
|
||||||
|
}) {
|
||||||
|
final resolvedType = _resolveSelectedType(
|
||||||
|
recipient: recipient,
|
||||||
|
availableTypes: availableTypes,
|
||||||
|
preferredType: preferredType,
|
||||||
|
);
|
||||||
|
|
||||||
|
var hasChanges = false;
|
||||||
|
if (resolvedType != _selectedType) {
|
||||||
|
_selectedType = resolvedType;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient != null && _manualPaymentData != null) {
|
||||||
|
_manualPaymentData = null;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset({
|
||||||
|
required Recipient? recipient,
|
||||||
|
required MethodMap availableTypes,
|
||||||
|
PaymentType? preferredType,
|
||||||
|
}) {
|
||||||
|
final resolvedType = _resolveSelectedType(
|
||||||
|
recipient: recipient,
|
||||||
|
availableTypes: availableTypes,
|
||||||
|
preferredType: preferredType,
|
||||||
|
);
|
||||||
|
|
||||||
|
var hasChanges = false;
|
||||||
|
|
||||||
|
if (resolvedType != _selectedType) {
|
||||||
|
_selectedType = resolvedType;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_manualPaymentData != null) {
|
||||||
|
_manualPaymentData = null;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges) notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectType(PaymentType type, {bool resetManualData = false}) {
|
||||||
|
if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedType = type;
|
||||||
|
if (resetManualData) {
|
||||||
|
_manualPaymentData = null;
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setManualPaymentData(PaymentMethodData? data) {
|
||||||
|
_manualPaymentData = data;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
PaymentType _resolveSelectedType({
|
||||||
|
required Recipient? recipient,
|
||||||
|
required MethodMap availableTypes,
|
||||||
|
PaymentType? preferredType,
|
||||||
|
}) {
|
||||||
|
if (recipient == null) {
|
||||||
|
return preferredType ?? _selectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableTypes.isEmpty) {
|
||||||
|
return preferredType ?? PaymentType.bankAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableTypes.keys.contains(_selectedType)) {
|
||||||
|
return _selectedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredType != null && availableTypes.keys.contains(preferredType)) {
|
||||||
|
return preferredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableTypes.keys.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
|
||||||
|
|
||||||
import 'package:pshared/models/payment/type.dart';
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentFlowProvider extends ChangeNotifier {
|
|
||||||
PaymentType _selectedType;
|
|
||||||
PaymentMethodData? _manualPaymentData;
|
|
||||||
|
|
||||||
PaymentFlowProvider({
|
|
||||||
required PaymentType initialType,
|
|
||||||
}) : _selectedType = initialType;
|
|
||||||
|
|
||||||
PaymentType get selectedType => _selectedType;
|
|
||||||
PaymentMethodData? get manualPaymentData => _manualPaymentData;
|
|
||||||
|
|
||||||
void syncWithSelector(PageSelectorProvider selector) {
|
|
||||||
final recipient = selector.selectedRecipient;
|
|
||||||
final resolvedType = _resolveSelectedType(selector, recipient);
|
|
||||||
|
|
||||||
var hasChanges = false;
|
|
||||||
if (resolvedType != _selectedType) {
|
|
||||||
_selectedType = resolvedType;
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient != null && _manualPaymentData != null) {
|
|
||||||
_manualPaymentData = null;
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasChanges) notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset(PageSelectorProvider selector) {
|
|
||||||
_selectedType = selector.getDefaultPaymentType();
|
|
||||||
_manualPaymentData = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectType(PaymentType type, {bool resetManualData = false}) {
|
|
||||||
if (_selectedType == type && (!resetManualData || _manualPaymentData == null)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedType = type;
|
|
||||||
if (resetManualData) {
|
|
||||||
_manualPaymentData = null;
|
|
||||||
}
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setManualPaymentData(PaymentMethodData? data) {
|
|
||||||
_manualPaymentData = data;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentType _resolveSelectedType(
|
|
||||||
PageSelectorProvider selector,
|
|
||||||
Recipient? recipient,
|
|
||||||
) {
|
|
||||||
final available = selector.getAvailablePaymentTypes();
|
|
||||||
final current = _selectedType;
|
|
||||||
|
|
||||||
if (recipient == null) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (available.keys.contains(current)) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
return selector.getDefaultPaymentType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import 'package:pshared/models/resources.dart';
|
|||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/loader.dart';
|
import 'package:pweb/pages/loader.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
|
||||||
import 'package:pweb/utils/logout.dart';
|
import 'package:pweb/utils/logout.dart';
|
||||||
import 'package:pweb/widgets/appbar/app_bar.dart';
|
import 'package:pweb/widgets/appbar/app_bar.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
@@ -32,9 +31,10 @@ class PageSelector extends StatelessWidget {
|
|||||||
final permissions = context.read<PermissionsProvider>();
|
final permissions = context.read<PermissionsProvider>();
|
||||||
if (!permissions.isReady) return Center(child: CircularProgressIndicator());
|
if (!permissions.isReady) return Center(child: CircularProgressIndicator());
|
||||||
|
|
||||||
final provider = context.watch<PageSelectorProvider>();
|
|
||||||
|
|
||||||
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
|
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
|
||||||
|
final fallbackDestination = restrictedAccess
|
||||||
|
? PayoutDestination.settings
|
||||||
|
: PayoutDestination.dashboard;
|
||||||
final allowedDestinations = restrictedAccess
|
final allowedDestinations = restrictedAccess
|
||||||
? <PayoutDestination>{
|
? <PayoutDestination>{
|
||||||
PayoutDestination.settings,
|
PayoutDestination.settings,
|
||||||
@@ -44,10 +44,10 @@ class PageSelector extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
: PayoutDestination.values.toSet();
|
: PayoutDestination.values.toSet();
|
||||||
|
|
||||||
final routeDestination = _destinationFromState(routerState) ?? provider.selected;
|
final routeDestination = _destinationFromState(routerState);
|
||||||
final selected = allowedDestinations.contains(routeDestination)
|
final selected = routeDestination != null && allowedDestinations.contains(routeDestination)
|
||||||
? routeDestination
|
? routeDestination
|
||||||
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard);
|
: fallbackDestination;
|
||||||
|
|
||||||
if (selected != routeDestination) {
|
if (selected != routeDestination) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -55,10 +55,6 @@ class PageSelector extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.selected != selected) {
|
|
||||||
provider.syncDestination(selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PayoutAppBar(
|
appBar: PayoutAppBar(
|
||||||
title: Text(selected.localizedLabel(context)),
|
title: Text(selected.localizedLabel(context)),
|
||||||
|
|||||||
Reference in New Issue
Block a user