Compare commits
486 Commits
lash/initi
...
sync-flags
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd8631eb05
|
||
|
|
c51fd89ad5
|
||
| bcccd79e70 | |||
|
|
193c9c119a
|
||
|
|
36a3c59287
|
||
|
|
1234723e10
|
||
|
|
61f48abb7d
|
||
|
|
3e40a09d39
|
||
|
|
7e90124199
|
||
|
|
e27905765c
|
||
|
|
4ce9baa379
|
||
|
|
e47415cc22
|
||
|
|
1f4d810e14
|
||
| 35d9d13442 | |||
|
|
72d038c076
|
||
|
|
058d802c0f
|
||
|
|
da64c3b80e | ||
| 823133aa37 | |||
|
|
44673ef61b
|
||
|
|
559b67a3e6
|
||
|
|
b1eb460988
|
||
|
|
619edc39f8
|
||
|
|
efa29b087e
|
||
|
|
e9988242b1
|
||
|
|
55afbedd1e
|
||
|
|
a3ab91f6a3
|
||
|
|
92a122732d
|
||
|
|
682785fc3f
|
||
|
|
ebb4581c27
|
||
|
|
cd1936c12f
|
||
|
|
16dd0f6ebf
|
||
|
|
a3dae12c7a
|
||
|
|
57426b3565
|
||
| b42dec8373 | |||
|
|
2807f039a7 | ||
| 0e6334058d | |||
|
|
ba2cb4b813
|
||
|
|
8416d4fddb
|
||
|
|
a33ff7ffda
|
||
|
|
7d1951ec7a
|
||
|
|
2ad5c2e8df
|
||
|
|
e1219354bb
|
||
|
|
b31a68ad8e
|
||
|
|
ea3a6d8382
|
||
|
|
0fcadd4634 | ||
|
|
9234bfd579
|
||
|
|
706b6fe629
|
||
|
|
a543569236
|
||
| dd56d52f4c | |||
|
|
3592e7747c
|
||
|
|
7fe40faa9d
|
||
|
|
02fd02dc21
|
||
|
|
b884b82197
|
||
|
|
280c382a3d
|
||
|
|
b497dde1e8
|
||
|
|
cedd55fd31
|
||
|
|
88926480dc
|
||
|
|
dabdf7eba2
|
||
|
|
96f6ca7d08
|
||
|
|
bc48dddd99
|
||
|
|
2ec612978d
|
||
|
|
632f891e16
|
||
|
|
d758b37065
|
||
|
|
929fb6a40d
|
||
|
|
5263af46d8
|
||
|
|
383ef51134
|
||
|
|
5479580230
|
||
|
|
6f2d965b89
|
||
|
|
150dc99201
|
||
|
|
b85c5a8788
|
||
|
|
23f1068402
|
||
|
|
e803048b13
|
||
|
|
137a5dd599 | ||
| 61ff337464 | |||
|
|
41c3b6d8e6
|
||
|
|
42dadb5eea | ||
|
|
7228b818e3 | ||
|
|
3b44b4b9e7
|
||
|
|
7c2454422e
|
||
|
|
95de1dad2b
|
||
|
|
fac703576f
|
||
|
|
df8e0b4d4a
|
||
|
|
b4bacfdc03
|
||
|
|
8dc203d111
|
||
|
|
dcadea8737
|
||
|
|
71496d0015
|
||
|
|
d9fc5440c5
|
||
|
|
0495472530
|
||
|
|
fd43060021
|
||
|
|
cf6e105fb9
|
||
|
|
91b57fece2
|
||
|
|
043c79384c
|
||
|
|
1cc1d00ffe
|
||
|
|
d7987bf460
|
||
|
|
21da4b976b
|
||
|
|
169d85e2cb
|
||
|
|
3ab76b3d22
|
||
|
|
b428e2e22d
|
||
| 1d23a0cc4b | |||
|
|
d3ad3b2db6
|
||
|
|
5d8fee470a
|
||
|
|
cf237c592a
|
||
|
|
2d8333b89a
|
||
|
|
c8146ea211 | ||
|
|
d03c0c4c0e | ||
|
|
e914d059e2
|
||
|
|
7b1676bb37 | ||
|
|
db5d55d8e3 | ||
|
|
06cab56427
|
||
|
|
d0c181bca2
|
||
| e379129b3d | |||
|
|
29d35d1ec4
|
||
|
|
687447e73e
|
||
|
|
ec1ddefeb3
|
||
|
|
4166c9aed5 | ||
|
|
78e82edfb5
|
||
|
|
a5c4866b23
|
||
|
|
b52239e6be
|
||
|
|
5f02df64d7
|
||
|
|
0ba65fc48c
|
||
|
|
ce8cc8e0fd
|
||
|
|
1499e4a29a
|
||
|
|
5f24ff3b1b
|
||
|
|
6cdee7b58b
|
||
|
|
af5537abfc
|
||
|
|
a6e569afb1
|
||
|
|
446514d8ce
|
||
|
|
131d106f38
|
||
|
|
4d62f0222e
|
||
|
|
37cc3e1bc1
|
||
|
|
491e97d8af
|
||
|
|
d4fefe3c39
|
||
|
|
e8ff468c7b
|
||
|
|
965f343230
|
||
|
|
3426d31a9e
|
||
|
|
8b2b667307
|
||
|
|
c206fa1329
|
||
|
|
ad395c4b1a
|
||
|
|
af7e6cc603
|
||
|
|
f90a188acc
|
||
|
|
7a542d1f1c
|
||
|
|
7d800d68a0
|
||
|
|
850458b5d8
|
||
|
|
ef8e3d5c35
|
||
|
|
982eebf374
|
||
|
|
228d936a0e
|
||
|
|
7c4d37b5b8
|
||
|
|
e6e8bb6671
|
||
|
|
6385735b89
|
||
|
|
70815aabd1
|
||
|
|
919899c704
|
||
|
|
9a2ad99d07
|
||
|
|
2f4959e191
|
||
|
|
1b2f3bb046
|
||
|
|
19a2b28367 | ||
|
|
25d124c58d
|
||
|
|
5b34ef28eb
|
||
|
|
0370a3def4
|
||
|
|
7c0651d218
|
||
|
|
a38ac06a3d
|
||
|
|
ac2a3721b2
|
||
|
|
7ea3cb6b51
|
||
|
|
0274133d48
|
||
|
|
5ff2e794ff
|
||
|
|
08deeca500
|
||
|
|
cea920c1f5
|
||
| ade236747c | |||
|
|
e1a3729e3b
|
||
|
|
f127fd7c0f
|
||
|
|
42177aadeb
|
||
| fa9f1a72ef | |||
|
|
4ffb32f14a | ||
| c41277db87 | |||
|
|
56cda36aa7
|
||
|
|
c9170ca45a | ||
|
|
57a1645c03
|
||
|
|
7d9c3b66a9
|
||
|
|
aec1f4c4c1
|
||
|
|
d680387ef1
|
||
|
|
0337c66f96
|
||
|
|
6070792fe4
|
||
|
|
69ae494b2c
|
||
|
|
b89abf3487
|
||
|
|
4ef8c47f8b
|
||
|
|
1076a9578e
|
||
|
|
a27d44e561
|
||
|
|
35a2732fe2
|
||
|
|
834f2ce629
|
||
|
|
26353bdf6e
|
||
|
|
1f6bf2bbed
|
||
|
|
0c1d9ab582
|
||
|
|
29d94bb2e5
|
||
|
|
067c496244
|
||
|
|
f8c258a3b4
|
||
|
|
aec96ce9ba
|
||
|
|
71ef950fff
|
||
|
|
477f3a307a
|
||
|
|
33c376c971
|
||
|
|
79bf09f3d1
|
||
|
|
f4804546d9
|
||
|
|
3fff03a164
|
||
|
|
094866c129
|
||
|
|
514f6ae05b
|
||
|
|
8a6659a98b
|
||
|
|
d094af9c51
|
||
|
|
f96f9c11e6
|
||
|
|
1d8b0ef951
|
||
|
|
fb2dc003b8
|
||
|
|
a11ca2a618
|
||
|
4268cc0589
|
|||
|
|
ae0672c7da
|
||
|
|
2c671bc0d4
|
||
|
|
c5673b339b
|
||
|
|
58242c8d55
|
||
|
|
cab90ed89a
|
||
|
|
73c3486400
|
||
|
|
3c84fb5ae7
|
||
| c3567313af | |||
| f82b413e34 | |||
|
|
695bfed349
|
||
|
|
8bf48cb081
|
||
|
|
04880b58a8
|
||
|
|
0458ac9498
|
||
|
|
a7e8c184f5
|
||
|
|
3615348efd
|
||
|
|
d0be79d817
|
||
|
|
ea0fc4491d
|
||
|
|
7883063e53
|
||
|
|
f562ce8adf
|
||
|
|
cd2f4328a8
|
||
|
|
95b9a6e486
|
||
|
|
9674a04cbc
|
||
|
|
6c46c097fb
|
||
|
|
c814c4ae5c
|
||
|
|
bbecec0310
|
||
|
|
eaa89c29df
|
||
|
|
2634790118 | ||
|
|
272a7ac05f
|
||
|
|
39c0d31995
|
||
|
|
18acb53ead
|
||
|
|
43d91f84f6
|
||
|
|
e832f46d22
|
||
|
|
039117f40e
|
||
|
|
3532f72fbd
|
||
|
|
04c7e20457
|
||
|
|
0914217769
|
||
|
|
ea117b7222
|
||
|
|
39e1c84a45
|
||
|
|
20ee4dfb24
|
||
|
0791eb1f11
|
|||
|
|
8ebd2bbbfa
|
||
|
|
06c534cbb1
|
||
|
|
b8d53f82bb
|
||
|
|
71c01e00cd
|
||
|
|
419716a2b4
|
||
|
|
54b2088842
|
||
|
|
b2208359d6 | ||
|
|
0828b1eb10 | ||
|
|
c22f9edeca | ||
|
|
c81b17994a
|
||
|
|
64b42b92ec
|
||
|
|
493f64157b
|
||
|
|
f6c9d54a65
|
||
|
|
74c82de472
|
||
|
|
ddb8c6e748
|
||
|
|
87ebd0029b
|
||
|
|
b941cf2562
|
||
|
|
1c8bda5ded
|
||
|
|
30cb800450
|
||
|
|
680a1e9681
|
||
|
|
b327b569fb
|
||
|
|
445ca0d0ff
|
||
|
|
e681c9cfca
|
||
|
|
d504571014
|
||
|
|
3b16e25ebd
|
||
|
|
5f37856927
|
||
|
|
e564f1328b
|
||
|
374f79388c
|
|||
|
|
30644d3535 | ||
|
|
e5e857e3cb
|
||
|
|
1af826e87f
|
||
|
|
38ef3b623e
|
||
|
|
f7e7f7bd1c
|
||
|
|
bd0e7822ed
|
||
|
|
4dba5f2ab5
|
||
| 144398e18b | |||
|
|
e97d4be296
|
||
|
|
c2a0b05c84
|
||
|
|
20c8af582c
|
||
|
|
101afd5ebd
|
||
|
|
830d867ac8
|
||
|
|
0cbe391209
|
||
|
|
226d1f5697
|
||
|
|
3e422f269f
|
||
|
|
7a6e5b094f
|
||
|
|
5ed63cec39
|
||
|
|
8f7d5ff66d
|
||
|
|
f922fb6d43
|
||
|
|
78af4d225f
|
||
|
|
b887c67e5d
|
||
|
|
0816190469
|
||
|
|
28e734e0ba
|
||
|
|
d92dc026f5
|
||
|
|
939df35c8c
|
||
|
|
c09ce32010
|
||
|
|
1f0e7c016e
|
||
|
|
a984c9dd06
|
||
|
|
e2d5546de1 | ||
|
|
95089875bf
|
||
|
|
4db25055ad
|
||
|
|
e8e6f0e371
|
||
|
|
91c4967efa
|
||
|
|
7b1824f18c
|
||
|
|
04c3f5ce65
|
||
|
|
e646658f40
|
||
|
|
c4cab444ad
|
||
|
|
b5ade9112e
|
||
|
|
3b9184e852
|
||
|
|
07b85768d1 | ||
|
|
c9678df152
|
||
|
|
c37fee5e54
|
||
|
|
98b2a31655
|
||
| d4fcf40b8d | |||
|
|
83a10efcd9
|
||
| 0089d6f125 | |||
| a7d13dc7c4 | |||
| bed7f58e81 | |||
|
|
07fd1110ed
|
||
|
|
2f1dbb9147
|
||
|
|
93f1897321
|
||
|
|
c324e29aea
|
||
|
|
f91056cca2 | ||
|
|
7d11377ffd
|
||
|
|
9a63234470
|
||
|
|
fe74dbb848
|
||
|
|
278edc7049
|
||
|
|
3b03d40279
|
||
|
|
4d46133194
|
||
| fd84f6ae98 | |||
|
|
1e8d5b1b83
|
||
|
|
628a57ea10
|
||
|
|
a37908323f
|
||
|
|
9c27be3a9d
|
||
|
|
1190c5b6f2 | ||
| b95452ae5f | |||
|
|
14df16098c
|
||
|
|
1cdd5a37ae | ||
|
|
6b3b8ffabe
|
||
|
|
6647416115
|
||
|
|
4155b267ee
|
||
| 0f2d6def23 | |||
|
|
c13768d782
|
||
|
|
af4b075df3
|
||
|
|
1d7027905d
|
||
|
|
6c77d04284 | ||
|
|
12acd508b1
|
||
|
|
f88e253486
|
||
|
|
68597ea7cc
|
||
|
|
3d9eeddab8
|
||
|
|
df58f69032
|
||
|
|
8b999a09a2
|
||
|
|
fdde1bb979
|
||
|
|
23cadc6178
|
||
|
|
0821241427
|
||
|
|
ca71062528
|
||
| 1918ea37d5 | |||
|
|
422b651097
|
||
|
|
861d04dbfd
|
||
|
|
c78081fb84
|
||
|
|
ff3c597158
|
||
|
|
09c5f3a14c
|
||
|
|
f7873bfef7
|
||
|
|
815e3b2a25
|
||
|
|
f9a9a9b4a6
|
||
|
|
04429ab74c
|
||
|
|
d3f1a14e71
|
||
|
|
5722d4f8dd
|
||
|
|
cad18c9e64 | ||
|
|
fbff584bb4
|
||
|
|
4169419442
|
||
|
|
bef62b97e7
|
||
|
|
56d0baad6e
|
||
|
|
5a586eb67a
|
||
|
|
6a945f8f20
|
||
|
|
39f8c86e8b
|
||
|
|
73b501c8aa
|
||
|
|
a6cd4b5fca
|
||
|
|
d47bc6c241 | ||
|
|
dd55906e70
|
||
|
|
6fdf3735d0
|
||
|
|
b492421851
|
||
|
|
128c0162d2
|
||
|
|
ebe94c705f
|
||
|
|
df0c1b3429
|
||
|
|
d66fd894bc
|
||
|
|
28185fc2c5
|
||
| d2f75c693a | |||
|
|
363c35efed
|
||
|
|
64a87231ca
|
||
|
|
0f361a065b | ||
|
|
4d1045a9f9
|
||
|
|
f3a6485bb3
|
||
|
|
cd37adc288
|
||
|
|
6f0994d692
|
||
|
|
fbaba15776
|
||
|
|
9a094f440f
|
||
|
|
6c77ff58ab | ||
|
|
c8ddafcf66
|
||
|
|
5f1a659789
|
||
|
|
0d4be0f7fc
|
||
|
|
b6161da749
|
||
|
f1c4d5f1f9
|
|||
|
73b876dbdd
|
|||
|
|
18e865ba26
|
||
|
|
d7c909f028
|
||
|
|
908a52cda6
|
||
|
|
0e480e3d55
|
||
|
|
ffea1a0b96
|
||
|
|
be5bd16616
|
||
|
|
93723616f6
|
||
|
|
c9257ba0d6
|
||
|
|
caabf4f8af
|
||
|
|
f884b19012
|
||
|
|
526dc822fb | ||
|
|
1249e45d54
|
||
|
|
99d03e3bc9
|
||
|
|
1e495763f5
|
||
|
|
5e83c89e03
|
||
|
|
1909b60bf4
|
||
|
|
49335a290f
|
||
|
|
2c8e60e1bb
|
||
|
|
4baacb325d | ||
|
|
c243edd4c4
|
||
| 44846c5950 | |||
|
|
592c57b72d
|
||
|
|
341f0b9cc8 | ||
|
|
1c911ee73f | ||
|
|
00b9079a55
|
||
|
|
ec6b078de3
|
||
|
|
0b4fde9d1e
|
||
|
|
66be350ad1
|
||
|
|
156511e499
|
||
|
|
8b68130ed4
|
||
|
|
7ab8eef623 | ||
| 7d513ac2a7 | |||
|
|
fbee26da08
|
||
|
|
4fb3474b51
|
||
|
|
b31011b390
|
||
|
|
358567e52e | ||
| 9c05e0b4a2 | |||
| 57fb1d0ef3 | |||
|
|
2dcec2e9fb
|
||
|
|
286a72f12e
|
||
|
|
933943636e
|
||
|
|
1e69f2167a
|
||
|
|
4e584cea92
|
||
|
|
20feef4103
|
||
|
|
8d5d3f1dc8
|
||
|
|
0f54ab4b4a
|
||
|
|
be47c999ca
|
||
|
|
c719db8b92
|
||
|
|
16cb2846b7
|
||
|
|
bc2abc2464
|
||
|
|
f435046385
|
||
|
|
efaf0ab22e
|
||
|
|
e29bcb5b27
|
||
|
|
cdb22f8cbb
|
||
|
|
9cb9e02442 | ||
|
|
6aa4bd8906
|
||
|
|
ab03e6d529
|
||
|
|
9b66146ef1
|
||
|
|
825c06c3bb
|
||
| 71ff251d51 | |||
|
|
4cc0de59a7
|
||
|
|
4b8505680b
|
||
|
|
104db94826
|
||
|
|
ecee5b4dee
|
||
|
|
879baa03fc
|
||
|
|
ecac4ae009 | ||
|
|
6b404f56c9
|
||
| ca7dc6779a | |||
|
|
471bbea5ca
|
||
|
|
78fae3526b
|
||
|
|
e410158432
|
||
|
|
ff06b92ffd
|
||
| 0a7152d6b8 |
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
!/args
|
||||||
|
!/cmd/africastalking
|
||||||
|
!/cmd/ssh
|
||||||
|
!/config
|
||||||
|
!/debug
|
||||||
|
!/handlers
|
||||||
|
!/internal
|
||||||
|
!/profile
|
||||||
|
!/services
|
||||||
|
!/ssh
|
||||||
|
!/store
|
||||||
|
!/LICENSE
|
||||||
|
!/README.md
|
||||||
|
!/go.*
|
||||||
|
!/.env.example
|
||||||
11
.env.example
11
.env.example
@@ -18,3 +18,14 @@ DATA_URL_BASE=http://localhost:5006
|
|||||||
#Language
|
#Language
|
||||||
DEFAULT_LANGUAGE=eng
|
DEFAULT_LANGUAGE=eng
|
||||||
LANGUAGES=eng, swa
|
LANGUAGES=eng, swa
|
||||||
|
|
||||||
|
#Alias search domains
|
||||||
|
ALIAS_SEARCH_DOMAINS=sarafu.local, sarafu.eth
|
||||||
|
|
||||||
|
#Pool swap
|
||||||
|
DEFAULT_POOL_NAME="Kenya ROLA Pool"
|
||||||
|
DEFAULT_POOL_SYMBOL=ROLA
|
||||||
|
DEFAULT_POOL_CONTRACT_ADDRESS=0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e
|
||||||
|
DEFAULT_LIMITER_ADDRESS=
|
||||||
|
DEFAULT_VOUCHER_REGISTRY=
|
||||||
|
INCLUDE_STABLES_PARAM=false
|
||||||
|
|||||||
56
.github/workflows/docker.yaml
vendored
Normal file
56
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Check out repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
|
|
||||||
|
- name: Login to GHCR Docker registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set outputs
|
||||||
|
run: |
|
||||||
|
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
|
||||||
|
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
BUILD=${{ env.RELEASE_SHORT_COMMIT }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
tags: |
|
||||||
|
ghcr.io/grassrootseconomics/sarafu-vise:latest
|
||||||
|
ghcr.io/grassrootseconomics/sarafu-vise:${{ env.RELEASE_TAG }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ cmd/.state/
|
|||||||
id_*
|
id_*
|
||||||
*.gdbm
|
*.gdbm
|
||||||
*.log
|
*.log
|
||||||
|
user-data
|
||||||
|
|||||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
FROM golang:1.23.4-bookworm AS build
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
|
||||||
|
ARG BUILDPLATFORM
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG BUILD=dev
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libgdbm-dev \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN git clone https://git.defalsify.org/vise.git go-vise
|
||||||
|
COPY . ./sarafu-vise
|
||||||
|
|
||||||
|
WORKDIR /build/sarafu-vise/services/registration
|
||||||
|
RUN echo "Compiling go-vise files"
|
||||||
|
RUN make VISE_PATH=/build/go-vise -B
|
||||||
|
|
||||||
|
WORKDIR /build/sarafu-vise
|
||||||
|
RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM"
|
||||||
|
RUN go mod download
|
||||||
|
RUN go build -tags logdebug,online -o sarafu-at -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libgdbm-dev \
|
||||||
|
ca-certificates \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /service
|
||||||
|
|
||||||
|
COPY --from=build /build/sarafu-vise/sarafu-at .
|
||||||
|
COPY --from=build /build/sarafu-vise/LICENSE .
|
||||||
|
COPY --from=build /build/sarafu-vise/README.md .
|
||||||
|
COPY --from=build /build/sarafu-vise/services ./services
|
||||||
|
COPY --from=build /build/sarafu-vise/.env.example .
|
||||||
|
RUN mv .env.example .env
|
||||||
|
|
||||||
|
EXPOSE 7123
|
||||||
|
|
||||||
|
CMD ["./sarafu-at"]
|
||||||
10
args/lang.go
10
args/lang.go
@@ -10,7 +10,7 @@ type LangVar struct {
|
|||||||
v []lang.Language
|
v []lang.Language
|
||||||
}
|
}
|
||||||
|
|
||||||
func(lv *LangVar) Set(s string) error {
|
func (lv *LangVar) Set(s string) error {
|
||||||
v, err := lang.LanguageFromCode(s)
|
v, err := lang.LanguageFromCode(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -19,16 +19,14 @@ func(lv *LangVar) Set(s string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func(lv *LangVar) String() string {
|
func (lv *LangVar) String() string {
|
||||||
var s []string
|
var s []string
|
||||||
for _, v := range(lv.v) {
|
for _, v := range lv.v {
|
||||||
s = append(s, v.Code)
|
s = append(s, v.Code)
|
||||||
}
|
}
|
||||||
return strings.Join(s, ",")
|
return strings.Join(s, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
func(lv *LangVar) Langs() []lang.Language {
|
func (lv *LangVar) Langs() []lang.Language {
|
||||||
return lv.v
|
return lv.v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,18 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.defalsify.org/vise.git/lang"
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
at "git.grassecon.net/grassrootseconomics/visedriver-africastalking/africastalking"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
|
||||||
|
|
||||||
httpremote "git.grassecon.net/grassrootseconomics/sarafu-api/remote/http"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||||
|
at "git.grassecon.net/grassrootseconomics/visedriver-africastalking/africastalking"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -37,8 +36,7 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var resourceDir string
|
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var host string
|
var host string
|
||||||
@@ -46,28 +44,29 @@ func main() {
|
|||||||
var err error
|
var err error
|
||||||
var gettextDir string
|
var gettextDir string
|
||||||
var langs args.LangVar
|
var langs args.LangVar
|
||||||
|
var logDbConnStr string
|
||||||
|
|
||||||
|
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||||
|
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr == "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logg.Infof("start command", "build", build, "conn", connData, "resourcedir", resourceDir, "outputsize", size)
|
logg.Infof("start command", "build", build, "conn", conns, "outputsize", size)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ln, err := lang.LanguageFromCode(config.Language())
|
ln, err := lang.LanguageFromCode(config.Language())
|
||||||
@@ -84,13 +83,14 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(128),
|
FlagCount: uint32(128),
|
||||||
MenuSeparator: menuSeparator,
|
MenuSeparator: menuSeparator,
|
||||||
|
ResetOnEmptyInput: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
rs, err := menuStorageService.GetResource(ctx)
|
rs, err := menuStorageService.GetResource(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "menustorageservice: %v\n", err)
|
fmt.Fprintf(os.Stderr, "menustorageservice: %v\n", err)
|
||||||
@@ -102,7 +102,11 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "userdatadb: %v\n", err)
|
fmt.Fprintf(os.Stderr, "userdatadb: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer userdataStore.Close()
|
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
dbResource, ok := rs.(*resource.DbResource)
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -116,12 +120,14 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
lhs.SetDataStore(&userdataStore)
|
lhs.SetDataStore(&userdataStore)
|
||||||
|
lhs.SetLogDb(&logdb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "setdatastore: %v\n", err)
|
fmt.Fprintf(os.Stderr, "setdatastore: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountService := &httpremote.HTTPAccountService{}
|
accountService := services.New(ctx, menuStorageService)
|
||||||
|
|
||||||
hl, err := lhs.GetHandler(accountService)
|
hl, err := lhs.GetHandler(accountService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "httpaccountservice: %v\n", err)
|
fmt.Fprintf(os.Stderr, "httpaccountservice: %v\n", err)
|
||||||
@@ -133,10 +139,10 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "getstatestore: %v\n", err)
|
fmt.Fprintf(os.Stderr, "getstatestore: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer stateStore.Close()
|
|
||||||
|
|
||||||
rp := &at.ATRequestParser{}
|
rp := &at.ATRequestParser{}
|
||||||
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||||
|
bsh = bsh.WithEngineFunc(lhs.GetEngine)
|
||||||
sh := at.NewATRequestHandler(bsh)
|
sh := at.NewATRequestHandler(bsh)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@@ -146,7 +152,10 @@ func main() {
|
|||||||
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
s.RegisterOnShutdown(sh.Shutdown)
|
shutdownFunc := func() {
|
||||||
|
sh.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
s.RegisterOnShutdown(shutdownFunc)
|
||||||
|
|
||||||
cint := make(chan os.Signal)
|
cint := make(chan os.Signal)
|
||||||
cterm := make(chan os.Signal)
|
cterm := make(chan os.Signal)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
@@ -14,12 +17,12 @@ import (
|
|||||||
"git.defalsify.org/vise.git/logging"
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
|
||||||
httpremote "git.grassecon.net/grassrootseconomics/sarafu-api/remote/http"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -44,9 +47,8 @@ func (p *asyncRequestParser) GetInput(r any) ([]byte, error) {
|
|||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var sessionId string
|
var sessionId string
|
||||||
var resourceDir string
|
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var host string
|
var host string
|
||||||
@@ -54,28 +56,31 @@ func main() {
|
|||||||
var err error
|
var err error
|
||||||
var gettextDir string
|
var gettextDir string
|
||||||
var langs args.LangVar
|
var langs args.LangVar
|
||||||
|
var logDbConnStr string
|
||||||
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||||
|
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr == "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId)
|
logg.Infof("start command", "conn", conns, "outputsize", size, "sessionId", sessionId)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -93,13 +98,14 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(128),
|
FlagCount: uint32(128),
|
||||||
MenuSeparator: menuSeparator,
|
MenuSeparator: menuSeparator,
|
||||||
|
ResetOnEmptyInput: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -116,7 +122,13 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer userdataStore.Close()
|
|
||||||
|
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
//defer userdataStore.Close(ctx)
|
||||||
|
|
||||||
dbResource, ok := rs.(*resource.DbResource)
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -125,8 +137,9 @@ func main() {
|
|||||||
|
|
||||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||||
lhs.SetDataStore(&userdataStore)
|
lhs.SetDataStore(&userdataStore)
|
||||||
accountService := &httpremote.HTTPAccountService{}
|
lhs.SetLogDb(&logdb)
|
||||||
|
|
||||||
|
accountService := services.New(ctx, menuStorageService)
|
||||||
hl, err := lhs.GetHandler(accountService)
|
hl, err := lhs.GetHandler(accountService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
@@ -138,12 +151,14 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer stateStore.Close()
|
//defer stateStore.Close(ctx)
|
||||||
|
|
||||||
rp := &asyncRequestParser{
|
rp := &asyncRequestParser{
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
}
|
}
|
||||||
sh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
sh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||||
|
sh = sh.WithEngineFunc(lhs.GetEngine)
|
||||||
|
|
||||||
cfg.SessionId = sessionId
|
cfg.SessionId = sessionId
|
||||||
rqs := request.RequestSession{
|
rqs := request.RequestSession{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
@@ -160,7 +175,7 @@ func main() {
|
|||||||
case _ = <-cint:
|
case _ = <-cint:
|
||||||
case _ = <-cterm:
|
case _ = <-cterm:
|
||||||
}
|
}
|
||||||
sh.Shutdown()
|
sh.Shutdown(ctx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
@@ -176,18 +191,26 @@ func main() {
|
|||||||
fmt.Errorf("error in output: %v", err)
|
fmt.Errorf("error in output: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
rqs, err = sh.Reset(rqs)
|
rqs, err = sh.Reset(ctx, rqs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logg.ErrorCtxf(ctx, "error in reset: %v", "err", err)
|
logg.ErrorCtxf(ctx, "error in reset: %v", "err", err)
|
||||||
fmt.Errorf("error in reset: %v", err)
|
fmt.Errorf("error in reset: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
_, err = fmt.Scanln(&rqs.Input)
|
in := bufio.NewReader(os.Stdin)
|
||||||
|
s, err := in.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
logg.DebugCtxf(ctx, "have EOF, bailing")
|
||||||
|
break
|
||||||
|
}
|
||||||
logg.ErrorCtxf(ctx, "error in input", "err", err)
|
logg.ErrorCtxf(ctx, "error in input", "err", err)
|
||||||
fmt.Errorf("error in input: %v", err)
|
fmt.Errorf("error in input: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
rqs.Input = []byte{}
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
rqs.Input = []byte(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ import (
|
|||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
httprequest "git.grassecon.net/grassrootseconomics/visedriver/request/http"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||||
|
httprequest "git.grassecon.net/grassrootseconomics/visedriver/request/http"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
|
||||||
httpremote "git.grassecon.net/grassrootseconomics/sarafu-api/remote/http"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -35,8 +35,7 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var resourceDir string
|
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var host string
|
var host string
|
||||||
@@ -44,27 +43,30 @@ func main() {
|
|||||||
var err error
|
var err error
|
||||||
var gettextDir string
|
var gettextDir string
|
||||||
var langs args.LangVar
|
var langs args.LangVar
|
||||||
|
var logDbConnStr string
|
||||||
|
|
||||||
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
|
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", config.Host(), "http host")
|
flag.StringVar(&host, "h", config.Host(), "http host")
|
||||||
flag.UintVar(&port, "p", config.Port(), "http port")
|
flag.UintVar(&port, "p", config.Port(), "http port")
|
||||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||||
|
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr == "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData, "resourcedir", resourceDir, "outputsize", size)
|
logg.Infof("start command", "conn", conns, "outputsize", size)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@@ -82,13 +84,14 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(128),
|
FlagCount: uint32(128),
|
||||||
MenuSeparator: menuSeparator,
|
MenuSeparator: menuSeparator,
|
||||||
|
ResetOnEmptyInput: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
|
|
||||||
rs, err := menuStorageService.GetResource(ctx)
|
rs, err := menuStorageService.GetResource(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,7 +104,12 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer userdataStore.Close()
|
|
||||||
|
logdb, err := menuStorageService.GetLogDb(ctx, userdataStore, logDbConnStr, "user-data")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
dbResource, ok := rs.(*resource.DbResource)
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -110,13 +118,15 @@ func main() {
|
|||||||
|
|
||||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||||
lhs.SetDataStore(&userdataStore)
|
lhs.SetDataStore(&userdataStore)
|
||||||
|
lhs.SetLogDb(&logdb)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountService := &httpremote.HTTPAccountService{}
|
accountService := services.New(ctx, menuStorageService)
|
||||||
|
|
||||||
hl, err := lhs.GetHandler(accountService)
|
hl, err := lhs.GetHandler(accountService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
@@ -128,16 +138,21 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer stateStore.Close()
|
|
||||||
|
//accountService := services.New(ctx, menuStorageService, connData)
|
||||||
|
|
||||||
rp := &httprequest.DefaultRequestParser{}
|
rp := &httprequest.DefaultRequestParser{}
|
||||||
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl)
|
||||||
|
bsh = bsh.WithEngineFunc(lhs.GetEngine)
|
||||||
sh := httprequest.NewHTTPRequestHandler(bsh)
|
sh := httprequest.NewHTTPRequestHandler(bsh)
|
||||||
s := &http.Server{
|
s := &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
|
||||||
Handler: sh,
|
Handler: sh,
|
||||||
}
|
}
|
||||||
s.RegisterOnShutdown(sh.Shutdown)
|
shutdownFunc := func() {
|
||||||
|
sh.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
s.RegisterOnShutdown(shutdownFunc)
|
||||||
|
|
||||||
cint := make(chan os.Signal)
|
cint := make(chan os.Signal)
|
||||||
cterm := make(chan os.Signal)
|
cterm := make(chan os.Signal)
|
||||||
|
|||||||
79
cmd/main.go
79
cmd/main.go
@@ -5,17 +5,19 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
"git.defalsify.org/vise.git/logging"
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
"git.defalsify.org/vise.git/lang"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/args"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/services"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -27,41 +29,41 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var size uint
|
var size uint
|
||||||
var sessionId string
|
var sessionId string
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var resourceDir string
|
|
||||||
var err error
|
var err error
|
||||||
var gettextDir string
|
var gettextDir string
|
||||||
var langs args.LangVar
|
var langs args.LangVar
|
||||||
|
var logDbConnStr string
|
||||||
|
|
||||||
flag.StringVar(&resourceDir, "resourcedir", scriptDir, "resource dir")
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory")
|
||||||
flag.Var(&langs, "language", "add symbol resolution for language")
|
flag.Var(&langs, "language", "add symbol resolution for language")
|
||||||
|
flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr == "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v\n", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData, "outputsize", size)
|
logg.Infof("start command", "conn", conns, "outputsize", size)
|
||||||
|
|
||||||
if len(langs.Langs()) == 0 {
|
if len(langs.Langs()) == 0 {
|
||||||
langs.Set(config.Language())
|
langs.Set(config.Language())
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
|
||||||
|
|
||||||
ln, err := lang.LanguageFromCode(config.Language())
|
ln, err := lang.LanguageFromCode(config.Language())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,19 +75,21 @@ func main() {
|
|||||||
pfp := path.Join(scriptDir, "pp.csv")
|
pfp := path.Join(scriptDir, "pp.csv")
|
||||||
|
|
||||||
cfg := engine.Config{
|
cfg := engine.Config{
|
||||||
Root: "root",
|
Root: "root",
|
||||||
SessionId: sessionId,
|
SessionId: sessionId,
|
||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(128),
|
FlagCount: uint32(128),
|
||||||
MenuSeparator: menuSeparator,
|
MenuSeparator: menuSeparator,
|
||||||
|
EngineDebug: engineDebug,
|
||||||
|
ResetOnEmptyInput: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "menu storage service error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "menu storage service error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if gettextDir != "" {
|
if gettextDir != "" {
|
||||||
menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs())
|
menuStorageService = menuStorageService.WithGettext(gettextDir, langs.Langs())
|
||||||
}
|
}
|
||||||
@@ -108,6 +112,12 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logdb, err := menuStorageService.GetLogDb(ctx, userdatastore, logDbConnStr, "user-data")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "get log db error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
dbResource, ok := rs.(*resource.DbResource)
|
dbResource, ok := rs.(*resource.DbResource)
|
||||||
if !ok {
|
if !ok {
|
||||||
fmt.Fprintf(os.Stderr, "get dbresource error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "get dbresource error: %v\n", err)
|
||||||
@@ -116,24 +126,35 @@ func main() {
|
|||||||
|
|
||||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||||
lhs.SetDataStore(&userdatastore)
|
lhs.SetDataStore(&userdatastore)
|
||||||
|
lhs.SetLogDb(&logdb)
|
||||||
lhs.SetPersister(pe)
|
lhs.SetPersister(pe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "localhandler service error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "localhandler service error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountService := services.New(ctx, menuStorageService, connData)
|
accountService := services.New(ctx, menuStorageService)
|
||||||
hl, err := lhs.GetHandler(accountService)
|
_, err = lhs.GetHandler(accountService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "get accounts service handler: %v\n", err)
|
fmt.Fprintf(os.Stderr, "get accounts service handler: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
en := lhs.GetEngine(cfg, rs, pe)
|
||||||
|
|
||||||
en := lhs.GetEngine()
|
cint := make(chan os.Signal)
|
||||||
en = en.WithFirst(hl.Init)
|
cterm := make(chan os.Signal)
|
||||||
if engineDebug {
|
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
|
||||||
en = en.WithDebug(nil)
|
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
|
||||||
}
|
go func() {
|
||||||
|
var s os.Signal
|
||||||
|
select {
|
||||||
|
case s = <-cterm:
|
||||||
|
case s = <-cint:
|
||||||
|
}
|
||||||
|
logg.InfoCtxf(ctx, "stopping on signal", "sig", s)
|
||||||
|
en.Finish(ctx)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
|
err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -31,34 +31,31 @@ var (
|
|||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var authConnStr string
|
var authConnStr string
|
||||||
var resourceDir string
|
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var stateDebug bool
|
var stateDebug bool
|
||||||
var host string
|
var host string
|
||||||
var port uint
|
var port uint
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
|
||||||
flag.StringVar(&authConnStr, "authdb", "", "auth connection string")
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource connection string")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", config.HostSSH(), "socket host")
|
flag.StringVar(&host, "h", config.HostSSH(), "socket host")
|
||||||
flag.UintVar(&port, "p", config.PortSSH(), "socket port")
|
flag.UintVar(&port, "p", config.PortSSH(), "socket port")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr == "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
if authConnStr == "" {
|
|
||||||
authConnStr = connStr
|
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
authConnData, err := storage.ToConnData(authConnStr)
|
authConnData, err := storage.ToConnData(authConnStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "auth connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "auth connstr err: %v", err)
|
||||||
@@ -79,14 +76,15 @@ func main() {
|
|||||||
logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!")
|
logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!")
|
||||||
logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)")
|
logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)")
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData, "authconn", authConnData, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port)
|
logg.Infof("start command", "conn", conns, "authconn", authConnData, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port)
|
||||||
|
|
||||||
pfp := path.Join(scriptDir, "pp.csv")
|
pfp := path.Join(scriptDir, "pp.csv")
|
||||||
|
|
||||||
cfg := engine.Config{
|
cfg := engine.Config{
|
||||||
Root: "root",
|
Root: "root",
|
||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(16),
|
FlagCount: uint32(128),
|
||||||
|
ResetOnEmptyInput: true,
|
||||||
}
|
}
|
||||||
if stateDebug {
|
if stateDebug {
|
||||||
cfg.StateDebug = true
|
cfg.StateDebug = true
|
||||||
@@ -102,7 +100,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
logg.TraceCtxf(ctx, "shutdown auth key store reached")
|
logg.TraceCtxf(ctx, "shutdown auth key store reached")
|
||||||
err = authKeyStore.Close()
|
err = authKeyStore.Close(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logg.ErrorCtxf(ctx, "keystore close error", "err", err)
|
logg.ErrorCtxf(ctx, "keystore close error", "err", err)
|
||||||
}
|
}
|
||||||
@@ -114,14 +112,13 @@ func main() {
|
|||||||
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
runner := &ssh.SshRunner{
|
runner := &ssh.SshRunner{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
Debug: engineDebug,
|
Debug: engineDebug,
|
||||||
FlagFile: pfp,
|
FlagFile: pfp,
|
||||||
Conn: connData,
|
Conn: conns,
|
||||||
ResourceDir: resourceDir,
|
SrvKeyFile: sshKeyFile,
|
||||||
SrvKeyFile: sshKeyFile,
|
Host: host,
|
||||||
Host: host,
|
Port: port,
|
||||||
Port: port,
|
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer store.Close()
|
defer store.Close(ctx)
|
||||||
|
|
||||||
err = store.AddFromFile(ctx, sshKeyFile, sessionId)
|
err = store.AddFromFile(ctx, sshKeyFile, sessionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
20
config/args.go
Normal file
20
config/args.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewOverride() *viseconfig.Override {
|
||||||
|
o := &viseconfig.Override{
|
||||||
|
StateConnMode: storage.DBMODE_TEXT,
|
||||||
|
ResourceConnMode: storage.DBMODE_TEXT,
|
||||||
|
UserConnMode: storage.DBMODE_BINARY,
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func Apply(o *viseconfig.Override) error {
|
||||||
|
viseconfig.ApplyConn(o)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,24 +1,36 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/env"
|
"strings"
|
||||||
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
|
||||||
apiconfig "git.grassecon.net/grassrootseconomics/sarafu-api/config"
|
apiconfig "git.grassecon.net/grassrootseconomics/sarafu-api/config"
|
||||||
|
viseconfig "git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
var (
|
||||||
env.LoadEnvVariables()
|
GetConns = viseconfig.GetConns
|
||||||
|
EnvPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadEnv() {
|
||||||
|
if EnvPath == "" {
|
||||||
|
env.LoadEnvVariables()
|
||||||
|
} else {
|
||||||
|
env.LoadEnvVariablesPath(EnvPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultSSHHost string = "127.0.0.1"
|
defaultSSHHost string = "127.0.0.1"
|
||||||
defaultSSHPort uint = 7122
|
defaultSSHPort uint = 7122
|
||||||
defaultHTTPHost string = "127.0.0.1"
|
defaultHTTPHost string = "127.0.0.1"
|
||||||
defaultHTTPPort uint = 7123
|
defaultHTTPPort uint = 7123
|
||||||
|
defaultDomain = "sarafu.local"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func LoadConfig() error {
|
func LoadConfig() error {
|
||||||
|
loadEnv()
|
||||||
err := viseconfig.LoadConfig()
|
err := viseconfig.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -30,8 +42,14 @@ func LoadConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DbConn() string {
|
func SearchDomains() []string {
|
||||||
return viseconfig.DbConn
|
var ParsedDomains []string
|
||||||
|
SearchDomains := env.GetEnv("ALIAS_SEARCH_DOMAINS", defaultDomain)
|
||||||
|
SearchDomainList := strings.Split(env.GetEnv("ALIAS_SEARCH_DOMAINS", SearchDomains), ",")
|
||||||
|
for _, domain := range SearchDomainList {
|
||||||
|
ParsedDomains = append(ParsedDomains, strings.ReplaceAll(domain, " ", ""))
|
||||||
|
}
|
||||||
|
return ParsedDomains
|
||||||
}
|
}
|
||||||
|
|
||||||
func Language() string {
|
func Language() string {
|
||||||
@@ -57,3 +75,15 @@ func PortSSH() uint {
|
|||||||
func ATEndpoint() string {
|
func ATEndpoint() string {
|
||||||
return env.GetEnv("AT_ENDPOINT", "/")
|
return env.GetEnv("AT_ENDPOINT", "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultPoolAddress() string {
|
||||||
|
return env.GetEnv("DEFAULT_POOL_CONTRACT_ADDRESS", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultPoolName() string {
|
||||||
|
return env.GetEnv("DEFAULT_POOL_NAME", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultPoolSymbol() string {
|
||||||
|
return env.GetEnv("DEFAULT_POOL_SYMBOL", "")
|
||||||
|
}
|
||||||
|
|||||||
41
debug/db.go
41
debug/db.go
@@ -1,11 +1,11 @@
|
|||||||
package debug
|
package debug
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
|
||||||
visedb "git.defalsify.org/vise.git/db"
|
visedb "git.defalsify.org/vise.git/db"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -13,10 +13,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type KeyInfo struct {
|
type KeyInfo struct {
|
||||||
SessionId string
|
SessionId string
|
||||||
Typ uint8
|
Typ uint8
|
||||||
SubTyp storedb.DataTyp
|
SubTyp storedb.DataTyp
|
||||||
Label string
|
Label string
|
||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,18 +32,10 @@ func (k KeyInfo) String() string {
|
|||||||
|
|
||||||
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
||||||
o := KeyInfo{}
|
o := KeyInfo{}
|
||||||
b := []byte(sessionId)
|
|
||||||
|
|
||||||
if len(k) <= len(b) {
|
|
||||||
return o, fmt.Errorf("storage key missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.SessionId = sessionId
|
o.SessionId = sessionId
|
||||||
|
|
||||||
o.Typ = uint8(k[0])
|
o.Typ = uint8(k[0])
|
||||||
k = k[1:]
|
k = k[1:]
|
||||||
o.SessionId = string(k[:len(b)])
|
|
||||||
k = k[len(b):]
|
|
||||||
|
|
||||||
if o.Typ == visedb.DATATYPE_USERDATA {
|
if o.Typ == visedb.DATATYPE_USERDATA {
|
||||||
if len(k) == 0 {
|
if len(k) == 0 {
|
||||||
@@ -53,30 +45,19 @@ func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
|||||||
o.SubTyp = storedb.DataTyp(v)
|
o.SubTyp = storedb.DataTyp(v)
|
||||||
o.Label = subTypToString(o.SubTyp)
|
o.Label = subTypToString(o.SubTyp)
|
||||||
k = k[2:]
|
k = k[2:]
|
||||||
|
if len(k) != 0 {
|
||||||
|
return o, fmt.Errorf("excess key information: %x", k)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
o.Label = typToString(o.Typ)
|
o.Label = typToString(o.Typ)
|
||||||
}
|
k = k[2:]
|
||||||
|
|
||||||
if len(k) != 0 {
|
|
||||||
return o, fmt.Errorf("excess key information")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromKey(k []byte) (KeyInfo, error) {
|
|
||||||
o := KeyInfo{}
|
|
||||||
|
|
||||||
if len(k) < 4 {
|
|
||||||
return o, fmt.Errorf("insufficient key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionIdBytes := k[1:len(k)-2]
|
|
||||||
return ToKeyInfo(k, string(sessionIdBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func subTypToString(v storedb.DataTyp) string {
|
func subTypToString(v storedb.DataTyp) string {
|
||||||
return dbTypStr[v + visedb.DATATYPE_USERDATA + 1]
|
return dbTypStr[v+visedb.DATATYPE_USERDATA+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func typToString(v uint8) string {
|
func typToString(v uint8) string {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//go:build debugdb
|
||||||
// +build debugdb
|
// +build debugdb
|
||||||
|
|
||||||
package debug
|
package debug
|
||||||
@@ -11,34 +12,37 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
DebugCap |= 1
|
DebugCap |= 1
|
||||||
dbTypStr[db.DATATYPE_STATE] = "internal state"
|
dbTypStr[db.DATATYPE_STATE] = "internal state"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TRACKING_ID] = "tracking id"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TRACKING_ID] = "tracking id"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_PUBLIC_KEY] = "public key"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_PUBLIC_KEY] = "public key"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACCOUNT_PIN] = "account pin"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACCOUNT_PIN] = "account pin"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_FIRST_NAME] = "first name"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_FIRST_NAME] = "first name"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_FAMILY_NAME] = "family name"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_FAMILY_NAME] = "family name"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_YOB] = "year of birth"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_YOB] = "year of birth"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_LOCATION] = "location"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_LOCATION] = "location"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_GENDER] = "gender"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_GENDER] = "gender"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_OFFERINGS] = "offerings"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_OFFERINGS] = "offerings"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_RECIPIENT] = "recipient"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_RECIPIENT] = "recipient"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_AMOUNT] = "amount"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_AMOUNT] = "amount"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TEMPORARY_VALUE] = "temporary value"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TEMPORARY_VALUE] = "temporary value"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_SYM] = "active sym"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_SYM] = "active sym"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_BAL] = "active bal"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_BAL] = "active bal"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_BLOCKED_NUMBER] = "blocked number"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_BLOCKED_NUMBER] = "blocked number"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_DECIMAL] = "active decimal"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_DECIMAL] = "active decimal"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_ACTIVE_ADDRESS] = "active address"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_ACTIVE_ADDRESS] = "active address"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_INCORRECT_PIN_ATTEMPTS] = "incorrect pin attempts"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_BALANCES] = "voucher balances"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_SELECTED_LANGUAGE_CODE] = "selected language"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_INITIAL_LANGUAGE_CODE] = "initial language"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_SENDERS] = "tx senders"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_BALANCES] = "voucher balances"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_RECIPIENTS] = "tx recipients"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_VALUES] = "tx values"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_ADDRESSES] = "tx addresses"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_SENDERS] = "tx senders"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_HASHES] = "tx hashes"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_RECIPIENTS] = "tx recipients"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_DATES] = "tx dates"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_VALUES] = "tx values"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_SYMBOLS] = "tx symbols"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_ADDRESSES] = "tx addresses"
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + storedb.DATA_TX_DECIMALS] = "tx decimals"
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_HASHES] = "tx hashes"
|
||||||
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_DATES] = "tx dates"
|
||||||
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_SYMBOLS] = "tx symbols"
|
||||||
|
dbTypStr[db.DATATYPE_USERDATA+1+storedb.DATA_TX_DECIMALS] = "tx decimals"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package debug
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
|
||||||
visedb "git.defalsify.org/vise.git/db"
|
visedb "git.defalsify.org/vise.git/db"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDebugDbSubKeyInfo(t *testing.T) {
|
func TestDebugDbSubKeyInfo(t *testing.T) {
|
||||||
s := "foo"
|
s := "foo"
|
||||||
b := []byte{0x20}
|
b := []byte{0x20}
|
||||||
b = append(b, []byte(s)...)
|
|
||||||
b = append(b, []byte{0x00, 0x02}...)
|
b = append(b, []byte{0x00, 0x02}...)
|
||||||
r, err := ToKeyInfo(b, s)
|
r, err := ToKeyInfo(b, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -25,7 +24,7 @@ func TestDebugDbSubKeyInfo(t *testing.T) {
|
|||||||
if r.SubTyp != 2 {
|
if r.SubTyp != 2 {
|
||||||
t.Fatalf("expected 2, got %d", r.SubTyp)
|
t.Fatalf("expected 2, got %d", r.SubTyp)
|
||||||
}
|
}
|
||||||
if DebugCap & 1 > 0 {
|
if DebugCap&1 > 0 {
|
||||||
if r.Label != "tracking id" {
|
if r.Label != "tracking id" {
|
||||||
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
|
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
|
||||||
}
|
}
|
||||||
@@ -46,7 +45,7 @@ func TestDebugDbKeyInfo(t *testing.T) {
|
|||||||
if r.Typ != 16 {
|
if r.Typ != 16 {
|
||||||
t.Fatalf("expected 16, got %d", r.Typ)
|
t.Fatalf("expected 16, got %d", r.Typ)
|
||||||
}
|
}
|
||||||
if DebugCap & 1 > 0 {
|
if DebugCap&1 > 0 {
|
||||||
if r.Label != "internal state" {
|
if r.Label != "internal state" {
|
||||||
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
|
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
|
||||||
}
|
}
|
||||||
@@ -56,7 +55,6 @@ func TestDebugDbKeyInfo(t *testing.T) {
|
|||||||
func TestDebugDbKeyInfoRestore(t *testing.T) {
|
func TestDebugDbKeyInfoRestore(t *testing.T) {
|
||||||
s := "bar"
|
s := "bar"
|
||||||
b := []byte{visedb.DATATYPE_USERDATA}
|
b := []byte{visedb.DATATYPE_USERDATA}
|
||||||
b = append(b, []byte(s)...)
|
|
||||||
k := storedb.ToBytes(storedb.DATA_ACTIVE_SYM)
|
k := storedb.ToBytes(storedb.DATA_ACTIVE_SYM)
|
||||||
b = append(b, k...)
|
b = append(b, k...)
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ func TestDebugDbKeyInfoRestore(t *testing.T) {
|
|||||||
if r.Typ != 32 {
|
if r.Typ != 32 {
|
||||||
t.Fatalf("expected 32, got %d", r.Typ)
|
t.Fatalf("expected 32, got %d", r.Typ)
|
||||||
}
|
}
|
||||||
if DebugCap & 1 > 0 {
|
if DebugCap&1 > 0 {
|
||||||
if r.Label != "active sym" {
|
if r.Label != "active sym" {
|
||||||
t.Fatalf("expected 'active sym', got '%s'", r.Label)
|
t.Fatalf("expected 'active sym', got '%s'", r.Label)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"admins": [
|
|
||||||
{
|
|
||||||
"phonenumber" : "<replace with any admin number to test with >"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla().WithDomain("adminstore")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Admin struct {
|
|
||||||
PhoneNumber string `json:"phonenumber"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Admins []Admin `json:"admins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Seed(ctx context.Context) error {
|
|
||||||
var config Config
|
|
||||||
adminstore, err := store.NewAdminStore(ctx, "../admin_numbers")
|
|
||||||
store := adminstore.FsStore
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer store.Close()
|
|
||||||
data, err := os.ReadFile("admin_numbers.json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, admin := range config.Admins {
|
|
||||||
err := store.Put(ctx, []byte(admin.PhoneNumber), []byte("1"))
|
|
||||||
if err != nil {
|
|
||||||
logg.Printf(logging.LVL_DEBUG, "Failed to insert admin number", admin.PhoneNumber)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,73 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/devtools/admin/commands"
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/internal/cmd"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla().WithContextKey("SessionId")
|
||||||
|
scriptDir = path.Join("services", "registration")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := context.Background()
|
config.LoadConfig()
|
||||||
err := commands.Seed(ctx)
|
|
||||||
|
override := config.NewOverride()
|
||||||
|
var sessionId string
|
||||||
|
|
||||||
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
config.Apply(override)
|
||||||
|
conns, err := config.GetConns()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize a list of admins with error %s", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
pfp := path.Join(scriptDir, "pp.csv")
|
||||||
|
flagParser, err := application.NewFlagManager(pfp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "flagparser fail: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
x := cmd.NewCmd(sessionId, flagParser)
|
||||||
|
err = x.Parse(flag.Args())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "cmd parse fail: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.Infof("start command", "conn", conns, "subcmd", x)
|
||||||
|
|
||||||
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "menu storage service error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = x.Exec(ctx, menuStorageService)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "cmd exec error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,17 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.defalsify.org/vise.git/lang"
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
||||||
changeHeadSrc = `LOAD reset_account_authorized 0
|
changeHeadSrc = `LOAD reset_account_authorized 0
|
||||||
LOAD reset_incorrect 0
|
LOAD reset_incorrect_pin 0
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
CATCH pin_entry flag_account_authorized 0
|
CATCH pin_entry flag_account_authorized 0
|
||||||
`
|
`
|
||||||
|
|
||||||
selectSrc = `LOAD set_language 6
|
selectSrc = `LOAD set_language 6
|
||||||
RELOAD set_language
|
RELOAD set_language
|
||||||
@@ -29,8 +28,8 @@ MOVE language_changed
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logg = logging.NewVanilla()
|
logg = logging.NewVanilla()
|
||||||
mouts string
|
mouts string
|
||||||
incmps string
|
incmps string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
logg.Tracef("using languages", "lang", config.Languages)
|
logg.Tracef("using languages", "lang", config.Languages)
|
||||||
|
|
||||||
for i, v := range(config.Languages) {
|
for i, v := range config.Languages {
|
||||||
ln, err := lang.LanguageFromCode(v)
|
ln, err := lang.LanguageFromCode(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error parsing language: %s\n", v)
|
fmt.Fprintf(os.Stderr, "error parsing language: %s\n", v)
|
||||||
@@ -76,7 +75,7 @@ func main() {
|
|||||||
incmps += fmt.Sprintf("INCMP %s %v\n", v, n)
|
incmps += fmt.Sprintf("INCMP %s %v\n", v, n)
|
||||||
|
|
||||||
p := path.Join(srcDir, v)
|
p := path.Join(srcDir, v)
|
||||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
w, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed open language set template output: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed open language set template output: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -93,7 +92,7 @@ func main() {
|
|||||||
src += "INCMP . *\n"
|
src += "INCMP . *\n"
|
||||||
|
|
||||||
p := path.Join(srcDir, "select_language.vis")
|
p := path.Join(srcDir, "select_language.vis")
|
||||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
w, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -107,7 +106,7 @@ func main() {
|
|||||||
|
|
||||||
src = changeHeadSrc + src
|
src = changeHeadSrc + src
|
||||||
p = path.Join(srcDir, "change_language.vis")
|
p = path.Join(srcDir, "change_language.vis")
|
||||||
w, err = os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
w, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/debug"
|
|
||||||
"git.defalsify.org/vise.git/db"
|
"git.defalsify.org/vise.git/db"
|
||||||
"git.defalsify.org/vise.git/logging"
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/debug"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -19,55 +19,58 @@ var (
|
|||||||
scriptDir = path.Join("services", "registration")
|
scriptDir = path.Join("services", "registration")
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatItem(k []byte, v []byte) (string, error) {
|
func formatItem(k []byte, v []byte, sessionId string) (string, error) {
|
||||||
o, err := debug.FromKey(k)
|
o, err := debug.ToKeyInfo(k, sessionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
s := fmt.Sprintf("%vValue: %v\n\n", o, string(v))
|
s := fmt.Sprintf("%v\t%v\n", o.Label, string(v))
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
var connStr string
|
override := config.NewOverride()
|
||||||
var sessionId string
|
var sessionId string
|
||||||
var database string
|
var database string
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
var err error
|
var err error
|
||||||
|
var first bool
|
||||||
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
flag.StringVar(&connStr, "c", ".state", "connection string")
|
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
|
||||||
|
flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory")
|
||||||
|
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
|
||||||
|
flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if connStr != "" {
|
config.Apply(override)
|
||||||
connStr = config.DbConn()
|
conns, err := config.GetConns()
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData)
|
logg.Infof("start command", "conn", conns)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
ctx = context.WithValue(ctx, "Database", database)
|
ctx = context.WithValue(ctx, "Database", database)
|
||||||
|
|
||||||
resourceDir := scriptDir
|
menuStorageService := storage.NewMenuStorageService(conns)
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
|
||||||
|
|
||||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
store, err := menuStorageService.GetUserdataDb(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "get userdata db: %v\n", err.Error())
|
fmt.Fprintf(os.Stderr, "get userdata db: %v\n", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
store.SetSession(sessionId)
|
||||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
store.SetPrefix(db.DATATYPE_USERDATA)
|
||||||
|
|
||||||
d, err := store.Dump(ctx, []byte(sessionId))
|
d, err := store.Dump(ctx, []byte(""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "store dump fail: %v\n", err.Error())
|
fmt.Fprintf(os.Stderr, "store dump fail: %v\n", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -78,15 +81,19 @@ func main() {
|
|||||||
if k == nil {
|
if k == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
r, err := formatItem(k, v)
|
if !first {
|
||||||
|
fmt.Printf("Session ID: %s\n---\n", sessionId)
|
||||||
|
first = true
|
||||||
|
}
|
||||||
|
r, err := formatItem(append([]byte{db.DATATYPE_USERDATA}, k...), v, sessionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "format db item error: %v", err)
|
fmt.Fprintf(os.Stderr, "format db item error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf(r)
|
fmt.Printf(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = store.Close()
|
err = store.Close(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
testdataloader "github.com/peteole/testdata-loader"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla()
|
|
||||||
baseDir = testdataloader.GetBasePath()
|
|
||||||
scriptDir = path.Join("services", "registration")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config.LoadConfig()
|
|
||||||
|
|
||||||
var connStr string
|
|
||||||
var sessionId string
|
|
||||||
var database string
|
|
||||||
var engineDebug bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if connStr != "" {
|
|
||||||
connStr = config.DbConn()
|
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(connStr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
|
||||||
ctx = context.WithValue(ctx, "Database", database)
|
|
||||||
|
|
||||||
resourceDir := scriptDir
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
|
||||||
|
|
||||||
userDb, err := menuStorageService.GetUserdataDb(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
userStore := store.UserDataStore{userDb}
|
|
||||||
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write([]byte(sessionId))
|
|
||||||
address := h.Sum(nil)
|
|
||||||
addressString := fmt.Sprintf("%x", address)
|
|
||||||
|
|
||||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(addressString))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = userStore.WriteEntry(ctx, addressString, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = userDb.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
38
go.mod
38
go.mod
@@ -3,14 +3,15 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise
|
|||||||
go 1.23.4
|
go 1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739
|
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66
|
||||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05
|
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e
|
||||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a
|
||||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63
|
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306
|
||||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d
|
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
|
||||||
github.com/alecthomas/assert/v2 v2.2.2
|
github.com/alecthomas/assert/v2 v2.2.2
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta
|
github.com/grassrootseconomics/ethutils v1.3.1
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta
|
||||||
github.com/jackc/pgx/v5 v5.7.1
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
github.com/peteole/testdata-loader v0.3.0
|
github.com/peteole/testdata-loader v0.3.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
@@ -19,24 +20,49 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||||
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
||||||
github.com/alecthomas/repr v0.2.0 // indirect
|
github.com/alecthomas/repr v0.2.0 // indirect
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
||||||
|
github.com/bits-and-blooms/bitset v1.14.3 // indirect
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
||||||
|
github.com/consensys/bavard v0.1.13 // indirect
|
||||||
|
github.com/consensys/gnark-crypto v0.12.1 // indirect
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect
|
||||||
|
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
|
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
|
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
|
||||||
|
github.com/ethereum/go-ethereum v1.14.9 // indirect
|
||||||
|
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta // indirect
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta // indirect
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
|
github.com/holiman/uint256 v1.3.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/lmittmann/w3 v0.17.1 // indirect
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
||||||
|
github.com/mmcloughlin/addchain v0.4.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
|
github.com/supranational/blst v0.3.11 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
golang.org/x/time v0.7.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
rsc.io/tmplfunc v0.0.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
175
go.sum
175
go.sum
@@ -1,13 +1,37 @@
|
|||||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739 h1:w7pj1oh7jXrfajahVYU7m7AfHst4C6jNVzDVoaqJ7e8=
|
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66 h1:hmtb2Q3lHxq+SXqG+Gn43pKhTRYx+sw5j1LpgBfXN1o=
|
||||||
git.defalsify.org/vise.git v0.2.3-0.20250115000535-e2d329b3f739/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05 h1:BenzGx6aDHKDwE23/mWIFD2InYIXyzHroZWV3MF5WUk=
|
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc=
|
||||||
git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
||||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f h1:FgccQi8vipX6dUt+GRiRDYHMR3UqC+plz73vw7y3fyU=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5 h1:VnRe01kHkZUBK/QjE7iV6gElSqSwQnAkWV3yCHtuYrI=
|
||||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.0.0-20250115072214-bca7c5de969f/go.mod h1:tbA4whUGMUIDgQVdIW0sxWPuuXOvZRSny5zeku5hX4k=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5/go.mod h1:H97hR+VOnZvR5BiGVb0ScCPwH/IoKBOlKM+yrQNVpq0=
|
||||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63 h1:bX7klKZpX+ZZu1LKbtOXDAhV/KK0YwExehiIi0jusAM=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623070026-d945964b0b46 h1:0+XkSRe7XSHa9WHXKpGPuC0myDszjchr4syH006lQ28=
|
||||||
git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250115003256-c0534ede1b63/go.mod h1:Syw9TZyigPAM7t9FvicOm36iUnidt45f0SxzT2JniQ8=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623070026-d945964b0b46/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d h1:q/NO1rEgK3pia32D/tCq5hXfEuJp84COZRwceFvy/MM=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623075057-7b42d509e6d4 h1:W+8CC7x5eCPylkGy2TEoOpfJuiIlqzEzyYTzCLlY/u8=
|
||||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250113103030-f0b2056fd87d/go.mod h1:AH15xABcvaJr1TCGlih3oGSuwWC0E5IdbHQwuu+E1KI=
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623075057-7b42d509e6d4/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624074830-5aa032400c12 h1:vD8biQmN36eouuE+TdxgXQjKisRf5cTGu/tMPv3afs0=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624074830-5aa032400c12/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624090744-339ba854c997 h1:8bCKyYoV4YiVBvCZlRclc3aQlBYpWhgtM35mvniDFD8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250624090744-339ba854c997/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250626065419-57ee409f9629 h1:ew3vCFrLS/7/8uULTTPCbsHzFntQ6X68SScnBEy3pl0=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250626065419-57ee409f9629/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213135-50ee455e7069 h1:re+hdr5NAC6JqhyvjMCkgX17fFi0u3Mawc6RBnBJW8I=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213135-50ee455e7069/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213606-12940bb5f284 h1:2zMU9jPd6xEO6oY9oxr84sdT9G3d09eyAkjVBAz9eco=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630213606-12940bb5f284/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a h1:KuhJ/WY4RCGmrXUA680ciaponM4vM5zBOJfnCpUo2fc=
|
||||||
|
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250630214912-814bef2b209a/go.mod h1:y/vsN8UO0wSxZk3gv0y5df4RPKMJI6TIxjVcVCPF8T8=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306 h1:Jo+yWysWw/N5BJQtAyEMN8ePVvAyPHv+JG4lQti+1N4=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306/go.mod h1:FdLwYtzsjOIcDiW4uDgDYnB4Wdzq12uJUe0QHSSPbSo=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 h1:DjJlBSz0S13acft5XZDWk7ZYnzElym0xLMYEVgyNJ+E=
|
||||||
|
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694/go.mod h1:DpibtYpnT3nG4Kn556hRAkdu4+CtiI/6MbnQHal51mQ=
|
||||||
|
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||||
|
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/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||||
|
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||||
|
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||||
|
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||||
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
|
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
|
||||||
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||||
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
|
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
|
||||||
@@ -16,21 +40,91 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk
|
|||||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bits-and-blooms/bitset v1.14.3 h1:Gd2c8lSNf9pKXom5JtD7AaKO8o7fGQ2LtFj1436qilA=
|
||||||
|
github.com/bits-and-blooms/bitset v1.14.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||||
|
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||||
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||||
|
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||||
|
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||||
|
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||||
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||||
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||||
|
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
|
||||||
|
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
|
||||||
|
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||||
|
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||||
|
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||||
|
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||||
|
github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ=
|
||||||
|
github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI=
|
||||||
|
github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M=
|
||||||
|
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
|
||||||
|
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
|
||||||
|
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
|
||||||
|
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||||
|
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
|
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
|
||||||
|
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||||
|
github.com/ethereum/go-ethereum v1.14.9 h1:J7iwXDrtUyE9FUjUYbd4c9tyzwMh6dTJsKzo9i6SrwA=
|
||||||
|
github.com/ethereum/go-ethereum v1.14.9/go.mod h1:QeW+MtTpRdBEm2pUFoonByee8zfHv7kGp0wK0odvU1I=
|
||||||
|
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A=
|
||||||
|
github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||||
|
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||||
|
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
|
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
|
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||||
|
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk=
|
github.com/grassrootseconomics/ethutils v1.3.1 h1:LlQO90HjJkl7ejC8fv6jP7RJUrAm1j4VMMCYfsoIrhU=
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0=
|
github.com/grassrootseconomics/ethutils v1.3.1/go.mod h1:Wuv1VEZrkLIXqTSEYI3Nh9HG/ZHOUQ+U+xvWJ8QtjgQ=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta h1:BSSQL/yPEvTVku9ja/ENZyZdwZkEaiTzzOUfg72bTy4=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.5.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta h1:pY6zns6ifXyClRxP+JJaWrged3oRE7tTS2xaftF9clA=
|
||||||
|
github.com/grassrootseconomics/ussd-data-service v1.6.0-beta/go.mod h1:9sGnorpKaK76FmOGXoh/xv7x5siSFNYdXxQo9BKW4DI=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||||
|
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||||
|
github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs=
|
||||||
|
github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -41,21 +135,52 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
|
||||||
|
github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8=
|
||||||
|
github.com/lmittmann/w3 v0.17.1 h1:zdXIimmNmYfqOFur+Jqc9Yqwtq6jwnsQufbTOnSAtW4=
|
||||||
|
github.com/lmittmann/w3 v0.17.1/go.mod h1:WVUGMbL83WYBu4Sge3SVlW3qIG4VaHe+S8+UUnwz9Eg=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
|
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
|
||||||
|
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
|
||||||
|
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
||||||
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A=
|
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A=
|
||||||
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
|
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
|
||||||
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
|
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
|
||||||
|
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||||
|
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y=
|
||||||
|
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
|
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||||
|
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||||
|
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||||
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||||
|
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
@@ -63,23 +188,45 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
|
||||||
|
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
|
||||||
|
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
|
||||||
|
|||||||
142
handlers/application/account_status.go
Normal file
142
handlers/application/account_status.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckAccountStatus queries the API using the TrackingId and sets flags
|
||||||
|
// based on the account status.
|
||||||
|
func (h *MenuHandlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
flag_account_success, _ := h.flagManager.GetFlag("flag_account_success")
|
||||||
|
flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
|
||||||
|
if r.Active {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_success)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_pending)
|
||||||
|
} else {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_success)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MenuHandlers) CheckAccountCreated(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
flag_language_set, _ := h.flagManager.GetFlag("flag_language_set")
|
||||||
|
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
// reset major flags
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_language_set)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_created)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckBlockedStatus:
|
||||||
|
// 1. Checks whether the DATA_SELF_PIN_RESET is 1 and sets the flag_account_pin_reset
|
||||||
|
// 2. resets the account blocked flag if the PIN attempts have been reset by an admin.
|
||||||
|
// 3. Sets key flags (language and PIN) if the data exists
|
||||||
|
func (h *MenuHandlers) CheckBlockedStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
||||||
|
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
||||||
|
|
||||||
|
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
|
||||||
|
flag_language_set, _ := h.flagManager.GetFlag("flag_language_set")
|
||||||
|
pinFlagSet := h.st.MatchFlag(flag_pin_set, true)
|
||||||
|
languageFlagSet := h.st.MatchFlag(flag_language_set, true)
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
// only check the data if the flag isn't set
|
||||||
|
if !pinFlagSet {
|
||||||
|
accountPin, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN)
|
||||||
|
if len(accountPin) > 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_pin_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !languageFlagSet {
|
||||||
|
languageCode, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||||
|
if len(languageCode) > 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_language_set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
||||||
|
|
||||||
|
selfPinReset, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET)
|
||||||
|
if err == nil {
|
||||||
|
pinResetValue, _ := strconv.ParseUint(string(selfPinReset), 0, 64)
|
||||||
|
if pinResetValue == 1 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_pin_reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||||
|
if err != nil {
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||||
|
if pinAttemptsValue == 0 {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_blocked)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
166
handlers/application/account_status_test.go
Normal file
166
handlers/application/account_status_test.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckAccountStatus(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
flag_account_success, _ := fm.GetFlag("flag_account_success")
|
||||||
|
flag_account_pending, _ := fm.GetFlag("flag_account_pending")
|
||||||
|
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
publicKey []byte
|
||||||
|
response *models.TrackStatusResult
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test when account is on the Sarafu network",
|
||||||
|
publicKey: []byte("TrackingId1234"),
|
||||||
|
response: &models.TrackStatusResult{
|
||||||
|
Active: true,
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_account_success},
|
||||||
|
FlagReset: []uint32{flag_api_error, flag_account_pending},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test when the account is not yet on the sarafu network",
|
||||||
|
publicKey: []byte("TrackingId1234"),
|
||||||
|
response: &models.TrackStatusResult{
|
||||||
|
Active: false,
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_account_pending},
|
||||||
|
FlagReset: []uint32{flag_api_error, flag_account_success},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil)
|
||||||
|
|
||||||
|
// Call the method under test
|
||||||
|
res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte(""))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckBlockedStatus(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
flag_account_blocked, _ := fm.GetFlag("flag_account_blocked")
|
||||||
|
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
||||||
|
flag_pin_set, _ := fm.GetFlag("flag_pin_set")
|
||||||
|
flag_language_set, _ := fm.GetFlag("flag_language_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
currentWrongPinAttempts string
|
||||||
|
expectedResult resource.Result
|
||||||
|
languageSet bool
|
||||||
|
PinSet bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Currently blocked account",
|
||||||
|
currentWrongPinAttempts: "4",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_pin_reset},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Account with 0 wrong PIN attempts",
|
||||||
|
currentWrongPinAttempts: "0",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid account with reset language and PIN flags",
|
||||||
|
currentWrongPinAttempts: "0",
|
||||||
|
languageSet: true,
|
||||||
|
PinSet: true,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_pin_reset, flag_account_blocked},
|
||||||
|
FlagSet: []uint32{flag_pin_set, flag_language_set},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(tt.currentWrongPinAttempts)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.languageSet {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte("eng")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.PinSet {
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte("hasedPinValue")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.CheckBlockedStatus(ctx, "", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedResult, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
135
handlers/application/alias.go
Normal file
135
handlers/application/alias.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestCustomAlias requests an ENS based alias name based on a user's input,then saves it as temporary value
|
||||||
|
func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var alias string
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
if string(input) == "0" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
flag_alias_unavailable, _ := h.flagManager.GetFlag("flag_alias_unavailable")
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
aliasHint, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
//Ensures that the call doesn't happen twice for the same alias hint
|
||||||
|
if !bytes.Equal(aliasHint, input) {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(string(input)))
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sanitizedInput := sanitizeAliasHint(string(input))
|
||||||
|
// Check if an alias already exists
|
||||||
|
existingAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||||
|
if err == nil && len(existingAlias) > 0 {
|
||||||
|
logg.InfoCtxf(ctx, "Current alias", "alias", string(existingAlias))
|
||||||
|
|
||||||
|
unavailable, err := h.isAliasUnavailable(ctx, sanitizedInput)
|
||||||
|
if err == nil && unavailable {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_alias_unavailable)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_alias_unavailable)
|
||||||
|
|
||||||
|
// Update existing alias
|
||||||
|
aliasResult, err := h.accountService.UpdateAlias(ctx, sanitizedInput, string(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed to update alias", "alias", sanitizedInput, "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
alias = aliasResult.Alias
|
||||||
|
logg.InfoCtxf(ctx, "Updated alias", "alias", alias)
|
||||||
|
} else {
|
||||||
|
logg.InfoCtxf(ctx, "Registering a new alias", "err", err)
|
||||||
|
|
||||||
|
unavailable, err := h.isAliasUnavailable(ctx, sanitizedInput)
|
||||||
|
if err == nil && unavailable {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_alias_unavailable)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_alias_unavailable)
|
||||||
|
|
||||||
|
// Register a new alias
|
||||||
|
aliasResult, err := h.accountService.RequestAlias(ctx, string(publicKey), sanitizedInput)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed to retrieve alias", "alias", sanitizedInput, "error_alias_request", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
|
||||||
|
alias = aliasResult.Alias
|
||||||
|
logg.InfoCtxf(ctx, "Registered alias", "alias", alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Store the new account alias
|
||||||
|
logg.InfoCtxf(ctx, "Final registered alias", "alias", alias)
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(alias))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write account alias", "key", storedb.DATA_ACCOUNT_ALIAS, "value", alias, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeAliasHint(input string) string {
|
||||||
|
for i, r := range input {
|
||||||
|
// Check if the character is a special character (non-alphanumeric)
|
||||||
|
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
|
||||||
|
return input[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no special character is found, return the whole input
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MenuHandlers) isAliasUnavailable(ctx context.Context, alias string) (bool, error) {
|
||||||
|
fqdn := fmt.Sprintf("%s.%s", alias, "sarafu.eth")
|
||||||
|
logg.InfoCtxf(ctx, "Checking if the fqdn alias is taken", "fqdn", fqdn)
|
||||||
|
|
||||||
|
aliasAddress, err := h.accountService.CheckAliasAddress(ctx, fqdn)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(aliasAddress.Address) > 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
77
handlers/application/authorization.go
Normal file
77
handlers/application/authorization.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN.
|
||||||
|
// It sets the required flags that control the flow.
|
||||||
|
func (h *MenuHandlers) Authorize(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||||
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
|
||||||
|
pinInput := string(input)
|
||||||
|
|
||||||
|
if !pin.IsValidPIN(pinInput) {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
AccountPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", storedb.DATA_ACCOUNT_PIN, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the user provided the correct PIN
|
||||||
|
if pin.VerifyPIN(string(AccountPin), pinInput) {
|
||||||
|
// set the required flags for a valid PIN
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
|
||||||
|
|
||||||
|
err := h.resetIncorrectPINAttempts(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// set the required flags for an incorrect PIN
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_authorized, flag_allow_update)
|
||||||
|
|
||||||
|
err = h.incrementIncorrectPINAttempts(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
|
||||||
|
func (h *MenuHandlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_allow_update)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
|
||||||
|
func (h *MenuHandlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
181
handlers/application/authorization_test.go
Normal file
181
handlers/application/authorization_test.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAuthorize(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create required mocks
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
flag_incorrect_pin, _ := fm.GetFlag("flag_incorrect_pin")
|
||||||
|
flag_account_authorized, _ := fm.GetFlag("flag_account_authorized")
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
|
||||||
|
// Set 1234 is the correct account pin
|
||||||
|
accountPIN := "1234"
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with correct PIN",
|
||||||
|
input: []byte("1234"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_incorrect_pin},
|
||||||
|
FlagSet: []uint32{flag_allow_update, flag_account_authorized},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with incorrect PIN",
|
||||||
|
input: []byte("1235"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_authorized, flag_allow_update},
|
||||||
|
FlagSet: []uint32{flag_incorrect_pin},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with PIN that is not a 4 digit",
|
||||||
|
input: []byte("1235aqds"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_authorized, flag_allow_update},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Hash the PIN
|
||||||
|
hashedPIN, err := pin.HashPIN(accountPIN)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedPIN))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method under test
|
||||||
|
res, err := h.Authorize(ctx, "authorize", []byte(tt.input))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetAllowUpdate(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Resets allow update",
|
||||||
|
input: []byte(""),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_allow_update},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create the MenuHandlers instance with the mock flag manager
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.ResetAllowUpdate(context.Background(), "reset_allow update", tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Flags should be equal to account created")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetAccountAuthorized(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_account_authorized, _ := fm.GetFlag("flag_account_authorized")
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Resets account authorized",
|
||||||
|
input: []byte(""),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_account_authorized},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create the MenuHandlers instance with the mock flag manager
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.ResetAccountAuthorized(context.Background(), "reset_account_authorized", tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
100
handlers/application/balance.go
Normal file
100
handlers/application/balance.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckBalance retrieves the balance of the active voucher and sets
|
||||||
|
// the balance as the result content.
|
||||||
|
func (h *MenuHandlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var (
|
||||||
|
res resource.Result
|
||||||
|
err error
|
||||||
|
content string
|
||||||
|
)
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
// get the active sym and active balance
|
||||||
|
activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||||
|
if err != nil {
|
||||||
|
logg.InfoCtxf(ctx, "could not find the activeSym in checkBalance:", "err", err)
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||||
|
if err != nil {
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||||
|
if err != nil {
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read account alias entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err = loadUserContent(ctx, string(activeSym), string(activeBal), string(accAlias))
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.Content = content
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadUserContent loads the main user content in the main menu: the alias, balance and active symbol associated with active voucher
|
||||||
|
func loadUserContent(ctx context.Context, activeSym string, balance string, alias string) (string, error) {
|
||||||
|
var content string
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
// Format the balance to 2 decimal places or default to 0.00
|
||||||
|
formattedAmount, err := store.TruncateDecimalString(balance, 2)
|
||||||
|
if err != nil {
|
||||||
|
formattedAmount = "0.00"
|
||||||
|
}
|
||||||
|
|
||||||
|
// format the final output
|
||||||
|
balStr := fmt.Sprintf("%s %s", formattedAmount, activeSym)
|
||||||
|
if alias != "" {
|
||||||
|
content = l.Get("%s\nBalance: %s\n", alias, balStr)
|
||||||
|
} else {
|
||||||
|
content = l.Get("Balance: %s\n", balStr)
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchCommunityBalance retrieves and displays the balance for community accounts in user's preferred language.
|
||||||
|
func (h *MenuHandlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
// retrieve the language code from the context
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
// Initialize the localization system with the appropriate translation directory
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
//TODO:
|
||||||
|
//Check if the address is a community account,if then,get the actual balance
|
||||||
|
res.Content = l.Get("Community Balance: 0.00")
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
146
handlers/application/balance_test.go
Normal file
146
handlers/application/balance_test.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckBalance(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sessionId string
|
||||||
|
publicKey string
|
||||||
|
alias string
|
||||||
|
activeSym string
|
||||||
|
activeBal string
|
||||||
|
expectedResult resource.Result
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "User with no active sym",
|
||||||
|
sessionId: "session123",
|
||||||
|
publicKey: "0X98765432109",
|
||||||
|
alias: "",
|
||||||
|
activeSym: "",
|
||||||
|
activeBal: "",
|
||||||
|
expectedResult: resource.Result{Content: "Balance: 0.00 \n"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User with active sym",
|
||||||
|
sessionId: "session123",
|
||||||
|
publicKey: "0X98765432109",
|
||||||
|
alias: "",
|
||||||
|
activeSym: "ETH",
|
||||||
|
activeBal: "1.5",
|
||||||
|
expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User with active sym and alias",
|
||||||
|
sessionId: "session123",
|
||||||
|
publicKey: "0X98765432109",
|
||||||
|
alias: "user72",
|
||||||
|
activeSym: "SRF",
|
||||||
|
activeBal: "10.967",
|
||||||
|
expectedResult: resource.Result{Content: "user72 balance: 10.96 SRF\n"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
ctx := context.WithValue(ctx, "SessionId", tt.sessionId)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.alias != "" {
|
||||||
|
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(tt.alias))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.activeSym != "" {
|
||||||
|
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.activeSym))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.activeBal != "" {
|
||||||
|
err := store.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.CheckBalance(ctx, "check_balance", []byte(""))
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedResult, res, "Result should match expected output")
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchCommunityBalance(t *testing.T) {
|
||||||
|
// Define test data
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
languageCode string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test community balance content when language is english",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "Community Balance: 0.00",
|
||||||
|
},
|
||||||
|
languageCode: "eng",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
st: mockState,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||||
|
Code: tt.languageCode,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, _ := h.FetchCommunityBalance(ctx, "fetch_community_balance", []byte(""))
|
||||||
|
|
||||||
|
//Assert that the result set to content is what was expected
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
74
handlers/application/language.go
Normal file
74
handlers/application/language.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
commonlang "git.grassecon.net/grassrootseconomics/common/lang"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetLanguage sets the language across the menu.
|
||||||
|
func (h *MenuHandlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
symbol, _ := h.st.Where()
|
||||||
|
code := strings.Split(symbol, "_")[1]
|
||||||
|
|
||||||
|
// TODO: Use defaultlanguage from config
|
||||||
|
if !commonlang.IsValidISO639(code) {
|
||||||
|
//Fallback to english instead?
|
||||||
|
code = "eng"
|
||||||
|
}
|
||||||
|
err := h.persistLanguageCode(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.Content = code
|
||||||
|
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
|
||||||
|
languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Error setting the languageSetFlag", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, languageSetFlag)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// persistLanguageCode persists the selected ISO 639 language code
|
||||||
|
func (h *MenuHandlers) persistLanguageCode(ctx context.Context, code string) error {
|
||||||
|
store := h.userdataStore
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE, []byte(code))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to persist language code", "key", storedb.DATA_SELECTED_LANGUAGE_CODE, "value", code, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return h.persistInitialLanguageCode(ctx, sessionId, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// persistInitialLanguageCode receives an initial language code and persists it to the store
|
||||||
|
func (h *MenuHandlers) persistInitialLanguageCode(ctx context.Context, sessionId string, code string) error {
|
||||||
|
store := h.userdataStore
|
||||||
|
_, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE, []byte(code))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to persist initial language code", "key", storedb.DATA_INITIAL_LANGUAGE_CODE, "value", code, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
159
handlers/application/language_test.go
Normal file
159
handlers/application/language_test.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetLanguage(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
execPath []string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set Default Language (English)",
|
||||||
|
execPath: []string{"set_eng"},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{state.FLAG_LANG, 8},
|
||||||
|
Content: "eng",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set Swahili Language",
|
||||||
|
execPath: []string{"set_swa"},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{state.FLAG_LANG, 8},
|
||||||
|
Content: "swa",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
// Set the ExecPath
|
||||||
|
mockState.ExecPath = tt.execPath
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock flag manager
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
userdataStore: store,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SetLanguage(ctx, "set_language", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||||
|
code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, string(code), tt.expectedResult.Content)
|
||||||
|
code, err = store.ReadEntry(ctx, sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, string(code), "eng")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPersistLanguageCode(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
expectedLanguageCode string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Set Default Language (English)",
|
||||||
|
code: "eng",
|
||||||
|
expectedLanguageCode: "eng",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set Swahili Language",
|
||||||
|
code: "swa",
|
||||||
|
expectedLanguageCode: "swa",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := h.persistLanguageCode(ctx, test.code)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
code, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SELECTED_LANGUAGE_CODE)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedLanguageCode, string(code))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPersistInitialLanguageCode(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
sessionId string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Persist initial Language (English)",
|
||||||
|
code: "eng",
|
||||||
|
sessionId: "session123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Persist initial Language (Swahili)",
|
||||||
|
code: "swa",
|
||||||
|
sessionId: "session456",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := h.persistInitialLanguageCode(ctx, tt.sessionId, tt.code)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
code, err := store.ReadEntry(ctx, tt.sessionId, storedb.DATA_INITIAL_LANGUAGE_CODE)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.code, string(code))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
372
handlers/application/pin.go
Normal file
372
handlers/application/pin.go
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt.
|
||||||
|
func (h *MenuHandlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||||
|
flag_account_blocked, _ := h.flagManager.GetFlag("flag_account_blocked")
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
|
||||||
|
|
||||||
|
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||||
|
if err != nil {
|
||||||
|
if !db.IsNotFound(err) {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||||
|
remainingPINAttempts := pin.AllowedPINAttempts - uint8(pinAttemptsValue)
|
||||||
|
if remainingPINAttempts == 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_blocked)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
if remainingPINAttempts < pin.AllowedPINAttempts {
|
||||||
|
res.Content = strconv.Itoa(int(remainingPINAttempts))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveTemporaryPin saves the valid PIN input to the DATA_TEMPORARY_VALUE,
|
||||||
|
// during the account creation process
|
||||||
|
// and during the change PIN process.
|
||||||
|
func (h *MenuHandlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin")
|
||||||
|
|
||||||
|
if string(input) == "0" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
accountPIN := string(input)
|
||||||
|
|
||||||
|
// Validate that the PIN has a valid format.
|
||||||
|
if !pin.IsValidPIN(accountPIN) {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_pin)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_invalid_pin)
|
||||||
|
|
||||||
|
// Hash the PIN
|
||||||
|
hashedPIN, err := pin.HashPIN(string(accountPIN))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to hash the PIN", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write temporaryAccountPIN log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetInvalidPIN resets the invalid PIN flag
|
||||||
|
func (h *MenuHandlers) ResetInvalidPIN(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
flag_invalid_pin, _ := h.flagManager.GetFlag("flag_invalid_pin")
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_invalid_pin)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmPinChange validates user's new PIN. If input matches the temporary PIN, saves it as the new account PIN.
|
||||||
|
func (h *MenuHandlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
|
||||||
|
flag_account_pin_reset, _ := h.flagManager.GetFlag("flag_account_pin_reset")
|
||||||
|
|
||||||
|
if string(input) == "0" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
if len(hashedTemporaryPin) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||||
|
} else {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the hashed PIN as the new account PIN
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "hashedPIN value", hashedTemporaryPin, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write AccountPIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the DATA_SELF_PIN_RESET as 0
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_SELF_PIN_RESET, []byte("0"))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write DATA_SELF_PIN_RESET entry with", "key", storedb.DATA_SELF_PIN_RESET, "self PIN reset value", "0", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_pin_reset)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateBlockedNumber performs validation of phone numbers during the Reset other's PIN.
|
||||||
|
// It checks phone number format and verifies registration status.
|
||||||
|
// If valid, it writes the number under DATA_BLOCKED_NUMBER on the admin account
|
||||||
|
func (h *MenuHandlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(input) == "0" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockedNumber := string(input)
|
||||||
|
formattedNumber, err := phone.FormatPhoneNumber(blockedNumber)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", blockedNumber, "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
logg.InfoCtxf(ctx, "Invalid or unregistered number")
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
|
||||||
|
return res, nil
|
||||||
|
} else {
|
||||||
|
logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(formattedNumber))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write blocked number log entry", "key", storedb.DATA_BLOCKED_NUMBER, "value", formattedNumber, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetOthersPin handles the PIN reset process for other users' accounts by:
|
||||||
|
// 1. Retrieving the blocked phone number from the session
|
||||||
|
// 2. Writing the DATA_SELF_PIN_RESET on the blocked phone number
|
||||||
|
// 3. Resetting the DATA_INCORRECT_PIN_ATTEMPTS to 0 for the blocked phone number
|
||||||
|
func (h *MenuHandlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
smsservice := h.smsService
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", storedb.DATA_BLOCKED_NUMBER, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the DATA_SELF_PIN_RESET for the account
|
||||||
|
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_SELF_PIN_RESET, []byte("1"))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, string(blockedPhonenumber), storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0")))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
blockedPhoneStr := string(blockedPhonenumber)
|
||||||
|
//Trigger an SMS to inform a user that the blocked account has been reset
|
||||||
|
if phone.IsValidPhoneNumber(blockedPhoneStr) {
|
||||||
|
err = smsservice.SendPINResetSMS(ctx, sessionId, blockedPhoneStr)
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to send PIN reset SMS", "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// incrementIncorrectPINAttempts keeps track of the number of incorrect PIN attempts
|
||||||
|
func (h *MenuHandlers) incrementIncorrectPINAttempts(ctx context.Context, sessionId string) error {
|
||||||
|
var pinAttemptsCount uint8
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
currentWrongPinAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
//First time Wrong PIN attempt: initialize with a count of 1
|
||||||
|
pinAttemptsCount = 1
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount))))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", currentWrongPinAttempts, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pinAttemptsValue, _ := strconv.ParseUint(string(currentWrongPinAttempts), 0, 64)
|
||||||
|
pinAttemptsCount = uint8(pinAttemptsValue) + 1
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(pinAttemptsCount))))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "value", pinAttemptsCount, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetIncorrectPINAttempts resets the number of incorrect PIN attempts after a correct PIN entry
|
||||||
|
func (h *MenuHandlers) resetIncorrectPINAttempts(ctx context.Context, sessionId string) error {
|
||||||
|
store := h.userdataStore
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("0")))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to reset incorrect PIN attempts ", "key", storedb.DATA_INCORRECT_PIN_ATTEMPTS, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyCreatePin checks whether the confirmation PIN is similar to the temporary PIN
|
||||||
|
// If similar, it sets the USERFLAG_PIN_SET flag and writes the account PIN allowing the user
|
||||||
|
// to access the main menu.
|
||||||
|
func (h *MenuHandlers) VerifyCreatePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
|
||||||
|
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
|
||||||
|
flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
|
||||||
|
|
||||||
|
if string(input) == "0" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
hashedTemporaryPin, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read hashedTemporaryPin entry with", "key", storedb.DATA_TEMPORARY_VALUE, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
if len(hashedTemporaryPin) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "hashedTemporaryPin is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pin.VerifyPIN(string(hashedTemporaryPin), string(input)) {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_valid_pin)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_pin_set)
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
|
||||||
|
} else {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the hashed PIN as the new account PIN
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write DATA_ACCOUNT_PIN entry with", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_ACCOUNT_PIN, []byte(hashedTemporaryPin))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write DATA_ACCOUNT_PIN log entry", "key", storedb.DATA_ACCOUNT_PIN, "value", hashedTemporaryPin, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
|
||||||
|
func (h *MenuHandlers) RetrieveBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
blockedNumber, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||||
|
|
||||||
|
res.Content = string(blockedNumber)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
293
handlers/application/pin_test.go
Normal file
293
handlers/application/pin_test.go
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/pin"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCountIncorrectPINAttempts(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
attempts := uint8(2)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(strconv.Itoa(int(attempts))))
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
err = h.incrementIncorrectPINAttempts(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptsAfterCount, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
pinAttemptsValue, _ := strconv.ParseUint(string(attemptsAfterCount), 0, 64)
|
||||||
|
pinAttemptsCount := uint8(pinAttemptsValue)
|
||||||
|
expectedAttempts := attempts + 1
|
||||||
|
assert.Equal(t, pinAttemptsCount, expectedAttempts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetIncorrectPINAttempts(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS, []byte(string("2")))
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
h.resetIncorrectPINAttempts(ctx, sessionId)
|
||||||
|
incorrectAttempts, err := store.ReadEntry(ctx, sessionId, storedb.DATA_INCORRECT_PIN_ATTEMPTS)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
assert.Equal(t, "0", string(incorrectAttempts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveTemporaryPin(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
ctx, userdatastore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_pin, _ := fm.GetFlag("flag_invalid_pin")
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock flag manager
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
userdataStore: userdatastore,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Pin entry",
|
||||||
|
input: []byte("1234"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_invalid_pin},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Pin entry",
|
||||||
|
input: []byte("12343"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_invalid_pin},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveTemporaryPin(ctx, "save_pin", tt.input)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfirmPinChange(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
flag_pin_mismatch, _ := fm.GetFlag("flag_pin_mismatch")
|
||||||
|
flag_account_pin_reset, _ := fm.GetFlag("flag_account_pin_reset")
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
temporarypin string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with correct pin confirmation",
|
||||||
|
input: []byte("1234"),
|
||||||
|
temporarypin: "1234",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_pin_mismatch, flag_account_pin_reset},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Hash the PIN
|
||||||
|
hashedPIN, err := pin.HashPIN(tt.temporarypin)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to hash temporaryPin", "error", err)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the expected behavior of the mock
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Call the function under test
|
||||||
|
res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.input)
|
||||||
|
|
||||||
|
//Assert that the result set to content is what was expected
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateBlockedNumber(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
validNumber := "+254712345678"
|
||||||
|
invalidNumber := "12343" // Invalid phone number
|
||||||
|
unregisteredNumber := "+254734567890" // Valid but unregistered number
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
flag_unregistered_number, _ := fm.GetFlag("flag_unregistered_number")
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
st: mockState,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, validNumber, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid and registered number",
|
||||||
|
input: []byte(validNumber),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Phone Number",
|
||||||
|
input: []byte(invalidNumber),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_unregistered_number},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unregistered Phone Number",
|
||||||
|
input: []byte(unregisteredNumber),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_unregistered_number},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
res, err := h.ValidateBlockedNumber(ctx, "validate_blocked_number", tt.input)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedResult, res)
|
||||||
|
|
||||||
|
if tt.name == "Valid and registered number" {
|
||||||
|
blockedNumber, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, validNumber, string(blockedNumber))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetOthersPin(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
blockedNumber := "+254712345678"
|
||||||
|
testPin := "1234"
|
||||||
|
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
hashedPIN, err := pin.HashPIN(testPin)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to hash testPin", "error", err)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write initial data to the store
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = userStore.WriteEntry(ctx, blockedNumber, storedb.DATA_TEMPORARY_VALUE, []byte(hashedPIN))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = h.ResetOthersPin(ctx, "reset_others_pin", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
185
handlers/application/pools.go
Normal file
185
handlers/application/pools.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPools fetches a list of 5 top pools
|
||||||
|
func (h *MenuHandlers) GetPools(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||||
|
|
||||||
|
// call the api to get a list of top 5 pools sorted by swaps
|
||||||
|
topPools, err := h.accountService.FetchTopPools(ctx)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if there are no pools
|
||||||
|
if len(topPools) == 0 {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
activePoolSymStr := ""
|
||||||
|
|
||||||
|
activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||||
|
if err != nil {
|
||||||
|
activePoolSymStr = config.DefaultPoolSymbol()
|
||||||
|
} else {
|
||||||
|
activePoolSymStr = string(activePoolSym)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the active pool from topPools
|
||||||
|
filteredPools := make([]dataserviceapi.PoolDetails, 0, len(topPools))
|
||||||
|
for _, p := range topPools {
|
||||||
|
if p.PoolSymbol != activePoolSymStr {
|
||||||
|
filteredPools = append(filteredPools, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := store.ProcessPools(filteredPools)
|
||||||
|
|
||||||
|
// Store the filtered Pool data
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_POOL_NAMES: data.PoolNames,
|
||||||
|
storedb.DATA_POOL_SYMBOLS: data.PoolSymbols,
|
||||||
|
storedb.DATA_POOL_ADDRESSES: data.PoolContractAdrresses,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data entries
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = h.ReplaceSeparatorFunc(data.PoolSymbols)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaultPool returns the current user's Pool. If none is set, it returns the default config pool.
|
||||||
|
func (h *MenuHandlers) GetDefaultPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
activePoolSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
// set the default as the response
|
||||||
|
res.Content = config.DefaultPoolSymbol()
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read the activePoolSym entry with", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = string(activePoolSym)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewPool retrieves the pool details from the user store
|
||||||
|
// and displays it to the user for them to select it.
|
||||||
|
func (h *MenuHandlers) ViewPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
flag_incorrect_pool, _ := h.flagManager.GetFlag("flag_incorrect_pool")
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "88" || inputStr == "98" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_pool)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
poolData, err := store.GetPoolData(ctx, h.userdataStore, sessionId, inputStr)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to retrieve pool data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if poolData == nil {
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
// no match found. Call the API using the inputStr as the symbol
|
||||||
|
poolResp, err := h.accountService.RetrievePoolDetails(ctx, inputStr)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(poolResp.PoolSymbol) == 0 {
|
||||||
|
// If the API does not return the data, set the flag
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_pool)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
poolData = poolResp
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.StoreTemporaryPool(ctx, h.userdataStore, sessionId, poolData); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on StoreTemporaryPool", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_pool)
|
||||||
|
res.Content = l.Get("Name: %s\nSymbol: %s", poolData.PoolName, poolData.PoolSymbol)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPool retrieves the temp pool data and sets it as the active data.
|
||||||
|
func (h *MenuHandlers) SetPool(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get temporary data
|
||||||
|
tempData, err := store.GetTemporaryPoolData(ctx, h.userdataStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on GetTemporaryPoolData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set as active and clear temporary data
|
||||||
|
if err := store.UpdatePoolData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on UpdatePoolData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = tempData.PoolSymbol
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
391
handlers/application/poolswap.go
Normal file
391
handlers/application/poolswap.go
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadSwapFromList returns a list of possible vouchers to swap to
|
||||||
|
func (h *MenuHandlers) LoadSwapToList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
// get the active address and symbol
|
||||||
|
activeAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active pool address and symbol or fall back to default
|
||||||
|
var activePoolAddress []byte
|
||||||
|
activePoolAddress, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
defaultPoolAddress := config.DefaultPoolAddress()
|
||||||
|
// store the default as the active pool address
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_ADDRESS, []byte(defaultPoolAddress))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write default PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "value", defaultPoolAddress, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
activePoolAddress = []byte(defaultPoolAddress)
|
||||||
|
} else {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read active PoolContractAdrress", "key", storedb.DATA_ACTIVE_POOL_ADDRESS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var activePoolSymbol []byte
|
||||||
|
activePoolSymbol, err = userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
defaultPoolSym := config.DefaultPoolName()
|
||||||
|
// store the default as the active pool symbol
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_POOL_SYM, []byte(defaultPoolSym))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write default Pool Symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "value", defaultPoolSym, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
activePoolSymbol = []byte(defaultPoolSym)
|
||||||
|
} else {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read active Pool symbol", "key", storedb.DATA_ACTIVE_POOL_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the api using the ActivePoolAddress and ActiveVoucherAddress to check if it is part of the pool
|
||||||
|
r, err := h.accountService.CheckTokenInPool(ctx, string(activePoolAddress), string(activeAddress))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on CheckTokenInPool", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "CheckTokenInPool", "response", r, "active_pool_address", string(activePoolAddress), "active_symbol_address", string(activeAddress))
|
||||||
|
|
||||||
|
if !r.CanSwapFrom {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
res.Content = l.Get(
|
||||||
|
"%s is not in %s. Please update your voucher and try again.",
|
||||||
|
activeSym,
|
||||||
|
activePoolSymbol,
|
||||||
|
)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
|
||||||
|
// call the api using the activePoolAddress to get a list of SwapToSymbolsData
|
||||||
|
swapToList, err := h.accountService.GetPoolSwappableVouchers(ctx, string(activePoolAddress))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "GetPoolSwappableVouchers", "swapToList", swapToList)
|
||||||
|
|
||||||
|
// Return if there are no vouchers
|
||||||
|
if len(swapToList) == 0 {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the active voucher from swapToList
|
||||||
|
filteredSwapToList := make([]dataserviceapi.TokenHoldings, 0, len(swapToList))
|
||||||
|
for _, s := range swapToList {
|
||||||
|
if s.TokenSymbol != string(activeSym) {
|
||||||
|
filteredSwapToList = append(filteredSwapToList, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store filtered swap to list data (excluding the current active voucher)
|
||||||
|
data := store.ProcessVouchers(filteredSwapToList)
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "ProcessVouchers", "data", data)
|
||||||
|
|
||||||
|
// Store all swap_to tokens data
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_POOL_TO_SYMBOLS: data.Symbols,
|
||||||
|
storedb.DATA_POOL_TO_BALANCES: data.Balances,
|
||||||
|
storedb.DATA_POOL_TO_DECIMALS: data.Decimals,
|
||||||
|
storedb.DATA_POOL_TO_ADDRESSES: data.Addresses,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = h.ReplaceSeparatorFunc(data.Symbols)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapMaxLimit returns the max FROM token
|
||||||
|
// check if max/tokenDecimals > 0.1 for UX purposes and to prevent swapping of dust values
|
||||||
|
func (h *MenuHandlers) SwapMaxLimit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||||
|
flag_low_swap_amount, _ := h.flagManager.GetFlag("flag_low_swap_amount")
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher, flag_low_swap_amount)
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "88" || inputStr == "98" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
metadata, err := store.GetSwapToVoucherData(ctx, userStore, sessionId, inputStr)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to retrieve swap to voucher data: %v", err)
|
||||||
|
}
|
||||||
|
if metadata == nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "Metadata from GetSwapToVoucherData:", "metadata", metadata)
|
||||||
|
|
||||||
|
// Store the active swap_to data
|
||||||
|
if err := store.UpdateSwapToVoucherData(ctx, userStore, sessionId, metadata); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on UpdateSwapToVoucherData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapData, err := store.ReadSwapData(ctx, userStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the api using the ActivePoolAddress, ActiveSwapFromAddress, ActiveSwapToAddress and PublicKey to get the swap max limit
|
||||||
|
logg.InfoCtxf(ctx, "Call GetSwapFromTokenMaxLimit with:", "ActivePoolAddress", swapData.ActivePoolAddress, "ActiveSwapFromAddress", swapData.ActiveSwapFromAddress, "ActiveSwapToAddress", swapData.ActiveSwapToAddress, "publicKey", swapData.PublicKey)
|
||||||
|
r, err := h.accountService.GetSwapFromTokenMaxLimit(ctx, swapData.ActivePoolAddress, swapData.ActiveSwapFromAddress, swapData.ActiveSwapToAddress, swapData.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on GetSwapFromTokenMaxLimit", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale down the amount
|
||||||
|
maxAmountStr := store.ScaleDownBalance(r.Max, swapData.ActiveSwapFromDecimal)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAmountFloat, err := strconv.ParseFloat(maxAmountStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to parse maxAmountStr as float", "value", maxAmountStr, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format to 2 decimal places
|
||||||
|
maxStr := fmt.Sprintf("%.2f", maxAmountFloat)
|
||||||
|
|
||||||
|
if maxAmountFloat < 0.1 {
|
||||||
|
// return with low amount flag
|
||||||
|
res.Content = maxStr
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_low_swap_amount)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, []byte(maxStr))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write swap max amount entry with", "key", storedb.DATA_ACTIVE_SWAP_MAX_AMOUNT, "value", maxStr, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"Maximum: %s\n\nEnter amount of %s to swap for %s:",
|
||||||
|
maxStr, swapData.ActiveSwapFromSym, swapData.ActiveSwapToSym,
|
||||||
|
)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapPreview displays the swap preview and estimates
|
||||||
|
func (h *MenuHandlers) SwapPreview(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxValue, err := strconv.ParseFloat(swapData.ActiveSwapMaxAmount, 64)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to convert the swapMaxAmount to a float", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputAmount, err := strconv.ParseFloat(inputStr, 64)
|
||||||
|
if err != nil || inputAmount > maxValue {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||||
|
res.Content = inputStr
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the amount to 2 decimal places
|
||||||
|
formattedAmount, err := store.TruncateDecimalString(inputStr, 2)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||||
|
res.Content = inputStr
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
finalAmountStr, err := store.ParseAndScaleAmount(formattedAmount, swapData.ActiveSwapFromDecimal)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT, []byte(finalAmountStr))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
// store the user's input amount in the temporary value
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(inputStr))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write swap amount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "value", finalAmountStr, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the API to get the quote
|
||||||
|
r, err := h.accountService.GetPoolSwapQuote(ctx, finalAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
||||||
|
if err != nil {
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
res.Content = l.Get("Your request failed. Please try again later.")
|
||||||
|
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale down the quoted amount
|
||||||
|
quoteAmountStr := store.ScaleDownBalance(r.OutValue, swapData.ActiveSwapToDecimal)
|
||||||
|
qouteAmount, err := strconv.ParseFloat(quoteAmountStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to parse quoteAmountStr as float", "value", quoteAmountStr, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format to 2 decimal places
|
||||||
|
qouteStr := fmt.Sprintf("%.2f", qouteAmount)
|
||||||
|
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"You will swap:\n%s %s for %s %s:",
|
||||||
|
inputStr, swapData.ActiveSwapFromSym, qouteStr, swapData.ActiveSwapToSym,
|
||||||
|
)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitiateSwap calls the poolSwap and returns a confirmation based on the result.
|
||||||
|
func (h *MenuHandlers) InitiateSwap(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
swapData, err := store.ReadSwapPreviewData(ctx, userStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapAmount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SWAP_AMOUNT)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read swapAmount entry with", "key", storedb.DATA_ACTIVE_SWAP_AMOUNT, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
swapAmountStr := string(swapAmount)
|
||||||
|
|
||||||
|
// Call the poolSwap API
|
||||||
|
r, err := h.accountService.PoolSwap(ctx, swapAmountStr, swapData.PublicKey, swapData.ActiveSwapFromAddress, swapData.ActivePoolAddress, swapData.ActiveSwapToAddress)
|
||||||
|
if err != nil {
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
res.Content = l.Get("Your request failed. Please try again later.")
|
||||||
|
logg.ErrorCtxf(ctx, "failed on poolSwap", "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trackingId := r.TrackingId
|
||||||
|
logg.InfoCtxf(ctx, "poolSwap", "trackingId", trackingId)
|
||||||
|
|
||||||
|
res.Content = l.Get(
|
||||||
|
"Your request has been sent. You will receive an SMS when your %s %s has been swapped for %s.",
|
||||||
|
swapData.TemporaryValue,
|
||||||
|
swapData.ActiveSwapFromSym,
|
||||||
|
swapData.ActiveSwapToSym,
|
||||||
|
)
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
616
handlers/application/profile.go
Normal file
616
handlers/application/profile.go
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/person"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveFirstname updates the first name in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveFirstname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
firstName := string(input)
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||||
|
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryFirstName) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryFirstName is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FIRST_NAME, "value", temporaryFirstName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if firstNameSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(0, firstName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveFamilyname updates the family name in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveFamilyname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
familyName := string(input)
|
||||||
|
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
|
||||||
|
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryFamilyName) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryFamilyName is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write firtname db log entry", "key", storedb.DATA_FAMILY_NAME, "value", temporaryFamilyName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if familyNameSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(1, familyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyYob verifies the length of the given input.
|
||||||
|
func (h *MenuHandlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
|
||||||
|
date := string(input)
|
||||||
|
_, err = strconv.Atoi(date)
|
||||||
|
if err != nil {
|
||||||
|
// If conversion fails, input is not numeric
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if person.IsValidYOb(date) {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
|
||||||
|
} else {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetIncorrectYob resets the incorrect date format flag after a new attempt.
|
||||||
|
func (h *MenuHandlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
yob := string(input)
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||||
|
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
yobSet := h.st.MatchFlag(flag_yob_set, true)
|
||||||
|
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryYob, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryYob) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryYob is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_YOB, []byte(temporaryYob))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write yob db log entry", "key", storedb.DATA_YOB, "value", temporaryYob)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if yobSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(3, yob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveLocation updates the location in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveLocation(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
location := string(input)
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
locationSet := h.st.MatchFlag(flag_location_set, true)
|
||||||
|
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryLocation) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryLocation is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", storedb.DATA_LOCATION, "value", temporaryLocation, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_LOCATION, []byte(temporaryLocation))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write location db log entry", "key", storedb.DATA_LOCATION, "value", temporaryLocation)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if locationSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", location, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(4, location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveGender updates the gender in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
symbol, _ := h.st.Where()
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
gender := strings.Split(symbol, "_")[1]
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||||
|
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
genderSet := h.st.MatchFlag(flag_gender_set, true)
|
||||||
|
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryGender, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryGender) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryGender is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", storedb.DATA_GENDER, "value", gender, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_GENDER, []byte(temporaryGender))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write gender db log entry", "key", storedb.DATA_TEMPORARY_VALUE, "value", temporaryGender)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if genderSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(gender))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(2, gender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input.
|
||||||
|
func (h *MenuHandlers) SaveOfferings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
offerings := string(input)
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
|
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||||
|
|
||||||
|
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||||
|
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
|
||||||
|
|
||||||
|
if allowUpdate {
|
||||||
|
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(temporaryOfferings) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "temporaryOfferings is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_OFFERINGS, []byte(temporaryOfferings))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_FIRST_NAME, []byte(temporaryOfferings))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write offerings db log entry", "key", storedb.DATA_OFFERINGS, "value", offerings)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if offeringsSet {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.profile.InsertOrShift(5, offerings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentProfileInfo retrieves specific profile fields based on the current state of the USSD session.
|
||||||
|
// Uses flag management system to track profile field status and handle menu navigation.
|
||||||
|
func (h *MenuHandlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var profileInfo []byte
|
||||||
|
var defaultValue string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||||
|
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||||
|
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||||
|
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||||
|
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||||
|
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||||
|
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_back_set)
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
language, ok := ctx.Value("Language").(lang.Language)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("value for 'Language' is not of type lang.Language")
|
||||||
|
}
|
||||||
|
code := language.Code
|
||||||
|
if code == "swa" {
|
||||||
|
defaultValue = "Haipo"
|
||||||
|
} else {
|
||||||
|
defaultValue = "Not Provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
sm, _ := h.st.Where()
|
||||||
|
parts := strings.SplitN(sm, "_", 2)
|
||||||
|
filename := parts[1]
|
||||||
|
dbKeyStr := "DATA_" + strings.ToUpper(filename)
|
||||||
|
logg.InfoCtxf(ctx, "GetCurrentProfileInfo", "filename", filename, "dbKeyStr:", dbKeyStr)
|
||||||
|
dbKey, err := storedb.StringToDataTyp(dbKeyStr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
switch dbKey {
|
||||||
|
case storedb.DATA_FIRST_NAME:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", storedb.DATA_FIRST_NAME, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
case storedb.DATA_FAMILY_NAME:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", storedb.DATA_FAMILY_NAME, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
|
||||||
|
case storedb.DATA_GENDER:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", storedb.DATA_GENDER, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
case storedb.DATA_YOB:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", storedb.DATA_YOB, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
case storedb.DATA_LOCATION:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", storedb.DATA_LOCATION, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
case storedb.DATA_OFFERINGS:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", storedb.DATA_OFFERINGS, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||||
|
res.Content = string(profileInfo)
|
||||||
|
case storedb.DATA_ACCOUNT_ALIAS:
|
||||||
|
profileInfo, err = store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
res.Content = defaultValue
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read account alias entry with", "key", "error", storedb.DATA_ACCOUNT_ALIAS, err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
alias := string(profileInfo)
|
||||||
|
if alias == "" {
|
||||||
|
res.Content = defaultValue
|
||||||
|
} else {
|
||||||
|
res.Content = alias
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileInfo provides a comprehensive view of a user's profile.
|
||||||
|
func (h *MenuHandlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var defaultValue string
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
language, ok := ctx.Value("Language").(lang.Language)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("value for 'Language' is not of type lang.Language")
|
||||||
|
}
|
||||||
|
code := language.Code
|
||||||
|
if code == "swa" {
|
||||||
|
defaultValue = "Haipo"
|
||||||
|
} else {
|
||||||
|
defaultValue = "Not Provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to handle nil byte slices and convert them to string
|
||||||
|
getEntryOrDefault := func(entry []byte, err error) string {
|
||||||
|
if err != nil || entry == nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return string(entry)
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
// Retrieve user data as strings with fallback to defaultValue
|
||||||
|
firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME))
|
||||||
|
familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME))
|
||||||
|
yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_YOB))
|
||||||
|
gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_GENDER))
|
||||||
|
location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION))
|
||||||
|
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS))
|
||||||
|
alias := getEntryOrDefault(store.ReadEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS))
|
||||||
|
|
||||||
|
if alias != defaultValue && alias != "" {
|
||||||
|
alias = strings.Split(alias, ".")[0]
|
||||||
|
} else {
|
||||||
|
alias = defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the full name
|
||||||
|
name := person.ConstructName(firstName, familyName, defaultValue)
|
||||||
|
|
||||||
|
// Calculate age from year of birth
|
||||||
|
age := defaultValue
|
||||||
|
if yob != defaultValue {
|
||||||
|
if yobInt, err := strconv.Atoi(yob); err == nil {
|
||||||
|
age = strconv.Itoa(person.CalculateAgeWithYOB(yobInt))
|
||||||
|
} else {
|
||||||
|
return res, fmt.Errorf("invalid year of birth: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch language.Code {
|
||||||
|
case "eng":
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||||
|
name, gender, age, location, offerings, alias,
|
||||||
|
)
|
||||||
|
case "swa":
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n",
|
||||||
|
name, gender, age, location, offerings, alias,
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||||
|
name, gender, age, location, offerings, alias,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handles bulk updates of profile information.
|
||||||
|
func (h *MenuHandlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||||
|
var err error
|
||||||
|
userStore := h.userdataStore
|
||||||
|
profileFlagNames := []string{
|
||||||
|
"flag_firstname_set",
|
||||||
|
"flag_familyname_set",
|
||||||
|
"flag_yob_set",
|
||||||
|
"flag_gender_set",
|
||||||
|
"flag_location_set",
|
||||||
|
"flag_offerings_set",
|
||||||
|
}
|
||||||
|
profileDataKeys := []storedb.DataTyp{
|
||||||
|
storedb.DATA_FIRST_NAME,
|
||||||
|
storedb.DATA_FAMILY_NAME,
|
||||||
|
storedb.DATA_GENDER,
|
||||||
|
storedb.DATA_YOB,
|
||||||
|
storedb.DATA_LOCATION,
|
||||||
|
storedb.DATA_OFFERINGS,
|
||||||
|
}
|
||||||
|
for index, profileItem := range h.profile.ProfileItems {
|
||||||
|
// Ensure the profileItem is not "0"(is set)
|
||||||
|
if profileItem != "0" {
|
||||||
|
flag, _ := h.flagManager.GetFlag(profileFlagNames[index])
|
||||||
|
isProfileItemSet := h.st.MatchFlag(flag, true)
|
||||||
|
if !isProfileItemSet {
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.FlagSet = append(res.FlagSet, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags.
|
||||||
|
func (h *MenuHandlers) UpdateAllProfileItems(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
err := h.insertProfileItems(ctx, sessionId, &res)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
766
handlers/application/profile_test.go
Normal file
766
handlers/application/profile_test.go
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/lang"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/state"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/profile"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSaveFirstname(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
firstName := "John"
|
||||||
|
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveFirstname(ctx, "save_firstname", []byte(firstName))
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
|
||||||
|
storedFirstName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FIRST_NAME)
|
||||||
|
assert.Equal(t, firstName, string(storedFirstName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveFamilyname(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
familyName := "Doeee"
|
||||||
|
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
st: mockState,
|
||||||
|
flagManager: fm,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveFamilyname(ctx, "save_familyname", []byte(familyName))
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
|
||||||
|
storedFamilyName, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_FAMILY_NAME)
|
||||||
|
assert.Equal(t, familyName, string(storedFamilyName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyYob(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
// Create required mocks
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format")
|
||||||
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with correct yob",
|
||||||
|
input: []byte("1980"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_incorrect_date_format},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with incorrect yob",
|
||||||
|
input: []byte("sgahaha"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_incorrect_date_format},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with numeric but less 4 digits",
|
||||||
|
input: []byte("123"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_incorrect_date_format},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Call the method under test
|
||||||
|
res, err := h.VerifyYob(ctx, "verify_yob", []byte(tt.input))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetIncorrectYob(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_incorrect_date_format, _ := fm.GetFlag("flag_incorrect_date_format")
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test incorrect yob reset",
|
||||||
|
input: []byte(""),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_incorrect_date_format},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create the MenuHandlers instance with the mock flag manager
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.ResetIncorrectYob(context.Background(), "reset_incorrect_yob", tt.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveYob(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(108)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
yob := "1980"
|
||||||
|
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_yob_set}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveYob(ctx, "save_yob", []byte(yob))
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_YOB entry has been updated with the temporary value
|
||||||
|
storedYob, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_YOB)
|
||||||
|
assert.Equal(t, yob, string(storedYob))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveLocation(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(108)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
location := "Kilifi"
|
||||||
|
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_location_set}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveLocation(ctx, "save_location", []byte(location))
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_LOCATION entry has been updated with the temporary value
|
||||||
|
storedLocation, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_LOCATION)
|
||||||
|
assert.Equal(t, location, string(storedLocation))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveGender(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(108)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedGender string
|
||||||
|
executingSymbol string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid Male Input",
|
||||||
|
input: []byte("1"),
|
||||||
|
expectedGender: "male",
|
||||||
|
executingSymbol: "set_male",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Female Input",
|
||||||
|
input: []byte("2"),
|
||||||
|
expectedGender: "female",
|
||||||
|
executingSymbol: "set_female",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Unspecified Input",
|
||||||
|
input: []byte("3"),
|
||||||
|
executingSymbol: "set_unspecified",
|
||||||
|
expectedGender: "unspecified",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockState.ExecPath = append(mockState.ExecPath, tt.executingSymbol)
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
st: mockState,
|
||||||
|
flagManager: fm,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveGender(ctx, "save_gender", tt.input)
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_gender_set}
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_GENDER entry has been updated with the temporary value
|
||||||
|
storedGender, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_GENDER)
|
||||||
|
assert.Equal(t, tt.expectedGender, string(storedGender))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveOfferings(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
|
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||||
|
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||||
|
|
||||||
|
// Set the flag in the State
|
||||||
|
mockState := state.NewState(108)
|
||||||
|
mockState.SetFlag(flag_allow_update)
|
||||||
|
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
offerings := "Bananas"
|
||||||
|
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult.FlagSet = []uint32{flag_offerings_set}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.SaveOfferings(ctx, "save_offerings", []byte(offerings))
|
||||||
|
|
||||||
|
// Assert results
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
|
||||||
|
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value
|
||||||
|
storedOfferings, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_OFFERINGS)
|
||||||
|
assert.Equal(t, offerings, string(storedOfferings))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCurrentProfileInfo(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||||
|
flag_familyname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||||
|
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||||
|
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||||
|
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||||
|
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||||
|
flag_back_set, _ := fm.GetFlag("flag_back_set")
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
st: state.NewState(16),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
execPath string
|
||||||
|
dbKey storedb.DataTyp
|
||||||
|
value string
|
||||||
|
expected resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test fetching first name",
|
||||||
|
execPath: "edit_first_name",
|
||||||
|
dbKey: storedb.DATA_FIRST_NAME,
|
||||||
|
value: "John",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_firstname_set},
|
||||||
|
Content: "John",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test fetching family name",
|
||||||
|
execPath: "edit_family_name",
|
||||||
|
dbKey: storedb.DATA_FAMILY_NAME,
|
||||||
|
value: "Doe",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_familyname_set},
|
||||||
|
Content: "Doe",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test fetching year of birth",
|
||||||
|
execPath: "edit_yob",
|
||||||
|
dbKey: storedb.DATA_YOB,
|
||||||
|
value: "1980",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_yob_set},
|
||||||
|
Content: "1980",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test fetching gender",
|
||||||
|
execPath: "edit_gender",
|
||||||
|
dbKey: storedb.DATA_GENDER,
|
||||||
|
value: "Male",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_gender_set},
|
||||||
|
Content: "Male",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test fetching location",
|
||||||
|
execPath: "edit_location",
|
||||||
|
dbKey: storedb.DATA_LOCATION,
|
||||||
|
value: "Nairobi",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_location_set},
|
||||||
|
Content: "Nairobi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test fetching offerings",
|
||||||
|
execPath: "edit_offerings",
|
||||||
|
dbKey: storedb.DATA_OFFERINGS,
|
||||||
|
value: "Fruits",
|
||||||
|
expected: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_back_set},
|
||||||
|
FlagSet: []uint32{flag_offerings_set},
|
||||||
|
Content: "Fruits",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||||
|
Code: "eng",
|
||||||
|
})
|
||||||
|
// Set ExecPath to include tt.execPath
|
||||||
|
h.st.ExecPath = []string{tt.execPath}
|
||||||
|
|
||||||
|
if tt.value != "" {
|
||||||
|
err := store.WriteEntry(ctx, sessionId, tt.dbKey, []byte(tt.value))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.GetCurrentProfileInfo(ctx, tt.execPath, []byte(""))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expected, res, "Result should match the expected output")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetProfileInfo(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
mockState := state.NewState(16)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
st: mockState,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
languageCode string
|
||||||
|
keys []storedb.DataTyp
|
||||||
|
profileInfo []string
|
||||||
|
result resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with full profile information in eng",
|
||||||
|
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||||
|
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||||
|
languageCode: "eng",
|
||||||
|
result: resource.Result{
|
||||||
|
Content: fmt.Sprintf(
|
||||||
|
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||||
|
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with with profile information in swa",
|
||||||
|
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||||
|
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||||
|
languageCode: "swa",
|
||||||
|
result: resource.Result{
|
||||||
|
Content: fmt.Sprintf(
|
||||||
|
"Jina: %s\nJinsia: %s\nUmri: %s\nEneo: %s\nUnauza: %s\nLakabu yako: %s\n",
|
||||||
|
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with with profile information with language that is not yet supported",
|
||||||
|
keys: []storedb.DataTyp{storedb.DATA_FAMILY_NAME, storedb.DATA_FIRST_NAME, storedb.DATA_GENDER, storedb.DATA_OFFERINGS, storedb.DATA_LOCATION, storedb.DATA_YOB, storedb.DATA_ACCOUNT_ALIAS},
|
||||||
|
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976", "DoeJohn"},
|
||||||
|
languageCode: "nor",
|
||||||
|
result: resource.Result{
|
||||||
|
Content: fmt.Sprintf(
|
||||||
|
"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\nYour alias: %s\n",
|
||||||
|
"John Doee", "Male", "49", "Kilifi", "Bananas", "DoeJohn",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||||
|
Code: tt.languageCode,
|
||||||
|
})
|
||||||
|
for index, key := range tt.keys {
|
||||||
|
err := store.WriteEntry(ctx, sessionId, key, []byte(tt.profileInfo[index]))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := h.GetProfileInfo(ctx, "get_profile_info", []byte(""))
|
||||||
|
|
||||||
|
//Assert that the result set to content is what was expected
|
||||||
|
assert.Equal(t, res, tt.result, "Result should contain profile information served back to user")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertProfileItems(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profileDataKeys := []storedb.DataTyp{
|
||||||
|
storedb.DATA_FIRST_NAME,
|
||||||
|
storedb.DATA_FAMILY_NAME,
|
||||||
|
storedb.DATA_GENDER,
|
||||||
|
storedb.DATA_YOB,
|
||||||
|
storedb.DATA_LOCATION,
|
||||||
|
storedb.DATA_OFFERINGS,
|
||||||
|
}
|
||||||
|
|
||||||
|
profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
profile: &profile.Profile{
|
||||||
|
ProfileItems: profileItems,
|
||||||
|
Max: 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &resource.Result{}
|
||||||
|
err = h.insertProfileItems(ctx, sessionId, res)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Loop through profileDataKeys to validate stored values
|
||||||
|
for i, key := range profileDataKeys {
|
||||||
|
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, profileItems[i], string(storedValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateAllProfileItems(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
mockState := state.NewState(128)
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||||
|
flag_familyname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||||
|
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||||
|
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||||
|
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||||
|
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||||
|
|
||||||
|
profileDataKeys := []storedb.DataTyp{
|
||||||
|
storedb.DATA_FIRST_NAME,
|
||||||
|
storedb.DATA_FAMILY_NAME,
|
||||||
|
storedb.DATA_GENDER,
|
||||||
|
storedb.DATA_YOB,
|
||||||
|
storedb.DATA_LOCATION,
|
||||||
|
storedb.DATA_OFFERINGS,
|
||||||
|
}
|
||||||
|
|
||||||
|
profileItems := []string{"John", "Doe", "Male", "1990", "Nairobi", "Software"}
|
||||||
|
|
||||||
|
expectedResult := resource.Result{
|
||||||
|
FlagSet: []uint32{
|
||||||
|
flag_firstname_set,
|
||||||
|
flag_familyname_set,
|
||||||
|
flag_yob_set,
|
||||||
|
flag_gender_set,
|
||||||
|
flag_location_set,
|
||||||
|
flag_offerings_set,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
st: mockState,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
profile: &profile.Profile{
|
||||||
|
ProfileItems: profileItems,
|
||||||
|
Max: 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Call the function under test
|
||||||
|
res, err := h.UpdateAllProfileItems(ctx, "symbol", nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Loop through profileDataKeys to validate stored values
|
||||||
|
for i, key := range profileDataKeys {
|
||||||
|
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, profileItems[i], string(storedValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
}
|
||||||
89
handlers/application/registration.go
Normal file
89
handlers/application/registration.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/hex"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// handles the account creation when no existing account is present for the session and stores associated data in the user data store.
|
||||||
|
func (h *MenuHandlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||||
|
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||||
|
flag_account_creation_failed, _ := h.flagManager.GetFlag("flag_account_creation_failed")
|
||||||
|
|
||||||
|
r, err := h.accountService.CreateAccount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_creation_failed)
|
||||||
|
logg.ErrorCtxf(ctx, "failed to create an account", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_creation_failed)
|
||||||
|
|
||||||
|
trackingId := r.TrackingId
|
||||||
|
publicKey := r.PublicKey
|
||||||
|
|
||||||
|
data := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_TRACKING_ID: trackingId,
|
||||||
|
storedb.DATA_PUBLIC_KEY: publicKey,
|
||||||
|
storedb.DATA_ACCOUNT_ALIAS: "",
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
for key, value := range data {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write log entry", "key", key, "value", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publicKeyNormalized, err := hex.NormalizeHex(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, publicKeyNormalized, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write log entry", "key", storedb.DATA_PUBLIC_KEY_REVERSE, "value", sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAccount checks if any account exists on the JSON data file, and if not,
|
||||||
|
// creates an account on the API,
|
||||||
|
// sets the default values and flags.
|
||||||
|
func (h *MenuHandlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
_, err = store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
|
||||||
|
err = h.createAccountNoExist(ctx, sessionId, &res)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on createAccountNoExist", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
76
handlers/application/registration_test.go
Normal file
76
handlers/application/registration_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateAccount(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_account_created, err := fm.GetFlag("flag_account_created")
|
||||||
|
flag_account_creation_failed, _ := fm.GetFlag("flag_account_creation_failed")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
serverResponse *models.AccountResult
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test account creation success",
|
||||||
|
serverResponse: &models.AccountResult{
|
||||||
|
TrackingId: "1234567890",
|
||||||
|
PublicKey: "0xD3adB33f",
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_account_created},
|
||||||
|
FlagReset: []uint32{flag_account_creation_failed},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
logDb: logDb,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("CreateAccount").Return(tt.serverResponse, nil)
|
||||||
|
|
||||||
|
// Call the method you want to test
|
||||||
|
res, err := h.CreateAccount(ctx, "create_account", []byte(""))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
380
handlers/application/send.go
Normal file
380
handlers/application/send.go
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/identity"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/grassrootseconomics/ethutils"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateRecipient validates that the given input is valid.
|
||||||
|
//
|
||||||
|
// TODO: split up functino
|
||||||
|
func (h *MenuHandlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var AliasAddressResult string
|
||||||
|
var AliasAddress *models.AliasAddress
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
|
||||||
|
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
// remove white spaces
|
||||||
|
recipient := strings.ReplaceAll(string(input), " ", "")
|
||||||
|
|
||||||
|
if recipient != "0" {
|
||||||
|
recipientType, err := identity.CheckRecipient(recipient)
|
||||||
|
if err != nil {
|
||||||
|
// Invalid recipient format (not a phone number, address, or valid alias format)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
|
||||||
|
res.Content = recipient
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the recipient as the temporaryRecipient
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recipient))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write temporaryRecipient entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", recipient, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch recipientType {
|
||||||
|
case "phone number":
|
||||||
|
// format the phone number
|
||||||
|
formattedNumber, err := phone.FormatPhoneNumber(recipient)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the phone number is registered
|
||||||
|
publicKey, err := store.ReadEntry(ctx, formattedNumber, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
|
||||||
|
res.Content = recipient
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the publicKey as the recipient
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", string(publicKey), "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "address":
|
||||||
|
// checksum the address
|
||||||
|
address := ethutils.ChecksumAddress(recipient)
|
||||||
|
|
||||||
|
// Save the valid Ethereum address as the recipient
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(address))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", recipient, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case "alias":
|
||||||
|
if strings.Contains(recipient, ".") {
|
||||||
|
AliasAddress, err = h.accountService.CheckAliasAddress(ctx, recipient)
|
||||||
|
if err == nil {
|
||||||
|
AliasAddressResult = AliasAddress.Address
|
||||||
|
} else {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Perform a search for each search domain,break on first match
|
||||||
|
for _, domain := range config.SearchDomains() {
|
||||||
|
fqdn := fmt.Sprintf("%s.%s", recipient, domain)
|
||||||
|
logg.InfoCtxf(ctx, "Resolving with fqdn alias", "alias", fqdn)
|
||||||
|
AliasAddress, err = h.accountService.CheckAliasAddress(ctx, fqdn)
|
||||||
|
if err == nil {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
AliasAddressResult = AliasAddress.Address
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", recipient, "error_alias_check", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if AliasAddressResult == "" {
|
||||||
|
res.Content = recipient
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
|
||||||
|
return res, nil
|
||||||
|
} else {
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(AliasAddressResult))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", storedb.DATA_RECIPIENT, "value", AliasAddressResult, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionReset resets the previous transaction data (Recipient and Amount)
|
||||||
|
// as well as the invalid flags.
|
||||||
|
func (h *MenuHandlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
|
||||||
|
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
|
||||||
|
store := h.userdataStore
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(""))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(""))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetTransactionAmount resets the transaction amount and invalid flag.
|
||||||
|
func (h *MenuHandlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||||
|
store := h.userdataStore
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(""))
|
||||||
|
if err != nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_invalid_amount)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxAmount gets the current balance from the API and sets it as
|
||||||
|
// the result content.
|
||||||
|
func (h *MenuHandlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
activeBal, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = string(activeBal)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAmount ensures that the given input is a valid amount and that
|
||||||
|
// it is not more than the current balance.
|
||||||
|
func (h *MenuHandlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
var balanceValue float64
|
||||||
|
|
||||||
|
// retrieve the active balance
|
||||||
|
activeBal, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", storedb.DATA_ACTIVE_BAL, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
balanceValue, err = strconv.ParseFloat(string(activeBal), 64)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract numeric part from the input amount
|
||||||
|
amountStr := strings.TrimSpace(string(input))
|
||||||
|
inputAmount, err := strconv.ParseFloat(amountStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||||
|
res.Content = amountStr
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputAmount > balanceValue {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||||
|
res.Content = amountStr
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the amount to 2 decimal places before saving (truncated)
|
||||||
|
formattedAmount, err := store.TruncateDecimalString(amountStr, 2)
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||||
|
res.Content = amountStr
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(formattedAmount))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", storedb.DATA_AMOUNT, "value", formattedAmount, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = formattedAmount
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecipient returns the transaction recipient phone number from the gdbm.
|
||||||
|
func (h *MenuHandlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
recipient, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if len(recipient) == 0 {
|
||||||
|
logg.ErrorCtxf(ctx, "recipient is empty", "key", storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
return res, fmt.Errorf("Data error encountered")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = string(recipient)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSender returns the sessionId (phoneNumber).
|
||||||
|
func (h *MenuHandlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = sessionId
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAmount retrieves the amount from teh Gdbm Db.
|
||||||
|
func (h *MenuHandlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
|
||||||
|
// retrieve the active symbol
|
||||||
|
activeSym, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, _ := store.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT)
|
||||||
|
|
||||||
|
res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym))
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitiateTransaction calls the TokenTransfer and returns a confirmation based on the result.
|
||||||
|
func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
var err error
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
data, err := store.ReadTransactionData(ctx, h.userdataStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalAmountStr, err := store.ParseAndScaleAmount(data.Amount, data.ActiveDecimal)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call TokenTransfer
|
||||||
|
r, err := h.accountService.TokenTransfer(ctx, finalAmountStr, data.PublicKey, data.Recipient, data.ActiveAddress)
|
||||||
|
if err != nil {
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
res.Content = l.Get("Your request failed. Please try again later.")
|
||||||
|
logg.ErrorCtxf(ctx, "failed on TokenTransfer", "error", err)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trackingId := r.TrackingId
|
||||||
|
logg.InfoCtxf(ctx, "TokenTransfer", "trackingId", trackingId)
|
||||||
|
|
||||||
|
res.Content = l.Get(
|
||||||
|
"Your request has been sent. %s will receive %s %s from %s.",
|
||||||
|
data.TemporaryValue,
|
||||||
|
data.Amount,
|
||||||
|
data.ActiveSym,
|
||||||
|
sessionId,
|
||||||
|
)
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
550
handlers/application/send_test.go
Normal file
550
handlers/application/send_test.go
Normal file
@@ -0,0 +1,550 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidateRecipient(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient")
|
||||||
|
flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite")
|
||||||
|
|
||||||
|
// Define test cases
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectError bool
|
||||||
|
expectedRecipient []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with invalid recepient",
|
||||||
|
input: []byte("7?1234"),
|
||||||
|
expectError: true,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_invalid_recipient},
|
||||||
|
Content: "7?1234",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with valid unregistered recepient",
|
||||||
|
input: []byte("0712345678"),
|
||||||
|
expectError: true,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_invalid_recipient_with_invite},
|
||||||
|
Content: "0712345678",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with valid registered recepient",
|
||||||
|
input: []byte("0711223344"),
|
||||||
|
expectError: false,
|
||||||
|
expectedRecipient: []byte(publicKey),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with address",
|
||||||
|
input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||||
|
expectError: false,
|
||||||
|
expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with alias recepient",
|
||||||
|
input: []byte("alias123.sarafu.local"),
|
||||||
|
expectError: false,
|
||||||
|
expectedRecipient: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test for checksummed address",
|
||||||
|
input: []byte("0x5523058cdffe5f3c1eadadd5015e55c6e00fb439"),
|
||||||
|
expectError: false,
|
||||||
|
expectedRecipient: []byte("0x5523058cdFfe5F3c1EaDADD5015E55C6E00fb439"),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with valid registered recepient that has white spaces",
|
||||||
|
input: []byte("0711 22 33 44"),
|
||||||
|
expectError: false,
|
||||||
|
expectedRecipient: []byte(publicKey),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// store a public key for the valid recipient
|
||||||
|
err = store.WriteEntry(ctx, "+254711223344", storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
// Create the MenuHandlers instance
|
||||||
|
h := &MenuHandlers{
|
||||||
|
flagManager: fm,
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
}
|
||||||
|
|
||||||
|
aliasResponse := &models.AliasAddress{
|
||||||
|
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.expectError {
|
||||||
|
storedRecipientAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_RECIPIENT)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedRecipient, storedRecipientAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the Result FlagSet has the required flags after language switch
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Result should contain flag(s) that have been reset")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransactionReset(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_recipient, _ := fm.GetFlag("flag_invalid_recipient")
|
||||||
|
flag_invalid_recipient_with_invite, _ := fm.GetFlag("flag_invalid_recipient_with_invite")
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
status string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test transaction reset for amount and recipient",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_invalid_recipient, flag_invalid_recipient_with_invite},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Call the method under test
|
||||||
|
res, _ := h.TransactionReset(ctx, "transaction_reset", tt.input)
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetTransactionAmount(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount")
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test amount reset",
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_invalid_amount},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Call the method under test
|
||||||
|
res, _ := h.ResetTransactionAmount(ctx, "transaction_reset_amount", []byte(""))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxAmount(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
activeBal := "500"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sessionId string
|
||||||
|
activeBal string
|
||||||
|
expectedError bool
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid session ID and active balance",
|
||||||
|
sessionId: sessionId,
|
||||||
|
activeBal: activeBal,
|
||||||
|
expectedError: false,
|
||||||
|
expectedResult: resource.Result{Content: activeBal},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Missing Session ID",
|
||||||
|
sessionId: "",
|
||||||
|
activeBal: activeBal,
|
||||||
|
expectedError: true,
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Failed to Read Active Balance",
|
||||||
|
sessionId: sessionId,
|
||||||
|
activeBal: "", // failure to read active balance
|
||||||
|
expectedError: true,
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
if tt.sessionId != "" {
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", tt.sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write active balance to the store only if it's not empty
|
||||||
|
if tt.activeBal != "" {
|
||||||
|
err := userStore.WriteEntry(ctx, tt.sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.MaxAmount(ctx, "max_amount", []byte(""))
|
||||||
|
|
||||||
|
if tt.expectedError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedResult, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAmount(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
flag_invalid_amount, _ := fm.GetFlag("flag_invalid_amount")
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
activeBal []byte
|
||||||
|
balance string
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test with valid amount",
|
||||||
|
input: []byte("4.10"),
|
||||||
|
activeBal: []byte("5"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "4.10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with amount larger than active balance",
|
||||||
|
input: []byte("5.02"),
|
||||||
|
activeBal: []byte("5"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_invalid_amount},
|
||||||
|
Content: "5.02",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with invalid amount format",
|
||||||
|
input: []byte("0.02ms"),
|
||||||
|
activeBal: []byte("5"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_invalid_amount},
|
||||||
|
Content: "0.02ms",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with valid decimal amount",
|
||||||
|
input: []byte("0.149"),
|
||||||
|
activeBal: []byte("5"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "0.14",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Test with valid large decimal amount",
|
||||||
|
input: []byte("1.8599999999"),
|
||||||
|
activeBal: []byte("5"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "1.85",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(tt.activeBal))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method under test
|
||||||
|
res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input)
|
||||||
|
|
||||||
|
// Assert no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Assert the result matches the expected result
|
||||||
|
assert.Equal(t, tt.expectedResult, res, "Expected result should match actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRecipient(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
recepient := "0712345678"
|
||||||
|
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(recepient))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, _ := h.GetRecipient(ctx, "get_recipient", []byte(""))
|
||||||
|
|
||||||
|
//Assert that the retrieved recepient is what was set as the content
|
||||||
|
assert.Equal(t, recepient, res.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSender(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, _ := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance
|
||||||
|
h := &MenuHandlers{}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, _ := h.GetSender(ctx, "get_sender", []byte(""))
|
||||||
|
|
||||||
|
//Assert that the sessionId is what was set as the result content.
|
||||||
|
assert.Equal(t, sessionId, res.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAmount(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Define test data
|
||||||
|
amount := "0.03"
|
||||||
|
activeSym := "SRF"
|
||||||
|
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(amount))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(activeSym))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the MenuHandlers instance with the mock store
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the method
|
||||||
|
res, _ := h.GetAmount(ctx, "get_amount", []byte(""))
|
||||||
|
|
||||||
|
formattedAmount := fmt.Sprintf("%s %s", amount, activeSym)
|
||||||
|
|
||||||
|
//Assert that the retrieved amount is what was set as the content
|
||||||
|
assert.Equal(t, formattedAmount, res.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitiateTransaction(t *testing.T) {
|
||||||
|
sessionId := "254712345678"
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
account_authorized_flag, _ := fm.GetFlag("flag_account_authorized")
|
||||||
|
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
TemporaryValue []byte
|
||||||
|
ActiveSym []byte
|
||||||
|
StoredAmount []byte
|
||||||
|
TransferAmount string
|
||||||
|
PublicKey []byte
|
||||||
|
Recipient []byte
|
||||||
|
ActiveDecimal []byte
|
||||||
|
ActiveAddress []byte
|
||||||
|
TransferResponse *models.TokenTransferResponse
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Test initiate transaction",
|
||||||
|
TemporaryValue: []byte("0711223344"),
|
||||||
|
ActiveSym: []byte("SRF"),
|
||||||
|
StoredAmount: []byte("1.00"),
|
||||||
|
TransferAmount: "1000000",
|
||||||
|
PublicKey: []byte("0X13242618721"),
|
||||||
|
Recipient: []byte("0x12415ass27192"),
|
||||||
|
ActiveDecimal: []byte("6"),
|
||||||
|
ActiveAddress: []byte("0xd4c288865Ce"),
|
||||||
|
TransferResponse: &models.TokenTransferResponse{
|
||||||
|
TrackingId: "1234567890",
|
||||||
|
},
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{account_authorized_flag},
|
||||||
|
Content: "Your request has been sent. 0711223344 will receive 1.00 SRF from 254712345678.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(tt.TemporaryValue))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.ActiveSym))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(tt.StoredAmount))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(tt.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_RECIPIENT, []byte(tt.Recipient))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(tt.ActiveDecimal))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tt.ActiveAddress))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("TokenTransfer").Return(tt.TransferResponse, nil)
|
||||||
|
|
||||||
|
// Call the method under test
|
||||||
|
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", []byte(""))
|
||||||
|
|
||||||
|
// Assert that no errors occurred
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
//Assert that the account created flag has been set to the result
|
||||||
|
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
190
handlers/application/transactions.go
Normal file
190
handlers/application/transactions.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb.
|
||||||
|
func (h *MenuHandlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch transactions from the API using the public key
|
||||||
|
transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
|
||||||
|
// Return if there are no transactions
|
||||||
|
if len(transactionsResp) == 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_no_transfers)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := store.ProcessTransfers(transactionsResp)
|
||||||
|
|
||||||
|
// Store all transaction data
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_TX_SENDERS: data.Senders,
|
||||||
|
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||||
|
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||||
|
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||||
|
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||||
|
storedb.DATA_TX_DATES: data.Dates,
|
||||||
|
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||||
|
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write tx db log entry", "key", key, "value", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_no_transfers)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionsList fetches the list of transactions and formats them.
|
||||||
|
func (h *MenuHandlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read transactions from the store and format them
|
||||||
|
TransactionSenders, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
TransactionSyms, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SYMBOLS))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
TransactionValues, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_VALUES))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
TransactionDates, err := h.prefixDb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_DATES))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the data
|
||||||
|
senders := strings.Split(string(TransactionSenders), "\n")
|
||||||
|
syms := strings.Split(string(TransactionSyms), "\n")
|
||||||
|
values := strings.Split(string(TransactionValues), "\n")
|
||||||
|
dates := strings.Split(string(TransactionDates), "\n")
|
||||||
|
|
||||||
|
var formattedTransactions []string
|
||||||
|
for i := 0; i < len(senders); i++ {
|
||||||
|
sender := strings.TrimSpace(senders[i])
|
||||||
|
sym := strings.TrimSpace(syms[i])
|
||||||
|
value := strings.TrimSpace(values[i])
|
||||||
|
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
|
||||||
|
|
||||||
|
status := "Received"
|
||||||
|
if sender == string(publicKey) {
|
||||||
|
status = "Sent"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the ReplaceSeparator function for the menu separator
|
||||||
|
transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date)
|
||||||
|
formattedTransactions = append(formattedTransactions, transactionLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = strings.Join(formattedTransactions, "\n")
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewTransactionStatement retrieves the transaction statement
|
||||||
|
// and displays it to the user.
|
||||||
|
func (h *MenuHandlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
userStore := h.userdataStore
|
||||||
|
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement")
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert input string to integer
|
||||||
|
index, err := strconv.Atoi(strings.TrimSpace(inputStr))
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("invalid input: must be a number between 1 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < 1 || index > 10 {
|
||||||
|
return res, fmt.Errorf("invalid input: index must be between 1 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
statement, err := store.GetTransferData(ctx, h.prefixDb, string(publicKey), index)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to retrieve transfer data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if statement == "" {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_statement)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||||
|
res.Content = statement
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
265
handlers/application/transactions_test.go
Normal file
265
handlers/application/transactions_test.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckTransactions(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
prefixDb: spdb,
|
||||||
|
logDb: logDb,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||||
|
{
|
||||||
|
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "100", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0x123wefsf34rf", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "200", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0xq34wresfdb44", DateBlock: time.Now(), TokenSymbol: "SRF", TokenDecimals: "6",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedSenders := []byte("0X13242618721\n0x41c188d63Qa")
|
||||||
|
|
||||||
|
mockAccountService.On("FetchTransactions", string(publicKey)).Return(mockTXResponse, nil)
|
||||||
|
|
||||||
|
_, err = h.CheckTransactions(ctx, "check_transactions", []byte(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Read tranfers senders data from the store
|
||||||
|
senderData, err := spdb.Get(ctx, storedb.ToBytes(storedb.DATA_TX_SENDERS))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert that the data is stored correctly
|
||||||
|
assert.Equal(t, expectedSenders, senderData)
|
||||||
|
|
||||||
|
mockAccountService.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTransactionsList(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||||
|
|
||||||
|
// Initialize MenuHandlers
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
prefixDb: spdb,
|
||||||
|
ReplaceSeparatorFunc: mockReplaceSeparator,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||||
|
{
|
||||||
|
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data := store.ProcessTransfers(mockTXResponse)
|
||||||
|
|
||||||
|
// Store all transaction data
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_TX_SENDERS: data.Senders,
|
||||||
|
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||||
|
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||||
|
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||||
|
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||||
|
storedb.DATA_TX_DATES: data.Dates,
|
||||||
|
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||||
|
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedTransactionList := []byte("1: Sent 10 SRF 2024-10-03\n2: Received 20 SRF 2024-10-03")
|
||||||
|
|
||||||
|
res, err := h.GetTransactionsList(ctx, "", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, res.Content, string(expectedTransactionList))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestViewTransactionStatement(t *testing.T) {
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
flag_incorrect_statement, _ := fm.GetFlag("flag_incorrect_statement")
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
prefixDb: spdb,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dateBlock, err := time.Parse(time.RFC3339, "2024-10-03T07:23:12Z")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTXResponse := []dataserviceapi.Last10TxResponse{
|
||||||
|
{
|
||||||
|
Sender: "0X13242618721", Recipient: "0x41c188d63Qa", TransferValue: "1000", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0x123wefsf34rf", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Sender: "0x41c188d63Qa", Recipient: "0X13242618721", TransferValue: "2000", ContractAddress: "0X1324262343rfdGW23",
|
||||||
|
TxHash: "0xq34wresfdb44", DateBlock: dateBlock, TokenSymbol: "SRF", TokenDecimals: "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data := store.ProcessTransfers(mockTXResponse)
|
||||||
|
|
||||||
|
// Store all transaction data
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_TX_SENDERS: data.Senders,
|
||||||
|
storedb.DATA_TX_RECIPIENTS: data.Recipients,
|
||||||
|
storedb.DATA_TX_VALUES: data.TransferValues,
|
||||||
|
storedb.DATA_TX_ADDRESSES: data.Addresses,
|
||||||
|
storedb.DATA_TX_HASHES: data.TxHashes,
|
||||||
|
storedb.DATA_TX_DATES: data.Dates,
|
||||||
|
storedb.DATA_TX_SYMBOLS: data.Symbols,
|
||||||
|
storedb.DATA_TX_DECIMALS: data.Decimals,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := h.prefixDb.Put(ctx, []byte(storedb.ToBytes(key)), []byte(value)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
expectedError error
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid input - index 1",
|
||||||
|
input: []byte("1"),
|
||||||
|
expectedError: nil,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "Sent 10 SRF\nTo: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0x123wefsf34rf\nDate: 2024-10-03 07:23:12 AM",
|
||||||
|
FlagReset: []uint32{flag_incorrect_statement},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid input - index 2",
|
||||||
|
input: []byte("2"),
|
||||||
|
expectedError: nil,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
Content: "Received 20 SRF\nFrom: 0x41c188d63Qa\nContract address: 0X1324262343rfdGW23\nTxhash: 0xq34wresfdb44\nDate: 2024-10-03 07:23:12 AM",
|
||||||
|
FlagReset: []uint32{flag_incorrect_statement},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid input - index 0",
|
||||||
|
input: []byte("0"),
|
||||||
|
expectedError: nil,
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_incorrect_statement},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid input - index 12",
|
||||||
|
input: []byte("12"),
|
||||||
|
expectedError: fmt.Errorf("invalid input: index must be between 1 and 10"),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid input - non-numeric",
|
||||||
|
input: []byte("abc"),
|
||||||
|
expectedError: fmt.Errorf("invalid input: must be a number between 1 and 10"),
|
||||||
|
expectedResult: resource.Result{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
res, err := h.ViewTransactionStatement(ctx, "view_transaction_statement", tt.input)
|
||||||
|
|
||||||
|
if tt.expectedError != nil {
|
||||||
|
assert.EqualError(t, err, tt.expectedError.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedResult, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
46
handlers/application/upsell.go
Normal file
46
handlers/application/upsell.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InviteValidRecipient sends an invitation to the valid phone number.
|
||||||
|
func (h *MenuHandlers) InviteValidRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
store := h.userdataStore
|
||||||
|
smsservice := h.smsService
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
recipient, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read invalid recipient info", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !phone.IsValidPhoneNumber(string(recipient)) {
|
||||||
|
logg.InfoCtxf(ctx, "corrupted recipient", "key", storedb.DATA_TEMPORARY_VALUE, "recipient", recipient)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = smsservice.Accountservice.SendUpsellSMS(ctx, sessionId, string(recipient))
|
||||||
|
if err != nil {
|
||||||
|
res.Content = l.Get("Your invite request for %s to Sarafu Network failed. Please try again later.", string(recipient))
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.Content = l.Get("Your invitation to %s to join Sarafu Network has been sent.", string(recipient))
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
297
handlers/application/vouchers.go
Normal file
297
handlers/application/vouchers.go
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
"gopkg.in/leonelquinteros/gotext.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and
|
||||||
|
// 1. sets the first as the default voucher if no active voucher is set.
|
||||||
|
// 2. Stores list of vouchers
|
||||||
|
// 3. updates the balance of the active voucher
|
||||||
|
func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
userStore := h.userdataStore
|
||||||
|
logdb := h.logDb
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch vouchers from API
|
||||||
|
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
|
||||||
|
if len(vouchersResp) == 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_no_active_voucher)
|
||||||
|
|
||||||
|
// add a variable to filter out the active voucher
|
||||||
|
activeSymStr := ""
|
||||||
|
|
||||||
|
// Check if user has an active voucher with proper error handling
|
||||||
|
activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM)
|
||||||
|
if err != nil {
|
||||||
|
if db.IsNotFound(err) {
|
||||||
|
// No active voucher, set the first one as default
|
||||||
|
firstVoucher := vouchersResp[0]
|
||||||
|
defaultSym := firstVoucher.TokenSymbol
|
||||||
|
defaultBal := firstVoucher.Balance
|
||||||
|
defaultDec := firstVoucher.TokenDecimals
|
||||||
|
defaultAddr := firstVoucher.TokenAddress
|
||||||
|
|
||||||
|
activeSymStr = defaultSym
|
||||||
|
|
||||||
|
// Scale down the balance
|
||||||
|
scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec)
|
||||||
|
|
||||||
|
firstVoucherMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_ACTIVE_SYM: defaultSym,
|
||||||
|
storedb.DATA_ACTIVE_BAL: scaledBalance,
|
||||||
|
storedb.DATA_ACTIVE_DECIMAL: defaultDec,
|
||||||
|
storedb.DATA_ACTIVE_ADDRESS: defaultAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range firstVoucherMap {
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
err = logdb.WriteLogEntry(ctx, sessionId, key, []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to write voucher db log entry", "key", key, "value", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr)
|
||||||
|
} else {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Find the matching voucher data
|
||||||
|
activeSymStr = string(activeSym)
|
||||||
|
var activeData *dataserviceapi.TokenHoldings
|
||||||
|
for _, voucher := range vouchersResp {
|
||||||
|
if voucher.TokenSymbol == activeSymStr {
|
||||||
|
activeData = &voucher
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if activeData == nil {
|
||||||
|
logg.InfoCtxf(ctx, "activeSym not found in vouchers, setting the first voucher as the default", "activeSym", activeSymStr)
|
||||||
|
firstVoucher := vouchersResp[0]
|
||||||
|
activeData = &firstVoucher
|
||||||
|
activeSymStr = string(activeData.TokenSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale down the balance
|
||||||
|
scaledBalance := store.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals)
|
||||||
|
|
||||||
|
// Update the balance field with the scaled value
|
||||||
|
activeData.Balance = scaledBalance
|
||||||
|
|
||||||
|
// Pass the matching voucher data to UpdateVoucherData
|
||||||
|
if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the active voucher from vouchersResp
|
||||||
|
filteredVouchers := make([]dataserviceapi.TokenHoldings, 0, len(vouchersResp))
|
||||||
|
for _, v := range vouchersResp {
|
||||||
|
if v.TokenSymbol != activeSymStr {
|
||||||
|
filteredVouchers = append(filteredVouchers, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store all voucher data (excluding the current active voucher)
|
||||||
|
data := store.ProcessVouchers(filteredVouchers)
|
||||||
|
|
||||||
|
dataMap := map[storedb.DataTyp]string{
|
||||||
|
storedb.DATA_VOUCHER_SYMBOLS: data.Symbols,
|
||||||
|
storedb.DATA_VOUCHER_BALANCES: data.Balances,
|
||||||
|
storedb.DATA_VOUCHER_DECIMALS: data.Decimals,
|
||||||
|
storedb.DATA_VOUCHER_ADDRESSES: data.Addresses,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data entries
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVoucherList fetches the list of vouchers from the store and formats them.
|
||||||
|
func (h *MenuHandlers) GetVoucherList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore := h.userdataStore
|
||||||
|
|
||||||
|
// Read vouchers from the store
|
||||||
|
voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
|
||||||
|
logg.InfoCtxf(ctx, "reading voucherData in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_SYMBOLS, "voucherData", voucherData)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_SYMBOLS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
voucherBalances, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES)
|
||||||
|
logg.InfoCtxf(ctx, "reading voucherBalances in GetVoucherList", "sessionId", sessionId, "key", storedb.DATA_VOUCHER_BALANCES, "voucherBalances", voucherBalances)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read voucherData entires with", "key", storedb.DATA_VOUCHER_BALANCES, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedVoucherList := store.FormatVoucherList(ctx, string(voucherData), string(voucherBalances))
|
||||||
|
finalOutput := strings.Join(formattedVoucherList, "\n")
|
||||||
|
|
||||||
|
logg.InfoCtxf(ctx, "final output for GetVoucherList", "sessionId", sessionId, "finalOutput", finalOutput)
|
||||||
|
|
||||||
|
res.Content = finalOutput
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewVoucher retrieves the token holding and balance from the subprefixDB
|
||||||
|
// and displays it to the user for them to select it.
|
||||||
|
func (h *MenuHandlers) ViewVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
|
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "88" || inputStr == "98" {
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := store.GetVoucherData(ctx, h.userdataStore, sessionId, inputStr)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata == nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the balance to 2 decimal places
|
||||||
|
formattedAmount, err := store.TruncateDecimalString(metadata.Balance, 2)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to TruncateDecimalString on ViewVoucher", "error", err)
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_voucher)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||||
|
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, formattedAmount)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVoucher retrieves the temp voucher data and sets it as the active data.
|
||||||
|
func (h *MenuHandlers) SetVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get temporary data
|
||||||
|
tempData, err := store.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set as active and clear temporary data
|
||||||
|
if err := store.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = tempData.TokenSymbol
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVoucherDetails retrieves the voucher details.
|
||||||
|
func (h *MenuHandlers) GetVoucherDetails(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
store := h.userdataStore
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
// get the active address
|
||||||
|
activeAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read activeAddress entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the voucher contract address to get the data from the API
|
||||||
|
voucherData, err := h.accountService.VoucherData(ctx, string(activeAddress))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
|
|
||||||
|
res.Content = fmt.Sprintf(
|
||||||
|
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation,
|
||||||
|
)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
282
handlers/application/vouchers_test.go
Normal file
282
handlers/application/vouchers_test.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/models"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/testutil/mocks"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManageVouchers(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
publicKey := "0X13242618721"
|
||||||
|
|
||||||
|
ctx, userStore := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
_, logdb := InitializeTestLogdbStore(t)
|
||||||
|
|
||||||
|
logDb := store.LogDb{
|
||||||
|
Db: logdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
flag_api_error, err := fm.GetFlag("flag_api_call_error")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
vouchersResp []dataserviceapi.TokenHoldings
|
||||||
|
storedActiveVoucher string
|
||||||
|
expectedVoucherSymbols []byte
|
||||||
|
expectedUpdatedAddress []byte
|
||||||
|
expectedResult resource.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "No vouchers available",
|
||||||
|
vouchersResp: []dataserviceapi.TokenHoldings{},
|
||||||
|
expectedVoucherSymbols: []byte(""),
|
||||||
|
expectedUpdatedAddress: []byte(""),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagSet: []uint32{flag_no_active_voucher},
|
||||||
|
FlagReset: []uint32{flag_api_error},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Set default voucher when no active voucher is set",
|
||||||
|
vouchersResp: []dataserviceapi.TokenHoldings{
|
||||||
|
{
|
||||||
|
TokenAddress: "0x123",
|
||||||
|
TokenSymbol: "TOKEN1",
|
||||||
|
TokenDecimals: "18",
|
||||||
|
Balance: "100",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedVoucherSymbols: []byte(""),
|
||||||
|
expectedUpdatedAddress: []byte(""),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check and update active voucher balance",
|
||||||
|
vouchersResp: []dataserviceapi.TokenHoldings{
|
||||||
|
{TokenAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
|
||||||
|
{TokenAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
|
||||||
|
},
|
||||||
|
storedActiveVoucher: "SRF",
|
||||||
|
expectedVoucherSymbols: []byte("1:MILO"),
|
||||||
|
expectedUpdatedAddress: []byte("0xd4c288865Ce"),
|
||||||
|
expectedResult: resource.Result{
|
||||||
|
FlagReset: []uint32{flag_api_error, flag_no_active_voucher},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: userStore,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
flagManager: fm,
|
||||||
|
logDb: logDb,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
|
||||||
|
|
||||||
|
// Store active voucher if needed
|
||||||
|
if tt.storedActiveVoucher != "" {
|
||||||
|
err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte(""))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedResult, res)
|
||||||
|
|
||||||
|
if tt.storedActiveVoucher != "" {
|
||||||
|
// Validate stored voucher symbols
|
||||||
|
voucherData, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedVoucherSymbols, voucherData)
|
||||||
|
|
||||||
|
// Validate stored active contract address
|
||||||
|
updatedAddress, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress)
|
||||||
|
|
||||||
|
mockAccountService.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVoucherList(t *testing.T) {
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
// Initialize MenuHandlers
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
ReplaceSeparatorFunc: mockReplaceSeparator,
|
||||||
|
}
|
||||||
|
|
||||||
|
mockSymbols := []byte("1:SRF\n2:MILO")
|
||||||
|
mockBalances := []byte("1:10.099999\n2:40.7")
|
||||||
|
|
||||||
|
// Put voucher symnols and balances data to the store
|
||||||
|
err := store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS, mockSymbols)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_VOUCHER_BALANCES, mockBalances)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedList := []byte("1: SRF 10.09\n2: MILO 40.70")
|
||||||
|
|
||||||
|
res, err := h.GetVoucherList(ctx, "", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, res.Content, string(expectedList))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestViewVoucher(t *testing.T) {
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define mock voucher data
|
||||||
|
mockData := map[storedb.DataTyp][]byte{
|
||||||
|
storedb.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
|
||||||
|
storedb.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
|
||||||
|
storedb.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
|
||||||
|
storedb.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the data
|
||||||
|
for key, value := range mockData {
|
||||||
|
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetVoucher(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
sessionId := "session123"
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the temporary voucher data
|
||||||
|
tempData := &dataserviceapi.TokenHoldings{
|
||||||
|
TokenSymbol: "SRF",
|
||||||
|
Balance: "200",
|
||||||
|
TokenDecimals: "6",
|
||||||
|
TokenAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.TokenAddress)
|
||||||
|
|
||||||
|
// store the expectedData
|
||||||
|
if err := store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := h.SetVoucher(ctx, "set_voucher", []byte(""))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, string(tempData.TokenSymbol), res.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetVoucherDetails(t *testing.T) {
|
||||||
|
ctx, store := InitializeTestStore(t)
|
||||||
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
|
||||||
|
mockAccountService := new(mocks.MockAccountService)
|
||||||
|
|
||||||
|
sessionId := "session123"
|
||||||
|
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||||
|
expectedResult := resource.Result{}
|
||||||
|
|
||||||
|
tokA_AAddress := "0x0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
h := &MenuHandlers{
|
||||||
|
userdataStore: store,
|
||||||
|
flagManager: fm,
|
||||||
|
accountService: mockAccountService,
|
||||||
|
}
|
||||||
|
err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tokenDetails := &models.VoucherDataResult{
|
||||||
|
TokenName: "Token A",
|
||||||
|
TokenSymbol: "TOKA",
|
||||||
|
TokenLocation: "Kilifi,Kenya",
|
||||||
|
TokenCommodity: "Farming",
|
||||||
|
}
|
||||||
|
expectedResult.Content = fmt.Sprintf(
|
||||||
|
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation,
|
||||||
|
)
|
||||||
|
mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil)
|
||||||
|
res, err := h.GetVoucherDetails(ctx, "SessionId", []byte(""))
|
||||||
|
expectedResult.FlagReset = append(expectedResult.FlagReset, flag_api_error)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedResult, res)
|
||||||
|
}
|
||||||
@@ -35,13 +35,6 @@ func (eu *EventsUpdater) HandleCustodialRegistration(ctx context.Context, ev *ap
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// err = pe.Load(identity.SessionId)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// st := pe.GetState()
|
|
||||||
// st.SetFlag(accountCreatedFlag)
|
|
||||||
// return pe.Save(identity.SessionId)
|
|
||||||
logg.DebugCtxf(ctx, "received custodial registration event", "identity", identity)
|
logg.DebugCtxf(ctx, "received custodial registration event", "identity", identity)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/persist"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
"git.defalsify.org/vise.git/persist"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
|
||||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -17,9 +17,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type EventsUpdater struct {
|
type EventsUpdater struct {
|
||||||
api remote.AccountService
|
api remote.AccountService
|
||||||
formatFunc func(string, int, any) string
|
formatFunc func(string, int, any) string
|
||||||
store storage.StorageService
|
store storage.StorageService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEventsUpdater(api remote.AccountService, store storage.StorageService) *EventsUpdater {
|
func NewEventsUpdater(api remote.AccountService, store storage.StorageService) *EventsUpdater {
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/db"
|
"git.defalsify.org/vise.git/db"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
|
||||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
|
||||||
"git.grassecon.net/grassrootseconomics/common/identity"
|
"git.grassecon.net/grassrootseconomics/common/identity"
|
||||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// execute all
|
// execute all
|
||||||
func (eu *EventsUpdater) updateToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, tokenAddress string) error {
|
func (eu *EventsUpdater) updateToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, tokenAddress string) error {
|
||||||
err := eu.updateTokenList(ctx, identity, userStore)
|
err := eu.updateTokenList(ctx, identity, userStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,19 +47,16 @@ func (eu *EventsUpdater) updateToken(ctx context.Context, identity identity.Iden
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// set default token to given symbol.
|
// set default token to given symbol.
|
||||||
func (eu *EventsUpdater) updateDefaultToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, activeSym string) error {
|
func (eu *EventsUpdater) updateDefaultToken(ctx context.Context, identity identity.Identity, userStore *store.UserDataStore, activeSym string) error {
|
||||||
pfxDb := toPrefixDb(userStore, identity.SessionId)
|
|
||||||
// TODO: the activeSym input should instead be newline separated list?
|
// TODO: the activeSym input should instead be newline separated list?
|
||||||
tokenData, err := store.GetVoucherData(ctx, pfxDb, activeSym)
|
tokenData, err := store.GetVoucherData(ctx, userStore, identity.SessionId, activeSym)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return store.UpdateVoucherData(ctx, userStore, identity.SessionId, tokenData)
|
return store.UpdateVoucherData(ctx, userStore, identity.SessionId, tokenData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// handle token transfer.
|
// handle token transfer.
|
||||||
//
|
//
|
||||||
// if from and to are NOT the same, handle code will be executed once for each side of the transfer.
|
// if from and to are NOT the same, handle code will be executed once for each side of the transfer.
|
||||||
@@ -189,7 +186,7 @@ func (eu *EventsUpdater) updateTokenTransferList(ctx context.Context, identity i
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tx := range(txs) {
|
for i, tx := range txs {
|
||||||
r = append(r, eu.formatFunc(apievent.EventTokenTransferTag, i, tx))
|
r = append(r, eu.formatFunc(apievent.EventTokenTransferTag, i, tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,55 +4,49 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/asm"
|
|
||||||
"git.defalsify.org/vise.git/db"
|
"git.defalsify.org/vise.git/db"
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.defalsify.org/vise.git/persist"
|
"git.defalsify.org/vise.git/persist"
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla().WithDomain("sarafu-vise.engine")
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerService interface {
|
type HandlerService interface {
|
||||||
GetHandler() (*application.MenuHandlers, error)
|
GetHandler() (*application.MenuHandlers, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getParser(fp string, debug bool) (*asm.FlagParser, error) {
|
|
||||||
flagParser := asm.NewFlagParser().WithDebug()
|
|
||||||
_, err := flagParser.Load(fp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return flagParser, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocalHandlerService struct {
|
type LocalHandlerService struct {
|
||||||
Parser *asm.FlagParser
|
Parser *application.FlagManager
|
||||||
DbRs *resource.DbResource
|
DbRs *resource.DbResource
|
||||||
Pe *persist.Persister
|
Pe *persist.Persister
|
||||||
UserdataStore *db.Db
|
UserdataStore *db.Db
|
||||||
AdminStore *store.AdminStore
|
LogDb *db.Db
|
||||||
Cfg engine.Config
|
Cfg engine.Config
|
||||||
Rs resource.Resource
|
Rs resource.Resource
|
||||||
|
first resource.EntryFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
|
func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
|
||||||
parser, err := getParser(fp, debug)
|
parser, err := application.NewFlagManager(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
adminstore, err := store.NewAdminStore(ctx, "admin_numbers")
|
if debug {
|
||||||
if err != nil {
|
parser.SetDebug()
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LocalHandlerService{
|
return &LocalHandlerService{
|
||||||
Parser: parser,
|
Parser: parser,
|
||||||
DbRs: dbResource,
|
DbRs: dbResource,
|
||||||
AdminStore: adminstore,
|
Cfg: cfg,
|
||||||
Cfg: cfg,
|
Rs: rs,
|
||||||
Rs: rs,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,17 +58,21 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
|
|||||||
ls.UserdataStore = db
|
ls.UserdataStore = db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ls *LocalHandlerService) SetLogDb(db *db.Db) {
|
||||||
|
ls.LogDb = db
|
||||||
|
}
|
||||||
|
|
||||||
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) (*application.MenuHandlers, error) {
|
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) (*application.MenuHandlers, error) {
|
||||||
replaceSeparatorFunc := func(input string) string {
|
replaceSeparatorFunc := func(input string) string {
|
||||||
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
|
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc)
|
appHandlers, err := application.NewMenuHandlers(ls.Parser, *ls.UserdataStore, *ls.LogDb, accountService, replaceSeparatorFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//appHandlers = appHandlers.WithPersister(ls.Pe)
|
|
||||||
appHandlers.SetPersister(ls.Pe)
|
appHandlers.SetPersister(ls.Pe)
|
||||||
|
ls.DbRs.AddLocalFunc("check_blocked_status", appHandlers.CheckBlockedStatus)
|
||||||
ls.DbRs.AddLocalFunc("set_language", appHandlers.SetLanguage)
|
ls.DbRs.AddLocalFunc("set_language", appHandlers.SetLanguage)
|
||||||
ls.DbRs.AddLocalFunc("create_account", appHandlers.CreateAccount)
|
ls.DbRs.AddLocalFunc("create_account", appHandlers.CreateAccount)
|
||||||
ls.DbRs.AddLocalFunc("save_temporary_pin", appHandlers.SaveTemporaryPin)
|
ls.DbRs.AddLocalFunc("save_temporary_pin", appHandlers.SaveTemporaryPin)
|
||||||
@@ -93,7 +91,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
|||||||
ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient)
|
ls.DbRs.AddLocalFunc("get_recipient", appHandlers.GetRecipient)
|
||||||
ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender)
|
ls.DbRs.AddLocalFunc("get_sender", appHandlers.GetSender)
|
||||||
ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount)
|
ls.DbRs.AddLocalFunc("get_amount", appHandlers.GetAmount)
|
||||||
ls.DbRs.AddLocalFunc("reset_incorrect", appHandlers.ResetIncorrectPin)
|
ls.DbRs.AddLocalFunc("reset_incorrect_pin", appHandlers.ResetIncorrectPin)
|
||||||
ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname)
|
ls.DbRs.AddLocalFunc("save_firstname", appHandlers.SaveFirstname)
|
||||||
ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname)
|
ls.DbRs.AddLocalFunc("save_familyname", appHandlers.SaveFamilyname)
|
||||||
ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender)
|
ls.DbRs.AddLocalFunc("save_gender", appHandlers.SaveGender)
|
||||||
@@ -106,23 +104,22 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
|||||||
ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob)
|
ls.DbRs.AddLocalFunc("verify_yob", appHandlers.VerifyYob)
|
||||||
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob)
|
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", appHandlers.ResetIncorrectYob)
|
||||||
ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction)
|
ls.DbRs.AddLocalFunc("initiate_transaction", appHandlers.InitiateTransaction)
|
||||||
ls.DbRs.AddLocalFunc("verify_new_pin", appHandlers.VerifyNewPin)
|
|
||||||
ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange)
|
ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange)
|
||||||
ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp)
|
ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp)
|
||||||
ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance)
|
ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance)
|
||||||
ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher)
|
ls.DbRs.AddLocalFunc("manage_vouchers", appHandlers.ManageVouchers)
|
||||||
ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers)
|
|
||||||
ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList)
|
ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList)
|
||||||
ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher)
|
ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher)
|
||||||
ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher)
|
ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher)
|
||||||
ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails)
|
ls.DbRs.AddLocalFunc("get_voucher_details", appHandlers.GetVoucherDetails)
|
||||||
ls.DbRs.AddLocalFunc("reset_valid_pin", appHandlers.ResetValidPin)
|
ls.DbRs.AddLocalFunc("get_default_pool", appHandlers.GetDefaultPool)
|
||||||
ls.DbRs.AddLocalFunc("check_pin_mismatch", appHandlers.CheckBlockedNumPinMisMatch)
|
ls.DbRs.AddLocalFunc("get_pools", appHandlers.GetPools)
|
||||||
|
ls.DbRs.AddLocalFunc("view_pool", appHandlers.ViewPool)
|
||||||
|
ls.DbRs.AddLocalFunc("set_pool", appHandlers.SetPool)
|
||||||
ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber)
|
ls.DbRs.AddLocalFunc("validate_blocked_number", appHandlers.ValidateBlockedNumber)
|
||||||
ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber)
|
ls.DbRs.AddLocalFunc("retrieve_blocked_number", appHandlers.RetrieveBlockedNumber)
|
||||||
ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber)
|
ls.DbRs.AddLocalFunc("reset_unregistered_number", appHandlers.ResetUnregisteredNumber)
|
||||||
ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin)
|
ls.DbRs.AddLocalFunc("reset_others_pin", appHandlers.ResetOthersPin)
|
||||||
ls.DbRs.AddLocalFunc("save_others_temporary_pin", appHandlers.SaveOthersTemporaryPin)
|
|
||||||
ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo)
|
ls.DbRs.AddLocalFunc("get_current_profile_info", appHandlers.GetCurrentProfileInfo)
|
||||||
ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions)
|
ls.DbRs.AddLocalFunc("check_transactions", appHandlers.CheckTransactions)
|
||||||
ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList)
|
ls.DbRs.AddLocalFunc("get_transactions", appHandlers.GetTransactionsList)
|
||||||
@@ -130,13 +127,28 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
|||||||
ls.DbRs.AddLocalFunc("update_all_profile_items", appHandlers.UpdateAllProfileItems)
|
ls.DbRs.AddLocalFunc("update_all_profile_items", appHandlers.UpdateAllProfileItems)
|
||||||
ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack)
|
ls.DbRs.AddLocalFunc("set_back", appHandlers.SetBack)
|
||||||
ls.DbRs.AddLocalFunc("show_blocked_account", appHandlers.ShowBlockedAccount)
|
ls.DbRs.AddLocalFunc("show_blocked_account", appHandlers.ShowBlockedAccount)
|
||||||
|
ls.DbRs.AddLocalFunc("clear_temporary_value", appHandlers.ClearTemporaryValue)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_invalid_pin", appHandlers.ResetInvalidPIN)
|
||||||
|
ls.DbRs.AddLocalFunc("request_custom_alias", appHandlers.RequestCustomAlias)
|
||||||
|
ls.DbRs.AddLocalFunc("check_account_created", appHandlers.CheckAccountCreated)
|
||||||
|
ls.DbRs.AddLocalFunc("reset_api_call_failure", appHandlers.ResetApiCallFailure)
|
||||||
|
ls.DbRs.AddLocalFunc("swap_to_list", appHandlers.LoadSwapToList)
|
||||||
|
ls.DbRs.AddLocalFunc("swap_max_limit", appHandlers.SwapMaxLimit)
|
||||||
|
ls.DbRs.AddLocalFunc("swap_preview", appHandlers.SwapPreview)
|
||||||
|
ls.DbRs.AddLocalFunc("initiate_swap", appHandlers.InitiateSwap)
|
||||||
|
ls.first = appHandlers.Init
|
||||||
|
|
||||||
return appHandlers, nil
|
return appHandlers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: enable setting of sessionId on engine init time
|
func (ls *LocalHandlerService) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
|
||||||
func (ls *LocalHandlerService) GetEngine() *engine.DefaultEngine {
|
en := engine.NewEngine(cfg, rs)
|
||||||
en := engine.NewEngine(ls.Cfg, ls.Rs)
|
if ls.first != nil {
|
||||||
en = en.WithPersister(ls.Pe)
|
en = en.WithFirst(ls.first)
|
||||||
|
}
|
||||||
|
en = en.WithPersister(pr)
|
||||||
|
if cfg.EngineDebug {
|
||||||
|
en = en.WithDebug(nil)
|
||||||
|
}
|
||||||
return en
|
return en
|
||||||
}
|
}
|
||||||
|
|||||||
96
internal/cmd/cmd.go
Normal file
96
internal/cmd/cmd.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla().WithDomain("cmd").WithContextKey("SessionId")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cmd struct {
|
||||||
|
sessionId string
|
||||||
|
conn storage.ConnData
|
||||||
|
flagParser *application.FlagManager
|
||||||
|
cmd int
|
||||||
|
enable bool
|
||||||
|
exec func(ctx context.Context, ss storage.StorageService) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCmd(sessionId string, flagParser *application.FlagManager) *Cmd {
|
||||||
|
return &Cmd{
|
||||||
|
sessionId: sessionId,
|
||||||
|
flagParser: flagParser,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Exec(ctx context.Context, ss storage.StorageService) error {
|
||||||
|
return c.exec(ctx, ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) execAdmin(ctx context.Context, ss storage.StorageService) error {
|
||||||
|
pe, err := ss.GetPersister(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = pe.Load(c.sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := pe.Save(c.sessionId)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed persister save: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
st := pe.GetState()
|
||||||
|
flag, err := c.flagParser.GetFlag("flag_admin_privilege")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.enable {
|
||||||
|
logg.InfoCtxf(ctx, "setting admin flag", "flag", flag)
|
||||||
|
st.SetFlag(flag)
|
||||||
|
} else {
|
||||||
|
st.ResetFlag(flag)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) parseCmdAdmin(cmd string, param string, more []string) (bool, error) {
|
||||||
|
if cmd == "admin" {
|
||||||
|
if param == "1" {
|
||||||
|
c.enable = true
|
||||||
|
} else if param != "0" {
|
||||||
|
return false, fmt.Errorf("invalid parameter: %v", param)
|
||||||
|
}
|
||||||
|
c.exec = c.execAdmin
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cmd) Parse(args []string) error {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return fmt.Errorf("Wrong number of arguments: %v", args)
|
||||||
|
}
|
||||||
|
cmd := args[0]
|
||||||
|
param := args[1]
|
||||||
|
args = args[2:]
|
||||||
|
|
||||||
|
r, err := c.parseCmdAdmin(cmd, param, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("unknown subcommand: %s", cmd)
|
||||||
|
}
|
||||||
96
internal/sms/sms.go
Normal file
96
internal/sms/sms.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package sms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/logging"
|
||||||
|
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||||
|
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logg = logging.NewVanilla().WithDomain("smsservice")
|
||||||
|
)
|
||||||
|
|
||||||
|
type SmsService struct {
|
||||||
|
Accountservice remote.AccountService
|
||||||
|
Userdatastore store.UserDataStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUpsellSMS will send an invitation SMS to an unregistered phone number
|
||||||
|
func (smsservice *SmsService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) error {
|
||||||
|
if !phone.IsValidPhoneNumber(inviterPhone) {
|
||||||
|
return fmt.Errorf("invalid inviter phone number %v", inviterPhone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !phone.IsValidPhoneNumber(inviteePhone) {
|
||||||
|
return fmt.Errorf("Invalid invitee phone number %v", inviteePhone)
|
||||||
|
}
|
||||||
|
_, err := smsservice.Accountservice.SendUpsellSMS(ctx, inviterPhone, inviteePhone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to send upsell sms: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendPINResetSMS will send an SMS to a user's phonenumber in the event that the associated account's PIN has been reset.
|
||||||
|
func (smsService *SmsService) SendPINResetSMS(ctx context.Context, adminPhoneNumber, blockedPhoneNumber string) error {
|
||||||
|
formattedAdminPhone, err := phone.FormatPhoneNumber(adminPhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to format admin phone number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedBlockedPhone, err := phone.FormatPhoneNumber(blockedPhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to format blocked phone number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !phone.IsValidPhoneNumber(formattedAdminPhone) {
|
||||||
|
return fmt.Errorf("invalid admin phone number")
|
||||||
|
}
|
||||||
|
if !phone.IsValidPhoneNumber(formattedBlockedPhone) {
|
||||||
|
return fmt.Errorf("invalid blocked phone number")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = smsService.Accountservice.SendPINResetSMS(ctx, formattedAdminPhone, formattedBlockedPhone)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to send pin reset sms: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendAddressSMS will triger an SMS when a user navigates to the my address node.The SMS will be sent to the associated phonenumber.
|
||||||
|
func (smsService *SmsService) SendAddressSMS(ctx context.Context) error {
|
||||||
|
store := smsService.Userdatastore
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
originPhone, err := phone.FormatPhoneNumber(sessionId)
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to format origin phonenumber", "sessionid", sessionId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !phone.IsValidPhoneNumber(originPhone) {
|
||||||
|
logg.InfoCtxf(ctx, "Invalid origin phone number", "origin phonenumber", originPhone)
|
||||||
|
return fmt.Errorf("invalid origin phone number")
|
||||||
|
}
|
||||||
|
err = smsService.Accountservice.SendAddressSMS(ctx, string(publicKey), originPhone)
|
||||||
|
if err != nil {
|
||||||
|
logg.DebugCtxf(ctx, "Failed to send address sms", "error", err)
|
||||||
|
return fmt.Errorf("Failed to send address sms: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,5 +1,67 @@
|
|||||||
{
|
{
|
||||||
"groups": [
|
"groups": [
|
||||||
|
{
|
||||||
|
"name": "main_my_vouchers_select_voucher_using_index",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter PIN to confirm selection:\nSymbol: SRF\nBalance: 2.745987\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Success! SRF is now your active voucher.\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "main_my_vouchers_select_voucher_using_symbol",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "My vouchers\n1:Select voucher\n2:Voucher details\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Select number or symbol from your vouchers:\n1SRF\n0:Back\n99:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "SRF",
|
||||||
|
"expectedContent": "Enter PIN to confirm selection:\nSymbol: SRF\nBalance: 2.745987\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "Success! SRF is now your active voucher.\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "my_account_change_pin",
|
"name": "my_account_change_pin",
|
||||||
"steps": [
|
"steps": [
|
||||||
@@ -9,7 +71,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "5",
|
"input": "5",
|
||||||
@@ -46,7 +108,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "2",
|
"input": "2",
|
||||||
@@ -54,7 +120,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1235",
|
"input": "1235",
|
||||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -74,6 +140,105 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_reset_others_pin_with_unregistered_number",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0700000001",
|
||||||
|
"expectedContent": "The number you have entered is either not registered with Sarafu or is invalid.\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_reset_others_pin_with_registered_number",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "Enter other's phone number:\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0700000000",
|
||||||
|
"expectedContent": "{secondary_session_id} will get a PIN reset request.\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1234",
|
||||||
|
"expectedContent": "PIN reset request for {secondary_session_id} was successful\n0:Back\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_my_account_reset_others_pin_with_no_privileges",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "5",
|
||||||
|
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "2",
|
||||||
|
"expectedContent": "You do not have privileges to perform this action\n\n9:Quit\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "0",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "menu_my_account_check_my_balance",
|
"name": "menu_my_account_check_my_balance",
|
||||||
"steps": [
|
"steps": [
|
||||||
@@ -83,7 +248,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
@@ -95,7 +260,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1235",
|
"input": "1235",
|
||||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -103,7 +268,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1234",
|
"input": "1234",
|
||||||
"expectedContent": "Balance: {balance}\n\n0:Back\n9:Quit"
|
"expectedContent": "{balance}\n\n0:Back\n9:Quit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
@@ -111,7 +276,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
@@ -128,7 +293,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
@@ -140,7 +305,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1235",
|
"input": "1235",
|
||||||
"expectedContent": "Incorrect PIN. You have: 2 remaining attempt(s).\n1:Retry\n9:Quit"
|
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -156,7 +321,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
@@ -173,7 +338,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -230,7 +395,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -267,7 +432,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -277,6 +442,10 @@
|
|||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
"expectedContent": "Please enter your PIN:"
|
"expectedContent": "Please enter your PIN:"
|
||||||
@@ -304,7 +473,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -341,7 +510,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -378,7 +547,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -415,7 +584,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -427,7 +596,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1234",
|
"input": "1234",
|
||||||
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back\n9:Quit"
|
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 80\nLocation: Kilifi\nYou provide: Bananas\nYour alias: Not Provided\n\n0:Back\n9:Quit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "0",
|
"input": "0",
|
||||||
@@ -438,6 +607,47 @@
|
|||||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "menu_block_account_via_view_profile",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"input": "",
|
||||||
|
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "3",
|
||||||
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "7",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1254",
|
||||||
|
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1254",
|
||||||
|
"expectedContent": "Incorrect PIN. You have: {attempts} remaining attempt(s).\n1:Retry\n9:Quit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1",
|
||||||
|
"expectedContent": "Please enter your PIN:"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input": "1254",
|
||||||
|
"expectedContent": "Your account has been locked. For help on how to unblock your account, contact support at: 0757628885"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -9,22 +9,22 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"git.defalsify.org/vise.git/logging"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/testutil/driver"
|
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/testutil"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/testutil"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil/driver"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testData = driver.ReadData()
|
logg = logging.NewVanilla().WithDomain("menutraversaltest")
|
||||||
sessionID string
|
testData = driver.ReadData()
|
||||||
src = rand.NewSource(42)
|
sessionID string
|
||||||
g = rand.New(src)
|
src = rand.NewSource(42)
|
||||||
|
g = rand.New(src)
|
||||||
|
secondarySessionId = "+254700000000"
|
||||||
)
|
)
|
||||||
|
|
||||||
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
|
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
|
||||||
var database = flag.String("db", "gdbm", "Specify the database (gdbm or postgres)")
|
|
||||||
var connStr = flag.String("conn", ".test_state", "connection string")
|
|
||||||
var dbSchema = flag.String("schema", "test", "Specify the database schema (default test)")
|
|
||||||
|
|
||||||
func GenerateSessionId() string {
|
func GenerateSessionId() string {
|
||||||
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
||||||
@@ -68,6 +68,16 @@ func extractMaxAmount(response []byte) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractRemainingAttempts(response []byte) string {
|
||||||
|
// Regex to match "You have: <number> remaining attempt(s)"
|
||||||
|
re := regexp.MustCompile(`(?m)You have:\s+(\d+)\s+remaining attempt\(s\)`)
|
||||||
|
match := re.FindSubmatch(response)
|
||||||
|
if match != nil {
|
||||||
|
return string(match[1]) // "<number>" of remaining attempts
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Extracts the send amount value from the engine response.
|
// Extracts the send amount value from the engine response.
|
||||||
func extractSendAmount(response []byte) string {
|
func extractSendAmount(response []byte) string {
|
||||||
// Regex to match the pattern "will receive X.XX SYM from"
|
// Regex to match the pattern "will receive X.XX SYM from"
|
||||||
@@ -80,12 +90,7 @@ func extractSendAmount(response []byte) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
// Parse the flags
|
|
||||||
flag.Parse()
|
|
||||||
sessionID = GenerateSessionId()
|
sessionID = GenerateSessionId()
|
||||||
// set the db
|
|
||||||
testutil.SetDatabase(*database, *connStr, *dbSchema)
|
|
||||||
|
|
||||||
// Cleanup the db after tests
|
// Cleanup the db after tests
|
||||||
defer testutil.CleanDatabase()
|
defer testutil.CleanDatabase()
|
||||||
|
|
||||||
@@ -93,14 +98,51 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountCreationSuccessful(t *testing.T) {
|
func TestAccountCreationSuccessful(t *testing.T) {
|
||||||
en, fn, eventChannel := testutil.TestEngine(sessionID)
|
en, fn, eventChannel, _, _ := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
for _, step := range group.Steps {
|
for i, step := range group.Steps {
|
||||||
|
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||||
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
|
}
|
||||||
|
if !cont {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
w := bytes.NewBuffer(nil)
|
||||||
|
_, err = en.Flush(ctx, w)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
|
||||||
|
}
|
||||||
|
b := w.Bytes()
|
||||||
|
match, err := step.MatchesExpectedContent(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error compiling regex for step '%s': %v", step.Input, err)
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", step.ExpectedContent, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<-eventChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecondaryAccount(t *testing.T) {
|
||||||
|
en, fn, eventChannel, _, _ := testutil.TestEngine(secondarySessionId)
|
||||||
|
defer fn()
|
||||||
|
ctx := context.Background()
|
||||||
|
sessions := testData
|
||||||
|
for _, session := range sessions {
|
||||||
|
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
|
||||||
|
for _, group := range groups {
|
||||||
|
for i, step := range group.Steps {
|
||||||
|
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
@@ -135,14 +177,15 @@ func TestAccountRegistrationRejectTerms(t *testing.T) {
|
|||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
edgeCaseSessionID := v.String()
|
edgeCaseSessionID := v.String()
|
||||||
en, fn, _ := testutil.TestEngine(edgeCaseSessionID)
|
en, fn, _, _, _ := testutil.TestEngine(edgeCaseSessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
groups := driver.FilterGroupsByName(session.Groups, "account_creation_reject_terms")
|
groups := driver.FilterGroupsByName(session.Groups, "account_creation_reject_terms")
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
for _, step := range group.Steps {
|
for i, step := range group.Steps {
|
||||||
|
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
@@ -170,14 +213,15 @@ func TestAccountRegistrationRejectTerms(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMainMenuHelp(t *testing.T) {
|
func TestMainMenuHelp(t *testing.T) {
|
||||||
en, fn, _ := testutil.TestEngine(sessionID)
|
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
groups := driver.FilterGroupsByName(session.Groups, "main_menu_help")
|
groups := driver.FilterGroupsByName(session.Groups, "main_menu_help")
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
for _, step := range group.Steps {
|
for i, step := range group.Steps {
|
||||||
|
logg.TraceCtxf(ctx, "executing step", "i", i, "step", step)
|
||||||
cont, err := en.Exec(ctx, []byte(step.Input))
|
cont, err := en.Exec(ctx, []byte(step.Input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
t.Fatalf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
|
||||||
@@ -211,7 +255,7 @@ func TestMainMenuHelp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMainMenuQuit(t *testing.T) {
|
func TestMainMenuQuit(t *testing.T) {
|
||||||
en, fn, _ := testutil.TestEngine(sessionID)
|
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
@@ -252,7 +296,7 @@ func TestMainMenuQuit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMyAccount_MyAddress(t *testing.T) {
|
func TestMyAccount_MyAddress(t *testing.T) {
|
||||||
en, fn, _ := testutil.TestEngine(sessionID)
|
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
@@ -296,7 +340,7 @@ func TestMyAccount_MyAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMainMenuSend(t *testing.T) {
|
func TestMainMenuSend(t *testing.T) {
|
||||||
en, fn, _ := testutil.TestEngine(sessionID)
|
en, fn, _, _, _ := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sessions := testData
|
sessions := testData
|
||||||
@@ -347,9 +391,12 @@ func TestGroups(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to load test groups: %v", err)
|
log.Fatalf("Failed to load test groups: %v", err)
|
||||||
}
|
}
|
||||||
en, fn, _ := testutil.TestEngine(sessionID)
|
en, fn, _, pe, flagParser := testutil.TestEngine(sessionID)
|
||||||
defer fn()
|
defer fn()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
flag_admin_privilege, _ := flagParser.GetFlag("flag_admin_privilege")
|
||||||
|
|
||||||
// Create test cases from loaded groups
|
// Create test cases from loaded groups
|
||||||
tests := driver.CreateTestCases(groups)
|
tests := driver.CreateTestCases(groups)
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -368,9 +415,21 @@ func TestGroups(t *testing.T) {
|
|||||||
}
|
}
|
||||||
b := w.Bytes()
|
b := w.Bytes()
|
||||||
balance := extractBalance(b)
|
balance := extractBalance(b)
|
||||||
|
attempts := extractRemainingAttempts(b)
|
||||||
|
|
||||||
|
st := pe.GetState()
|
||||||
|
|
||||||
|
if st != nil {
|
||||||
|
st.SetFlag(flag_admin_privilege)
|
||||||
|
if tt.Name == "menu_my_account_reset_others_pin_with_no_privileges" {
|
||||||
|
st.ResetFlag(flag_admin_privilege)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expectedContent := []byte(tt.ExpectedContent)
|
expectedContent := []byte(tt.ExpectedContent)
|
||||||
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{attempts}"), []byte(attempts), -1)
|
||||||
|
expectedContent = bytes.Replace(expectedContent, []byte("{secondary_session_id}"), []byte(secondarySessionId), -1)
|
||||||
|
|
||||||
tt.ExpectedContent = string(expectedContent)
|
tt.ExpectedContent = string(expectedContent)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/tos\n\n1:Yes\n2:No"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1234",
|
"input": "1234",
|
||||||
"expectedContent": "Your account is being created...Thank you for using Sarafu. Goodbye!"
|
"expectedContent": "Your account is being created. Thank you for using Sarafu. Goodbye!"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "1",
|
"input": "1",
|
||||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n1:Yes\n2:No"
|
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/tos\n\n1:Yes\n2:No"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "2",
|
"input": "2",
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "3",
|
"input": "3",
|
||||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n7:My Alias\n0:Back"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"input": "6",
|
"input": "6",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func (p *Profile) InsertOrShift(index int, value string) {
|
|||||||
for len(p.ProfileItems) < index {
|
for len(p.ProfileItems) < index {
|
||||||
p.ProfileItems = append(p.ProfileItems, "0")
|
p.ProfileItems = append(p.ProfileItems, "0")
|
||||||
}
|
}
|
||||||
p.ProfileItems = append(p.ProfileItems, "0")
|
p.ProfileItems = append(p.ProfileItems, "0")
|
||||||
p.ProfileItems[index] = value
|
p.ProfileItems[index] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
profile/profile_test.go
Normal file
42
profile/profile_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert/v2"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInsertOrShift(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
profile Profile
|
||||||
|
index int
|
||||||
|
value string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Insert within range",
|
||||||
|
profile: Profile{ProfileItems: []string{"A", "B", "C"}, Max: 5},
|
||||||
|
index: 1,
|
||||||
|
value: "X",
|
||||||
|
expected: []string{"A", "X"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Insert beyond range",
|
||||||
|
profile: Profile{ProfileItems: []string{"A"}, Max: 5},
|
||||||
|
index: 3,
|
||||||
|
value: "Y",
|
||||||
|
expected: []string{"A", "0", "0", "Y"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
p := tt.profile
|
||||||
|
p.InsertOrShift(tt.index, tt.value)
|
||||||
|
require.NotNil(t, p.ProfileItems)
|
||||||
|
assert.Equal(t, tt.expected, p.ProfileItems)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
|
//go:build !online
|
||||||
// +build !online
|
// +build !online
|
||||||
|
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
devremote "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
|
devremote "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
|
||||||
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
apievent "git.grassecon.net/grassrootseconomics/sarafu-api/event"
|
||||||
|
"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
|
||||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/event"
|
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/event"
|
||||||
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type localEmitter struct {
|
type localEmitter struct {
|
||||||
@@ -37,7 +38,7 @@ func (d *localEmitter) emit(ctx context.Context, msg apievent.Msg) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, storageService storage.StorageService, conn storage.ConnData) remote.AccountService {
|
func New(ctx context.Context, storageService storage.StorageService) remote.AccountService {
|
||||||
svc := devremote.NewDevAccountService(ctx, storageService)
|
svc := devremote.NewDevAccountService(ctx, storageService)
|
||||||
svc = svc.WithAutoVoucher(ctx, "FOO", 42)
|
svc = svc.WithAutoVoucher(ctx, "FOO", 42)
|
||||||
eu := event.NewEventsUpdater(svc, storageService)
|
eu := event.NewEventsUpdater(svc, storageService)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ TXTS = $(wildcard ./*.txt.orig)
|
|||||||
VISE_PATH := ../../go-vise
|
VISE_PATH := ../../go-vise
|
||||||
|
|
||||||
# Rule to build .bin files from .vis files
|
# Rule to build .bin files from .vis files
|
||||||
%.vis:
|
%.vis: buildasm
|
||||||
go run $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
|
./vise-asm -f pp.csv $(basename $@).vis > $(basename $@).bin
|
||||||
@echo "Built $(basename $@).bin from $(basename $@).vis"
|
@echo "Built $(basename $@).bin from $(basename $@).vis"
|
||||||
|
|
||||||
# Rule to copy .orig files to .txt
|
# Rule to copy .orig files to .txt
|
||||||
@@ -19,5 +19,10 @@ all: $(INPUTS) $(TXTS)
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -vf *.bin
|
rm -vf *.bin
|
||||||
|
rm -vf ./vise-asm
|
||||||
|
|
||||||
|
buildasm:
|
||||||
|
go build -v -o ./vise-asm $(VISE_PATH)/dev/asm/main.go
|
||||||
|
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
Your account is being created...
|
Your account is being created.
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
Your account is still being created.
|
Your account is still being created. For more help, please call: 0757628885
|
||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
Akaunti yako bado inatengenezwa
|
Akaunti yako bado inatengenezwa. Kwa usaidizi zaidi, piga: 0757628885
|
||||||
Binary file not shown.
@@ -1,5 +1,4 @@
|
|||||||
LOAD check_identifier 0
|
LOAD check_identifier 0
|
||||||
RELOAD check_identifier
|
|
||||||
MAP check_identifier
|
MAP check_identifier
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
MOUT quit 9
|
MOUT quit 9
|
||||||
|
|||||||
1
services/registration/alias_updated
Normal file
1
services/registration/alias_updated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Your alias has been updated successfully
|
||||||
5
services/registration/alias_updated.vis
Normal file
5
services/registration/alias_updated.vis
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
MOUT back 0
|
||||||
|
MOUT quit 9
|
||||||
|
HALT
|
||||||
|
INCMP ^ 0
|
||||||
|
INCMP quit 9
|
||||||
1
services/registration/alias_updated_swa
Normal file
1
services/registration/alias_updated_swa
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Ombi lako la kubadilisha lakabu limefanikiwa.
|
||||||
Binary file not shown.
@@ -1,12 +1,12 @@
|
|||||||
LOAD reset_transaction_amount 0
|
LOAD reset_transaction_amount 0
|
||||||
LOAD max_amount 10
|
LOAD max_amount 40
|
||||||
RELOAD max_amount
|
RELOAD max_amount
|
||||||
MAP max_amount
|
MAP max_amount
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
HALT
|
HALT
|
||||||
LOAD validate_amount 64
|
LOAD validate_amount 64
|
||||||
RELOAD validate_amount
|
RELOAD validate_amount
|
||||||
CATCH api_failure flag_api_call_error 1
|
CATCH api_failure flag_api_call_error 1
|
||||||
CATCH invalid_amount flag_invalid_amount 1
|
CATCH invalid_amount flag_invalid_amount 1
|
||||||
INCMP _ 0
|
INCMP _ 0
|
||||||
LOAD get_recipient 0
|
LOAD get_recipient 0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Failed to connect to the custodial service.Please try again.
|
Your request failed. Please try again later.
|
||||||
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
|
LOAD reset_api_call_failure 6
|
||||||
|
RELOAD reset_api_call_failure
|
||||||
MOUT retry 1
|
MOUT retry 1
|
||||||
MOUT quit 9
|
MOUT quit 9
|
||||||
HALT
|
HALT
|
||||||
INCMP _ 1
|
INCMP ^ 1
|
||||||
INCMP quit 9
|
INCMP quit 9
|
||||||
|
|||||||
1
services/registration/api_failure_swa
Normal file
1
services/registration/api_failure_swa
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Ombi lako halikufaulu. Tafadhali jaribu tena baadaye.
|
||||||
2
services/registration/authorize_reset_others_pin
Normal file
2
services/registration/authorize_reset_others_pin
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{{.retrieve_blocked_number}} will get a PIN reset request.
|
||||||
|
Please enter your PIN to confirm:
|
||||||
12
services/registration/authorize_reset_others_pin.vis
Normal file
12
services/registration/authorize_reset_others_pin.vis
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
LOAD retrieve_blocked_number 0
|
||||||
|
RELOAD retrieve_blocked_number
|
||||||
|
MAP retrieve_blocked_number
|
||||||
|
MOUT back 0
|
||||||
|
MOUT quit 9
|
||||||
|
LOAD authorize_account 6
|
||||||
|
HALT
|
||||||
|
RELOAD authorize_account
|
||||||
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
|
INCMP _ 0
|
||||||
|
INCMP quit 9
|
||||||
|
INCMP pin_reset_result *
|
||||||
2
services/registration/authorize_reset_others_pin_swa
Normal file
2
services/registration/authorize_reset_others_pin_swa
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{{.retrieve_blocked_number}} atapokea ombi la kuweka upya PIN.
|
||||||
|
Tafadhali weka PIN yako kudhibitisha:
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
LOAD reset_account_authorized 0
|
LOAD reset_account_authorized 0
|
||||||
LOAD reset_incorrect 0
|
LOAD reset_incorrect_pin 0
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
CATCH pin_entry flag_account_authorized 0
|
CATCH pin_entry flag_account_authorized 0
|
||||||
MOUT english 1
|
MOUT english 1
|
||||||
|
|||||||
Binary file not shown.
@@ -1,5 +1,6 @@
|
|||||||
LOAD check_transactions 0
|
LOAD check_transactions 0
|
||||||
RELOAD check_transactions
|
RELOAD check_transactions
|
||||||
|
CATCH api_failure flag_api_call_error 1
|
||||||
CATCH no_transfers flag_no_transfers 1
|
CATCH no_transfers flag_no_transfers 1
|
||||||
LOAD authorize_account 6
|
LOAD authorize_account 6
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
LOAD reset_incorrect 6
|
LOAD reset_incorrect_pin 6
|
||||||
LOAD fetch_community_balance 0
|
LOAD fetch_community_balance 0
|
||||||
CATCH api_failure flag_api_call_error 1
|
CATCH api_failure flag_api_call_error 1
|
||||||
MAP fetch_community_balance
|
MAP fetch_community_balance
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
CATCH pin_entry flag_account_authorized 0
|
CATCH pin_entry flag_account_authorized 0
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user