Compare commits

...

570 Commits

Author SHA1 Message Date
lash
c0534ede1b
Strip scheme from connstring in gdbm 2025-01-15 00:32:56 +00:00
lash
24e729d275
Fix untidy go mod 2025-01-15 00:11:11 +00:00
lash
ae6e2a99c5
upgrade vise dep 2025-01-15 00:09:14 +00:00
lash
7ca3974371
Enable fs, mem in storageservice 2025-01-15 00:08:44 +00:00
lash
adbfab3964
Enable conndata fs, mem 2025-01-14 18:57:04 +00:00
lash
5228aef088
Add mem storage service 2025-01-13 21:33:25 +00:00
lash
5bf0a0e858
remove session package 2025-01-13 10:31:42 +00:00
lash
f13dab9a45
Remove docker stuff 2025-01-12 15:54:57 +00:00
lash
3f8e08151a
Add debug string for db type in menuhandler 2025-01-12 15:35:44 +00:00
lash
9e4c65c8b4
Correct storage service interface signature 2025-01-12 12:13:25 +00:00
lash
611c5a8dfc
Rename session handler to request handler 2025-01-12 10:40:23 +00:00
lash
dcf777bf08
Fix missing err in config 2025-01-12 10:24:50 +00:00
lash
ec4ad6e44b
Move out config 2025-01-12 09:37:40 +00:00
lash
5b312f9569
Move out common, util, debug, storage to sarafu-vise 2025-01-12 07:59:03 +00:00
lash
2d89db3a37
Rename http source file 2025-01-11 22:38:38 +00:00
lash
6d8992fe38
Remove remaining devtools cmds 2025-01-11 22:33:03 +00:00
lash
5cffbe5cd8
Remove lang devtool 2025-01-11 22:31:21 +00:00
lash
b28a047777
Remove dialoguss reference 2025-01-11 22:29:08 +00:00
lash
2ea51d88d8
Factor out adminstore 2025-01-11 22:21:09 +00:00
lash
2e0e854c4b
Remove last internal package 2025-01-11 21:47:57 +00:00
lash
c93a07832d
Move out API services related code 2025-01-11 16:31:06 +00:00
lash
46bf21b7b8
Remove commented code 2025-01-11 15:16:14 +00:00
lash
dff662663d
Rehabilitate http server test 2025-01-11 08:37:10 +00:00
lash
977d14c529
Properly isolate http session handler 2025-01-11 08:33:52 +00:00
lash
fd6e5caf53
Remove persister chainer for menu handler 2025-01-11 08:08:52 +00:00
lash
840c22ca89
Factor out session handler, introduce entry handler 2025-01-11 08:01:48 +00:00
lash
f939a20543
Remove vise code 2025-01-11 07:23:15 +00:00
lash
8387644019
Export http package 2025-01-10 20:39:36 +00:00
lash
c535938dbc
Move out remaining local handler dependent code 2025-01-10 20:31:34 +00:00
lash
b60d2648ea
Rehabilitate until localhandler 2025-01-10 14:04:23 +00:00
lash
85ede15613 Merge branch 'master' into lash/purify-max 2025-01-10 13:46:52 +00:00
b11f11b5fa Merge pull request 'refactor: rename files to snake_case' (#268) from konstantinmds/ussd:refactor/148-convert-files-to-snake-case into master
Reviewed-on: urdt/ussd#268
2025-01-10 14:40:58 +01:00
3d35a5de78 rename files to snake case 2025-01-10 13:43:49 +01:00
a19ace85f8 refactor: rename files to snake_case 2025-01-10 13:41:23 +01:00
lash
a7a8a482ab
Rehabilitate go test running 2025-01-10 12:03:37 +00:00
lash
d8e7c443b5
Add gettext in exported storage service 2025-01-10 11:54:59 +00:00
lash
5216a9383e
Add service mock missing 2025-01-10 11:49:47 +00:00
lash
6cd639fd19
Add missing pgxpool module 2025-01-10 11:44:15 +00:00
lash
a7ca280964 Merge branch 'master' into lash/purify-max 2025-01-10 11:40:49 +00:00
8f5ed0cd4f Merge pull request 'postgres-switch-for-tests' (#255) from postgres-switch-for-tests into master
Reviewed-on: urdt/ussd#255
2025-01-10 12:07:07 +01:00
lash
f1664a43a8
Rename module, export storage, testutil 2025-01-10 10:53:27 +00:00
c29abfe21e Merge branch 'master' into postgres-switch-for-tests 2025-01-10 13:43:16 +03:00
9a6d8e5158
Refactored the code to switch between postgres and gdbm, with db cleanup 2025-01-10 13:41:05 +03:00
lash
9ca5091692 Merge branch 'master' into lash/purify-max 2025-01-10 08:56:59 +00:00
db431c750e Merge pull request 'copy-language-code' (#264) from copy-language-code into master
Reviewed-on: urdt/ussd#264
2025-01-10 09:53:17 +01:00
2b9c6d641e
Merge branch 'master' into copy-language-code 2025-01-10 11:06:32 +03:00
lash
b9712098ef
Fix remaining conflict diff 2025-01-09 13:49:50 +00:00
lash
bcf1965a6c Merge branch 'master' into lash/purify-max 2025-01-09 13:48:39 +00:00
3747f87a7c
test language code saving 2025-01-09 15:11:29 +03:00
1f0568df32 Merge pull request 'Implement connstring handling' (#247) from lash/purify-more into master
Reviewed-on: urdt/ussd#247
2025-01-09 13:03:28 +01:00
lash
24c513d4f0 Merge branch 'master' into lash/purify-more 2025-01-09 12:02:17 +00:00
b3fd6f5c1a Merge pull request 'Rename handler/ussd package' (#254) from konstantinmds/ussd:refactor/24-rename-ussd-to-application into master
Reviewed-on: urdt/ussd#254
2025-01-09 13:01:19 +01:00
73eb765408
persist selected language code 2025-01-09 13:14:46 +03:00
f660f6c19a
add key to hold selected langauge code 2025-01-09 13:04:11 +03:00
3fccfaab61
Replace the connStr if it is not set 2025-01-09 13:01:28 +03:00
lash
b50a51df9b
Implement postgres schema 2025-01-09 07:42:09 +00:00
lash
df8c9aab0c
Rehabilitate tests 2025-01-08 22:27:19 +00:00
lash
ddefdd7fb3 Merge branch 'lash/purify-more' into postgres-switch-for-tests 2025-01-08 22:05:12 +00:00
5734011f96 refactor: rename ussd package to application (#24)
- Rename internal/handlers/ussd directory to application
- Update all imports and references to use new package name
2025-01-08 13:40:00 +01:00
lash
379d98ccd5 Merge branch 'master' into lash/purify-more 2025-01-08 12:32:11 +00:00
f40e11c267 Merge pull request 'account-pin-block-v2' (#256) from account-pin-block-v2 into master
Reviewed-on: urdt/ussd#256
2025-01-08 13:30:39 +01:00
b698f08136
chore: add space after punctuation 2025-01-08 15:27:10 +03:00
4d7589ad95 Merge branch 'master' into lash/purify-more 2025-01-08 13:07:50 +01:00
efdb52bccd
chore: add space after punctuation 2025-01-08 14:54:58 +03:00
2ff9fed3c5
chore: rename countIncorrectPINAttempts to incrementIncorrectPINAttempts 2025-01-08 14:54:57 +03:00
477b4cf8f6
chore : rename remainingPINAttempts to currentWrongPinAttemptsCount 2025-01-08 14:54:57 +03:00
ed6651697a
chore : add variable description to AllowedPINAttempts 2025-01-08 14:54:56 +03:00
c359d99075 Merge branch 'master' into account-pin-block-v2 2025-01-08 10:00:46 +01:00
8d477356f3
update tests 2025-01-08 11:47:55 +03:00
7f3294a8a2
update tests 2025-01-08 11:47:41 +03:00
4b5f08e25e
Merge branch 'master' into postgres-switch-for-tests 2025-01-08 11:06:15 +03:00
ea9cab930e
cleanup the generated test data for the schema 2025-01-08 10:59:22 +03:00
a37f6e6da3
pass the dbschema in the context 2025-01-08 10:57:58 +03:00
f59c3a53ef
allow the BuildConnStr to be accessed by different packages 2025-01-08 10:56:59 +03:00
81c3378ea6
use a flag to pass the schema to the context 2025-01-08 10:55:43 +03:00
46a6d2bc6e
create a schema if it does not exist and use it in the connection 2025-01-08 10:37:47 +03:00
lash
721f80d0f2
Repalce missing context 2025-01-08 07:34:22 +00:00
f49e54a562 Merge pull request 'Space after comma' (#259) from lash/helpcomma into master
Reviewed-on: urdt/ussd#259
2025-01-08 07:57:18 +01:00
lash
5081b6d4ce
Space after comma 2025-01-08 06:48:35 +00:00
4d72ae0313
add handler showing a message for a blocked account 2025-01-08 09:30:51 +03:00
4fe64a7747
show message for a blocked account 2025-01-08 09:29:00 +03:00
3004698d5b
add a message for a blocked account 2025-01-08 09:28:31 +03:00
50c7ff1046
register handler to show blocked account message 2025-01-08 09:27:45 +03:00
07b061a68b
remove blocked account templates 2025-01-08 09:26:53 +03:00
6339f0c2e5 Merge branch 'master' into lash/purify-more 2025-01-07 17:58:19 +01:00
lash
1fa830f286
Add auth conn string to ssh, use connstr for execs 2025-01-07 13:51:26 +00:00
64fba91670
catch blocked account 2025-01-07 14:38:44 +03:00
c15958a1ad
reset incorrect pin attempts on correct entry 2025-01-07 14:32:44 +03:00
ee442daefa
add blocked account node 2025-01-07 14:03:53 +03:00
656052dc74 Merge pull request 'trim any leading whitespace in the input' (#258) from send-input-fix into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#258
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2025-01-07 10:33:20 +01:00
6c5873da6f
trim any leading whitespace in the input 2025-01-07 12:15:15 +03:00
11d30583a4
map content of reset_incorrect and catch blocked account 2025-01-07 10:50:30 +03:00
f83f539046
add node to show remaining pin attempts 2025-01-07 10:48:59 +03:00
562bd4fa24
check for incorrect pin 2025-01-06 22:54:31 +03:00
90df0eefc3
add value for allowed number of PIN attempts 2025-01-06 22:53:59 +03:00
b37f2a0a11
add flag for when an account has been blocked 2025-01-06 21:06:54 +03:00
68e4c9af03
add key for incorrect pin attempts 2025-01-06 21:00:34 +03:00
c12e867ac3
add a db flag to specify the database of choice 2025-01-06 15:06:25 +03:00
79de0a9092
pass the base directory to load the .env file 2025-01-06 14:54:04 +03:00
3ee15497a5
specify the base directory for loading the .env file 2025-01-06 14:50:39 +03:00
lash
e09e324a50 Merge branch 'lash/purify-more' into lash/purify-max 2025-01-06 09:03:30 +00:00
lash
599815c343
Fix remaining conflict in cmd cli 2025-01-06 09:00:41 +00:00
lash
462c0d7677 Merge branch 'master' into lash/purify-more 2025-01-06 08:59:42 +00:00
80b96e9bf6 Merge pull request 'Add gettext capability to template and menu resources' (#239) from lash/gettext into master
Reviewed-on: urdt/ussd#239
2025-01-06 09:49:53 +01:00
b5561decd1 Merge branch 'master' into lash/gettext 2025-01-06 09:48:33 +01:00
lash
02823fd64e Merge branch 'lash/ssh-fixes' into lash/purify-more 2025-01-06 08:47:49 +00:00
lash
cd575c2edb Merge remote-tracking branch 'urdt' into lash/purify-more 2025-01-06 08:46:01 +00:00
f3d4f35718 Merge pull request 'Factor out db dump formatting' (#243) from lash/dump-format into master
Reviewed-on: urdt/ussd#243
2025-01-06 09:44:29 +01:00
52787bdb4d Merge branch 'master' into lash/dump-format 2025-01-06 09:42:26 +01:00
lash
f8d8f265f1
Merge upstream 2025-01-06 08:40:50 +00:00
lash
9371b52f3e Merge branch 'lash/ssh-fixes' into lash/purify-max 2025-01-06 08:39:40 +00:00
lash
563000ec15 Merge branch 'lash/purify-more' into lash/purify-max 2025-01-06 08:38:05 +00:00
824d39908b
ci: fix missing ssh dir 2025-01-06 11:19:36 +03:00
lash
52fd1eced2
Enable env, config, db in ssh 2025-01-06 08:11:37 +00:00
a312ea5b84
feat: inject build string in ssh binary, expose default ssh port 2025-01-06 11:09:51 +03:00
lash
5c7a535288 Merge branch 'lash/purify-more' into lash/ssh-fixes 2025-01-06 07:55:40 +00:00
4836162f40
ci: add ssh build 2025-01-06 10:51:20 +03:00
lash
cc2f7b41df Merge branch 'master' into lash/purify-more 2025-01-06 07:50:55 +00:00
lash
d39740a09a
Edit ssh cli help text 2025-01-06 07:41:24 +00:00
lash
2024cc96e2
Bring up-to-date with refactor 2025-01-06 07:22:58 +00:00
lash
d2d878d5d7 Merge branch 'master' into lash/ssh-4 2025-01-06 07:12:00 +00:00
c995143543 Merge pull request 'log-session-id-at-sessionid' (#251) from log-session-id-at-sessionid into master
Reviewed-on: urdt/ussd#251
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2025-01-06 08:01:20 +01:00
44570e20ef
remove unused context key :- at-session-id 2025-01-06 09:59:47 +03:00
362eb209ef
add SessionId to context key 2025-01-06 09:54:28 +03:00
c69d3896f1
pass context as an argument,rename context keys 2025-01-06 08:52:53 +03:00
974af6b2a7
pass context as an argument 2025-01-06 08:50:53 +03:00
lash
bb4037e73f
Add languages env example 2025-01-05 21:25:09 +00:00
lash
739fd90dfd
Expose httpmocks 2025-01-05 21:04:21 +00:00
lash
6789c4f550
Export handlers 2025-01-05 11:17:58 +00:00
lash
437f73827d
Rehabilitate all tets 2025-01-05 09:54:19 +00:00
lash
f0a4a0df61
Correct package name in errors, add state store getter 2025-01-05 07:19:25 +00:00
lash
bd604219b8
WIP Factor out request, errors 2025-01-04 22:27:46 +00:00
lash
51b6fc0dde
Remove unused methods in storage interfaces, improve logs 2025-01-04 21:13:39 +00:00
lash
cc9760125a
Remove unnecessary connection chain step 2025-01-04 21:02:08 +00:00
lash
3a9f3fa373
Update env example 2025-01-04 20:54:51 +00:00
lash
89c21847b9
Improve error message 2025-01-04 20:52:49 +00:00
lash
450dfa02cc
Refactor to use conndata as menustorageservice conn arg 2025-01-04 20:36:18 +00:00
lash
f61e65f4fe
Rename accountservice testservice source file 2025-01-04 13:24:14 +00:00
lash
a4d6cef9c0
Remove commented code 2025-01-04 13:21:31 +00:00
lash
2992f7ae8e
Update executables with new conn str 2025-01-04 13:19:30 +00:00
lash
dc61d05584
WIP revert connstr gdbm to dir only, add file as table spec 2025-01-04 12:16:28 +00:00
lash
83857026d3 Merge branch 'master' into lash/dump-format 2025-01-04 10:00:25 +00:00
lash
349051b5ef Merge branch 'master' into lash/gettext 2025-01-04 09:59:26 +00:00
47b5ff0435 Merge pull request 'Improve separation of concerns in all modules, phase 1' (#246) from lash/purify into master
Reviewed-on: urdt/ussd#246
2025-01-04 10:56:17 +01:00
lash
e92e498726 Merge branch 'lash/purify' into lash/purify-more 2025-01-04 09:46:18 +00:00
lash
25867cf05e
Rehabilitate voucher test 2025-01-04 09:42:36 +00:00
lash
c3cbe1cd92
Add connstr to last executable 2025-01-04 09:41:24 +00:00
lash
418080d093 Merge branch 'lash/purify' into lash/purify-more 2025-01-04 09:38:23 +00:00
lash
2e30739ec9
Implement connstr 2025-01-04 09:37:12 +00:00
d5a2680500
make context accessible 2025-01-04 12:02:45 +03:00
lash
dc1674ec55
WIP add connection string parser 2025-01-04 08:40:43 +00:00
lash
d950b10b50
Move prefix db spec to separate package 2025-01-04 08:37:28 +00:00
lash
bcb3ab905e
Move db related to own package 2025-01-04 08:09:18 +00:00
lash
3ed9caf16d
Factor out request parsers 2025-01-04 08:02:44 +00:00
lash
86464c31d2 Merge branch 'master' into lash/purify 2025-01-04 07:57:54 +00:00
5ee10d8e14 Merge pull request 'logs-at-sessionid' (#245) from logs-at-sessionid into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#245
2025-01-04 08:56:09 +01:00
62f3681b9e
define context keysessionid using go-vise --withcontext 2025-01-04 10:40:26 +03:00
3ce1435591
extract session id from africastalking request 2025-01-04 10:38:25 +03:00
f65c458daa
update go-vise. 2025-01-04 10:35:59 +03:00
lash
67007fcd48
Factor out gdbm package 2025-01-04 07:35:28 +00:00
lash
f1b258fa6d
Factor out at code 2025-01-04 07:29:22 +00:00
lash
daec816a3e
Move store devtools location 2025-01-03 17:21:52 +00:00
lash
ac0c43cb43
Factor out formatting method 2025-01-03 17:18:23 +00:00
lash
9013cc3618
Improve error messages 2025-01-03 15:10:20 +00:00
lash
056d056613
Add language source and template file generator 2025-01-03 14:43:08 +00:00
lash
e581ec4771 Merge tag 'v0.8.0-beta.4' into lash/gettext 2025-01-03 10:29:17 +00:00
lash
e16b7445e8
Move arg var to same spot as other runners 2025-01-03 10:28:27 +00:00
lash
1b12f0ba5f
Add po language alternative to all runners 2025-01-03 10:00:52 +00:00
d2fce05461 Merge pull request 'fix: language change' (#242) from language-change-fix into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#242
2025-01-03 09:30:27 +01:00
68ac237449 Merge branch 'master' into language-change-fix 2025-01-03 09:28:48 +01:00
162e6c1934
fix: language change 2025-01-03 11:26:56 +03:00
8bd025f2b2 Merge pull request 'hash-pin' (#235) from hash-pin into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#235
2025-01-03 09:25:26 +01:00
9d6e25e184
revert to previous state for the adminstore 2025-01-03 11:24:24 +03:00
c26f5683f6
removed second unused argument 2025-01-03 11:17:09 +03:00
91dc9ce82f
tests: add sample pin/hash pair from migration dataset 2025-01-03 11:10:07 +03:00
0fe48a30fa
Merge branch 'master' into hash-pin 2025-01-03 06:58:41 +03:00
lash
c1e0617bb3
Update go-vise 2025-01-02 21:13:06 +00:00
lash
6723884103
Update go-vise 2025-01-02 21:02:01 +00:00
lash
b888af446d
update govise 2025-01-02 18:49:16 +00:00
lash
43b2c3b78d
Rehabilitate gettext resource 2025-01-02 18:13:37 +00:00
lash
d67853f6d9 Merge branch 'master' into lash/gettext 2025-01-02 14:53:18 +00:00
58edfa01a2 Merge pull request 'menu-primary-selectors' (#237) from menu-primary-selectors into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#237
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2025-01-02 15:50:42 +01:00
3830c12a57
update tests 2025-01-02 17:42:03 +03:00
f1fd690a7b
update expected content 2025-01-02 17:37:26 +03:00
lash
06230dc557
Add todo comment 2025-01-02 14:31:13 +00:00
491b7424a9
point to the correct ./devtools/admin_numbers directory 2025-01-02 16:01:19 +03:00
29ce4b83bd
added tests for HashPIN and VerifyPIN 2025-01-02 15:22:07 +03:00
ca8df5989a
updated expected age in test 2025-01-02 15:15:52 +03:00
82b4365d16
hash the PIN in TestAuthorize 2025-01-02 14:38:22 +03:00
98db85511b
hash the PIN in the ResetOthersPin function 2025-01-02 14:37:45 +03:00
99a4d3ff42
verify the PIN input against the hashed PIN 2025-01-02 13:51:57 +03:00
d95c7abea4
return if the PIN is not a match, and hash the PIN before saving it 2025-01-02 13:45:18 +03:00
fd1ac85a1b
add code to Hash and Verify the PIN 2025-01-02 13:43:38 +03:00
c899c098f6
updated the expected age 2025-01-02 13:20:01 +03:00
5ca6a74274
move PIN test to the common package 2025-01-02 13:18:49 +03:00
48d63fb43f
added pin.go to contain all PIN related functionality 2025-01-02 13:16:38 +03:00
lash
6ee2c88fe2
Implement gettext spec in local vm cmd 2025-01-02 09:39:49 +00:00
e666c58644
start primary selectors with 1 2025-01-02 12:17:28 +03:00
e980586910
chore: repeat same node on invalid menu choice 2025-01-02 12:15:57 +03:00
ffd5be1f1f
add quit option on view profile 2025-01-02 12:12:52 +03:00
ed1aeecf7d Merge pull request 'Legible dumper' (#232) from lash/dump-key-prefix into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#232
2024-12-31 09:56:04 +01:00
3b69f3d38d Merge branch 'master' into lash/dump-key-prefix 2024-12-31 09:55:40 +01:00
lash
cd58f5ae33
Upgrade govise 2024-12-31 08:55:25 +00:00
7a535f796a
output the value as a string 2024-12-31 11:41:04 +03:00
7c4c73125e Merge pull request 'force-restart-state' (#223) from force-restart-state into master
Reviewed-on: urdt/ussd#223
2024-12-31 09:34:44 +01:00
lash
c7dbe1d88f
Remove obsolete subprefix strings 2024-12-31 08:30:08 +00:00
4ea52bf3fb
removed unused code 2024-12-31 11:16:43 +03:00
be2ea3a2f0
removed the non-working restart_state devtool 2024-12-31 10:51:29 +03:00
8217ea8fdc Merge branch 'master' into force-restart-state 2024-12-31 05:06:26 +03:00
3c73fc7188
added a test for the Init func with the different states 2024-12-31 05:05:39 +03:00
1311a0cab9
use the 'send_with_invite' test group in the menu traversal test 2024-12-31 02:36:28 +03:00
lash
3bcd48e5a7
Update govise 2024-12-30 19:58:34 +00:00
lash
0e12c0ee4e
Add prefix for dumper, format base dump key for pg 2024-12-30 19:35:45 +00:00
3caee98cdb Merge pull request 'mixed-languages' (#228) from mixed-languages into master
Reviewed-on: urdt/ussd#228
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-30 09:07:13 +01:00
db7c9bf56d
chore: add colon to enhance formatting. 2024-12-30 08:07:39 +03:00
0a332ec501
chore: ensure swahili language translation. 2024-12-30 08:05:36 +03:00
90367fe53e Merge pull request 'profile-update-fix' (#226) from profile-update-fix into master
Reviewed-on: urdt/ussd#226
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-28 16:20:32 +01:00
50c006546c
added code to reset the state and persist it 2024-12-28 13:21:03 +03:00
e8c171a82e Merge branch 'master' into force-restart-state 2024-12-28 11:46:15 +03:00
58a60f2c81
update expected age in test 2024-12-28 08:51:38 +03:00
0820e1b9f2 Merge branch 'master' into profile-update-fix 2024-12-28 06:30:14 +01:00
46edf2b819
remove
redundant catch on pin entry
2024-12-27 16:13:36 +03:00
11eb61ba35
repeat same node on invalid selection 2024-12-27 16:11:09 +03:00
813b92af78 Merge pull request 'issue-205: added comments for menu handlers methods.' (#218) from konstantinmds/ussd:dev/issue-205 into master
Reviewed-on: urdt/ussd#218
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-27 12:58:00 +01:00
5579991d66
guard profile update after being set 2024-12-27 10:07:05 +03:00
f4f4fdd3ac issue-205:
added comments for menu handlers methods and changed function name to better fit function workings.
2024-12-25 15:59:28 +01:00
be215d3f75
set the code to an empty byte for it to move to the top node 2024-12-20 12:26:07 +03:00
235af3519d Merge pull request 'add-space-after-colon' (#211) from add-space-after-colon into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#211
2024-12-19 11:35:13 +01:00
1292851226
rename the function to ReplaceSeparatorFunc 2024-12-19 13:32:39 +03:00
dfd0a0994b Merge branch 'master' into force-restart-state 2024-12-18 22:39:20 +03:00
97fcdda12f
Merge branch 'master' into add-space-after-colon 2024-12-18 22:30:41 +03:00
055c2db790
use a common mockReplaceSeparator func 2024-12-18 22:25:47 +03:00
ecfdab47a8
updated test 2024-12-18 22:21:52 +03:00
fda68231ea
use the replaceSeparator func to format the generated menus 2024-12-18 21:59:09 +03:00
d08afff443
add the replaceSeparator func and pass it to the Handler struct 2024-12-18 21:56:37 +03:00
17ba6a06ba
remove the MenuSeparator from the context 2024-12-18 21:49:42 +03:00
dbd59a4023 Merge pull request 'add link to terms page' (#217) from link-terms-and-conditions into master
Reviewed-on: urdt/ussd#217
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-18 07:45:48 +01:00
5534706189
reset the state when input is nil 2024-12-17 17:58:08 +03:00
5428626c3f
cleaned up the restart_state 2024-12-17 17:56:56 +03:00
9b33117cb1
add space on expected content 2024-12-17 16:02:35 +03:00
70b2fa4ac2
add spacing after link 2024-12-17 15:46:28 +03:00
fd6ff86579
add link to terms and conditions as expected content 2024-12-17 15:24:15 +03:00
549782f230
add link to terms page 2024-12-17 15:12:38 +03:00
8cf4848b45 Merge pull request 'Userstore dumper tool' (#153) from lash/store-dumper into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#153
2024-12-14 13:02:48 +01:00
9f6c0a1111 Merge branch 'master' into lash/store-dumper 2024-12-14 13:02:38 +01:00
lash
1ab49647f6
Upgrade vise 2024-12-14 12:02:13 +00:00
lash
8d4d8a48e0
Fix compile errors, test errors 2024-12-14 11:56:31 +00:00
7aea2af9a1
updated tests 2024-12-13 11:44:04 +03:00
5cd791aae7
use the MenuSeparator 2024-12-13 11:43:47 +03:00
df5e5f1a4b
properly format the vouchers 2024-12-13 11:40:39 +03:00
64c1fe5276
set the separator as a var and add it to the context 2024-12-13 11:38:10 +03:00
f38ea59569
set the separator as a config and not an arg 2024-12-13 01:10:46 +03:00
6cc285d1e8
add the custom separator to the menu 2024-12-12 21:12:25 +03:00
0d7f7aaca1
use latest commits from go-vise 2024-12-12 21:09:48 +03:00
f8ea2daa73
initialize the restart state devtool 2024-12-12 19:55:01 +03:00
lash
c820e89cb7
Segment tx userdata types, add internals docs 2024-12-11 19:13:13 +00:00
e05f8e7291
update to the latest go-vise changes 2024-12-11 19:46:52 +03:00
2383e8ead3
updated failed menuhandler_test 2024-12-11 19:35:04 +03:00
1a4ee0d3e1
updated the description of the GetTransactionsList function 2024-12-11 19:32:41 +03:00
6f3b30e2fe
Capitalize statement details and add a space after the colon 2024-12-11 19:31:17 +03:00
b1e4b63c6a
Add a space after the colon for vouchers 2024-12-11 19:28:53 +03:00
3129e8210e
Capitalize the transfer statement details 2024-12-11 19:25:38 +03:00
5d8de80a18
Write the error in the response 2024-12-11 18:58:50 +03:00
lash
604c16ec90
Habilitate store dumper 2024-12-08 22:48:39 +00:00
lash
a3e821fb16
Clarify gen cmd example with reverse public key lookup 2024-12-08 21:57:53 +00:00
lash
890f50704f
Add documentation comments for db subprefix types 2024-12-08 21:21:48 +00:00
lash
3416fdf50c
Add keyinfo restore 2024-12-07 23:06:03 +00:00
43892f0d8c Merge pull request 'profile-edit-traverse' (#199) from profile-edit-traverse into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#199
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-05 16:37:37 +01:00
8e6b1e6f52
Merge branch 'master' into profile-edit-traverse 2024-12-05 18:34:29 +03:00
lash
ff943a125c Merge branch 'master' into lash/store-dumper 2024-12-05 15:30:29 +00:00
9cbbdff993
chore: use profileDataKeys as an array 2024-12-05 18:25:51 +03:00
316358765d Merge pull request 'data-items-cleanup' (#203) from data-items-cleanup into master
Reviewed-on: urdt/ussd#203
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-05 16:03:50 +01:00
14737b5f12
Removed redundant naming of transfer related data 2024-12-05 17:58:51 +03:00
caff27b43d
Replace IntToBytes(value int) and ToBytes() with a single ToBytes() function 2024-12-05 17:50:40 +03:00
589a94216b
Use the DATATYPE_USERDATA as the prefix for the NewSubPrefixDb func 2024-12-05 17:02:26 +03:00
a659fb06fa
Added a function to convert int to []byte 2024-12-05 16:58:03 +03:00
f733fe5636
Start the sub prefix data at 256 (0x0100) 2024-12-05 16:31:47 +03:00
18423fcd9c
updated the name of the voucher related data 2024-12-05 16:26:56 +03:00
72a3681767
add test data. 2024-12-05 16:03:08 +03:00
160ccbb220
read test file from test run arg 2024-12-05 14:05:32 +03:00
22f96363ba
add test files 2024-12-05 14:04:56 +03:00
3e7f90733e
update test names 2024-12-05 12:22:29 +03:00
321f038c7c
iterate over a map for the set profile items 2024-12-05 10:52:45 +03:00
7a9de79aae
return nil with error 2024-12-04 21:07:11 +03:00
862830e9de
renamed internal/storage/db.go -> internal/storage/sub_prefix_db.go for clarity 2024-12-04 20:59:46 +03:00
bc0e536d3d
updated failing tests 2024-12-04 20:55:03 +03:00
82884a75a3
Merge branch 'master' into data-items-cleanup 2024-12-04 20:45:38 +03:00
93c44861e0
Use numeric prefixes 2024-12-04 20:42:47 +03:00
4ecfc9de38
removed DATATYPE_USERSUB and replaced with DATATYPE_USERDATA 2024-12-04 20:38:52 +03:00
a84c3e0852
update menu traversal test data 2024-12-04 09:46:16 +03:00
8efed966a0
set flag when location is set 2024-12-04 09:08:47 +03:00
e7c4b5bca7
update tests 2024-12-04 09:08:25 +03:00
ed632248c5
add doc lines and check for back naviagtions 2024-12-04 08:30:07 +03:00
5c202741d6
add handler for catching back navigations 2024-12-04 08:20:43 +03:00
c5ebdbf85b
catch back navigations 2024-12-04 08:20:09 +03:00
c4282a870e
add flag to catch back navigations 2024-12-04 08:19:20 +03:00
91cd6077ce
Merge branch 'master' into profile-edit-traverse 2024-12-03 22:25:47 +03:00
c0ed6fa9c8
catch incorrect pin entries 2024-12-03 21:43:24 +03:00
22c9c3e0f0 Merge pull request 'minor-bug-fixes' (#177) from minor-bug-fixes into master
Reviewed-on: urdt/ussd#177
2024-12-03 18:18:23 +01:00
a709d24520 Merge branch 'master' into minor-bug-fixes 2024-12-03 18:16:19 +01:00
e56138e416 Merge pull request 'Clear persister from handler in outer code aswell' (#200) from lash/persister-freakout into master
Reviewed-on: urdt/ussd#200
2024-12-03 17:18:11 +01:00
lash
d516584d90
Clear persister from handler in outer code aswell 2024-12-03 16:16:53 +00:00
b420a9bba0
set flag if profile data is set 2024-12-03 17:41:06 +03:00
a20ab79355
explicit reload save gender 2024-12-03 17:38:20 +03:00
e0ec15b272
allow sequential profile edit 2024-12-03 14:40:57 +03:00
9e998f9a29
add a zero pad value to unfilled profile item 2024-12-03 14:37:55 +03:00
1c7c0af712
catch next unset profile item 2024-12-03 14:36:48 +03:00
d40a4a171f
formatted code 2024-12-03 14:12:47 +03:00
ba430a5849
add a separate function to handle ConstructName 2024-12-03 14:10:05 +03:00
0f21b01813
resolved error in the TestViewVoucher 2024-12-03 13:37:00 +03:00
10586baf0d
resolved error in the TestCheckBalance 2024-12-03 13:35:14 +03:00
e979742424
resolved error in the TestValidateRecipient 2024-12-03 13:32:18 +03:00
ff3f049226
updated the CheckAliasAddress mock 2024-12-03 13:31:30 +03:00
13b45c49da Merge branch 'master' into minor-bug-fixes 2024-12-03 12:58:26 +03:00
a72fb08dc8
allow sequential profile edit 2024-12-03 11:20:35 +03:00
944fa89b3c
add profile holder struct 2024-12-03 11:19:38 +03:00
48e1b02e0e
allow all item profile edit 2024-12-02 20:50:21 +03:00
3e0bbe5ffe
add handler to update profile items 2024-12-02 20:35:56 +03:00
fd586d09c3
add required profile edit flags 2024-12-02 20:35:04 +03:00
lash
c2a4efde2b Merge branch 'master' into lash/store-dumper 2024-12-02 15:39:29 +00:00
b615c27cf6 Merge pull request 'trigger-balance-reload' (#193) from trigger-balance-reload into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#193
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-02 16:36:26 +01:00
22e870b3e5 Merge branch 'master' into trigger-balance-reload 2024-12-02 16:36:08 +01:00
da462346f9 Merge pull request 'Always reset persister in handler' (#194) from lash/no-persister-deadlock into master
Reviewed-on: urdt/ussd#194
2024-12-02 16:17:36 +01:00
lash
406bd84875
Always reset persister in handler 2024-12-02 14:53:18 +00:00
lash
c9deca1180
change userdata prefix on subprefix debug 2024-12-02 14:36:33 +00:00
419cd185fc Merge branch 'master' into minor-bug-fixes 2024-12-02 15:25:34 +03:00
7976e237ca Merge pull request 'voucher-details' (#179) from voucher-details into master
Reviewed-on: urdt/ussd#179
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-12-02 11:43:30 +01:00
19ec8f0817 Merge branch 'master' into voucher-details 2024-12-02 13:32:32 +03:00
ef3a3d6717
updated the TestGetVoucherDetails 2024-12-02 13:30:33 +03:00
c2019267d1
capitalize the voucher descriptions 2024-12-02 13:26:46 +03:00
0091fbcabb
fix: looped navigation 2024-12-02 09:03:04 +03:00
lash
6d4f3109f8
Consolidate subtyp and typ debug 2024-12-01 23:12:58 +00:00
lash
35cf3a1cd1
Add debug label test, capability debug flag 2024-12-01 18:41:28 +00:00
lash
1a782c1db9
Add debug package 2024-12-01 17:32:06 +00:00
lash
3d2ca606ca Merge branch 'master' into lash/store-dumper 2024-12-01 16:34:10 +00:00
aa7497573e
removed unused code 2024-11-30 15:29:28 +03:00
54c1fe51ef
update the active voucher data when checking the current vouchers 2024-11-30 15:28:21 +03:00
7a86b2ad3b
updated the UpdateVoucherData description 2024-11-30 15:26:13 +03:00
6b23c284e5
check vouchers before checking the balance 2024-11-30 15:24:14 +03:00
aab6660edd
Capitalize menu items 2024-11-29 15:39:27 +03:00
c46f41e25f
Format the balance to 2 decimal places 2024-11-29 14:47:22 +03:00
00c0445eed
show name without depending on family name being set 2024-11-28 11:42:47 +03:00
c8c6b05b8a Merge branch 'master' into minor-bug-fixes 2024-11-26 15:31:45 +03:00
08ff1056d7 Validate aliases, addresses and phone numbers in the send menu (#176)
Some checks failed
release / docker (push) Has been cancelled
- Update the phone number regex
- Check whether the recipient is a valid phone number, alias or address

Reviewed-on: urdt/ussd#176
Co-authored-by: alfred-mk <alfredmwaik@gmail.com>
Co-committed-by: alfred-mk <alfredmwaik@gmail.com>
2024-11-26 07:24:57 +01:00
0f4a7e900f Merge pull request 'feat: upgrade go-vise dep, minor cleanups to go.mod' (#182) from sohail/upgrade-deps into master
Reviewed-on: urdt/ussd#182
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-11-26 07:02:07 +01:00
2d6c434bde
feat: upgrade go-vise dep, minor cleanups to go.mod
* added stub for Dump()
2024-11-25 09:56:24 +03:00
f5d2644031 Merge pull request 'account-statement' (#126) from account-statement into master
Reviewed-on: urdt/ussd#126
2024-11-22 19:24:08 +01:00
09b4fa2860 Merge branch 'master' into account-statement 2024-11-22 19:23:59 +01:00
a748c1b6b2
chore: fix old reference 2024-11-22 11:53:13 +03:00
a17cf78d29
Merge remote-tracking branch 'refs/remotes/origin/minor-bug-fixes' into minor-bug-fixes 2024-11-22 11:35:57 +03:00
9847433e0a
use _ for back navigation 2024-11-22 11:30:13 +03:00
7ce50398d1
use the language translation instead of hardcoded eng 2024-11-21 15:54:00 +03:00
e30bc177e9
fixed typo and added a new translation 2024-11-21 15:52:07 +03:00
b9ff467c0c
use the correct balance 2024-11-21 15:51:04 +03:00
1174500e3f
add test for voucher details 2024-11-21 15:19:36 +03:00
07df450b3c
include labels to define the symbol and balance while selecting a voucher 2024-11-21 15:15:15 +03:00
b8d938d3aa
add voucher details 2024-11-21 13:04:19 +03:00
d1e9340ea9
add voucher details 2024-11-21 13:03:43 +03:00
8925e26c4c
refactor check for valid yob 2024-11-21 11:25:47 +03:00
9b89462797
add function to check validity of provided yob 2024-11-21 11:21:16 +03:00
51bad64a51 Merge branch 'master' into account-statement 2024-11-20 19:00:14 +03:00
7880294c6f
set eng as default language 2024-11-20 17:14:25 +03:00
451b15fb6b
explicit set_language reload 2024-11-20 17:13:14 +03:00
d20700ca74
fix size limit error 2024-11-20 16:39:50 +03:00
9cf1cbe425
add swahili translation for catch node 2024-11-20 16:36:18 +03:00
212cd48249
debug: postgres conn string
Some checks failed
release / docker (push) Has been cancelled
2024-11-19 17:53:06 +03:00
7adc0c9c08
fix: Dockerfile to include .env
Some checks failed
release / docker (push) Has been cancelled
2024-11-19 17:25:20 +03:00
0a19a6c48b
fix: github ci spec
Some checks failed
release / docker (push) Has been cancelled
2024-11-19 17:18:13 +03:00
66b5843b0d feat: dockerfile and github based CI build for Africastalking variant (#174)
## Summary

* fixed missing error handler in main
* add injectable build string in main
* add (dynamically linked) docker build
* add github CI workflow
* add extra but useful dev files in dev folder

* closes #93

## Notes

* We'll move to a self-hosted CI runner once it is installed on Gitea.

Reviewed-on: urdt/ussd#174
Co-authored-by: Mohammed Sohail <sohailsameja@gmail.com>
Co-committed-by: Mohammed Sohail <sohailsameja@gmail.com>
2024-11-19 15:15:24 +01:00
df89fe69e1
return to the main node 2024-11-19 17:09:59 +03:00
9498242901
removed debug statement 2024-11-19 16:27:19 +03:00
eab6dbd74c
Check if index is within range 2024-11-19 16:23:35 +03:00
6159686a8e
update function comment 2024-11-19 16:21:55 +03:00
1d82bc2261
added transactions functions 2024-11-19 16:07:28 +03:00
bfcdd79f33
view single statement 2024-11-19 16:04:46 +03:00
d49b68597c
check transactions and catch when none exist 2024-11-19 16:03:54 +03:00
1d9f4fc13e
added statement related flags 2024-11-19 15:50:19 +03:00
6fa0d8e2ff Merge branch 'master' into account-statement 2024-11-19 10:33:09 +03:00
109a2a2020 Merge pull request 'menu-balances' (#173) from menu-balances into master
Reviewed-on: urdt/ussd#173
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-11-19 08:18:43 +01:00
bc6d8098f3
updated the failed test 2024-11-19 10:15:34 +03:00
01e75e9217
update test 2024-11-18 17:30:34 +03:00
f2b17880ba
check community and my balance separately 2024-11-18 17:30:09 +03:00
1d4f116079
community balance str 2024-11-18 17:23:59 +03:00
44c52b6ed7
rename fetch_custodial_balances -> fetch_community_balance 2024-11-18 17:23:36 +03:00
454f67b317
show balance based on current voucher 2024-11-18 17:21:04 +03:00
e1506a3dcf
show a placehlolder community balance 2024-11-18 17:20:12 +03:00
b22a4adec1 Merge pull request 'updated send node' (#172) from send-node into master
Reviewed-on: urdt/ussd#172
2024-11-18 14:19:42 +01:00
1ba90a8b78
updated the test 2024-11-16 14:52:24 +03:00
5dd4f2a3fb
scale down the balance according to the decimals 2024-11-16 14:52:02 +03:00
b40ad78294
add the GetVoucherDetails function 2024-11-15 21:03:57 +03:00
11bb194f26
add a struct to access the tokenDetails 2024-11-15 21:03:29 +03:00
c0ccdce0a9
add the voucher details node 2024-11-15 21:02:44 +03:00
7d16b710d8
set the TokenDecimals as int to match the API response 2024-11-15 20:50:07 +03:00
c8fc32a4e7
cleaned up models 2024-11-15 18:50:06 +03:00
baeb5e0ccb
use a single doRequest and updated the structs for TokenHoldings and Transfers 2024-11-15 18:37:20 +03:00
51bf2534b8
use a single bearer token 2024-11-15 18:35:49 +03:00
d3fae34290
modified the send node traversal 2024-11-15 13:24:54 +03:00
1a77092ccb
updated the menuhandler tests 2024-11-15 13:12:30 +03:00
36846c2587
added TestReadTransactionData 2024-11-15 12:13:09 +03:00
222d801ecc
added TestParseAndScaleAmount 2024-11-15 11:49:47 +03:00
1a0b4deab3
added the TransactionData struct to organiza the data 2024-11-15 09:20:19 +03:00
cb13b09291
remove commented code 2024-11-15 08:42:44 +03:00
de5ecc5fe7
add the tokens functionality to the common package 2024-11-14 21:21:04 +03:00
5773305785
parse data and initialize a transaction 2024-11-14 20:10:48 +03:00
7985b20200
added message string for failed transaction 2024-11-14 20:09:50 +03:00
c34906cb1f
added the TokenTransfer functionality and mocks 2024-11-14 20:02:48 +03:00
c10e1a6a1b
added the TokenTransferResponse model 2024-11-14 19:36:30 +03:00
fabcccfa60
added the tokenTransfer url 2024-11-14 19:35:44 +03:00
f3e3badff6
save the entire voucher data when setting the default voucher 2024-11-14 17:31:17 +03:00
9d2d01e3e2
save the recipient number in DATA_TEMPORARY_VALUE 2024-11-14 17:30:13 +03:00
93df6a6a08
validate recipients and invite valid ones 2024-11-14 14:59:39 +03:00
8c13e44a15 Merge pull request 'profile-edit-show' (#171) from profile-edit-show into master
Reviewed-on: urdt/ussd#171
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-11-14 09:27:42 +01:00
345dfbaa21
Merge remote-tracking branch 'refs/remotes/origin/profile-edit-show' into profile-edit-show 2024-11-14 11:01:42 +03:00
381e581e8e
add terminal logs 2024-11-14 11:00:12 +03:00
94d2e8203f
reload verify yob 2024-11-14 10:53:20 +03:00
f9e51618c5
remove extra line 2024-11-14 10:29:22 +03:00
f97ad2a262
use 1 for retry 2024-11-14 10:25:42 +03:00
59b14301ad Merge branch 'master' into profile-edit-show 2024-11-14 10:08:25 +03:00
8b097a4395 Merge pull request 'log directly to the terminal' (#170) from terminal-logs into master
Reviewed-on: urdt/ussd#170
2024-11-14 08:01:59 +01:00
fd2486b5cf
Merge branch 'terminal-logs' into profile-edit-show 2024-11-13 21:04:40 +03:00
f9f25d898b
use logg 2024-11-13 19:00:27 +03:00
b6b3ef83a4
updated the comment to match the functionality 2024-11-13 18:02:02 +03:00
d7232a53ef
use the bearer token 2024-11-13 16:16:32 +03:00
047bf0e12e
updated the env example 2024-11-13 16:15:37 +03:00
09f61eb64d
log all errors from the hander 2024-11-13 15:19:45 +03:00
abdb17640b
add terminal logs 2024-11-12 10:36:08 +03:00
9af7b775a7
log directly to the terminal 2024-11-11 16:32:17 +03:00
97741b113b
Merge branch 'master' into profile-edit-show 2024-11-11 11:03:01 +03:00
0bb444cd50 Merge pull request 'updated README' (#169) from readme-documentation into master
Reviewed-on: urdt/ussd#169
2024-11-08 22:23:57 +01:00
1d07d7fb1d
updated README 2024-11-08 20:35:17 +03:00
a3e5aab6c4 Merge pull request 'Africastalking POST route' (#168) from africastalking-endpoint into master
Reviewed-on: urdt/ussd#168
2024-11-08 17:25:46 +01:00
9ebfb643aa
remove unused import 2024-11-08 17:28:20 +03:00
0aad21a52c
Merge branch 'master' into profile-edit-show 2024-11-08 17:21:27 +03:00
68d1628546
replace - with: Not provided 2024-11-08 17:19:41 +03:00
f4f95b3292
add some spacing 2024-11-08 17:15:27 +03:00
574807d254
set the africastalking POST route using env 2024-11-08 16:52:19 +03:00
256ed6491b Merge pull request 'http-logs' (#167) from http-logs into master
Reviewed-on: urdt/ussd#167
2024-11-08 12:51:30 +01:00
7a02ffcf0c Merge pull request 'log-file' (#166) from log-file into http-logs
Reviewed-on: urdt/ussd#166
2024-11-08 08:09:15 +01:00
dcd8fce59a
add log on create account 2024-11-08 10:07:06 +03:00
64a7b49218
Remove unused Warning logger 2024-11-08 00:21:11 +03:00
1bcbb2079e
Removed unused variable 2024-11-08 00:17:02 +03:00
e63468433e
Merge branch 'http-logs' into log-file 2024-11-08 00:14:34 +03:00
9c972ffa6b
add specific log files per binary 2024-11-07 16:46:12 +03:00
a11776e1b3
ignore .log files 2024-11-07 16:41:38 +03:00
6ac9ac29d8
add simple http logging 2024-11-07 16:41:08 +03:00
fc8915ea33
add http logging 2024-11-07 16:35:58 +03:00
cc36ddcb6d
add handler for showing the currently set profile information 2024-11-07 12:02:03 +03:00
f3388aef31
use _ for back 2024-11-07 11:59:12 +03:00
4e170b25e2
show currently set profile information 2024-11-07 11:58:58 +03:00
3e258a35fa
show currently set profile information 2024-11-07 11:58:25 +03:00
f66609bbae
add helper for getting db key from string 2024-11-07 09:18:11 +03:00
ebdc7b200a
register handler for getting current profile information 2024-11-07 09:16:49 +03:00
29e1e912d7
use edit prefix on each profile edit node 2024-11-06 17:52:22 +03:00
308f3327d0
use edit prefix on each profile edit information 2024-11-06 17:48:50 +03:00
46b2b354fd Merge pull request 'swahili-templates-menu' (#158) from swahili-templates-menu into master
Reviewed-on: urdt/ussd#158
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-11-05 11:29:07 +01:00
7676cfd40c
Merge branch 'master' into swahili-templates-menu 2024-11-05 13:25:49 +03:00
4e350aa25a
update test data file 2024-11-05 10:49:08 +03:00
584d02db29 Merge branch 'master' into lash/store-dumper 2024-11-05 00:19:06 +01:00
859de0513a Merge pull request 'api-error-fix' (#161) from api-error-fix into master
Reviewed-on: urdt/ussd#161
2024-11-05 00:18:04 +01:00
266d3d06c3
Check the flag_no_active_voucher before proceeding 2024-11-04 17:56:25 +03:00
92ea3df4aa
Removeearly return statement 2024-11-04 17:38:55 +03:00
c46c31ea36
ensure swahili translation 2024-11-04 16:06:33 +03:00
5937c6bf5c Merge branch 'master' into lash/store-dumper 2024-11-04 13:56:44 +01:00
da91eed9d4
add retry menu option 2024-11-04 15:55:58 +03:00
e2b28a31b2 Merge pull request 'lash/export-to-term' (#157) from lash/export-to-term into master
Reviewed-on: urdt/ussd#157
2024-11-04 13:54:58 +01:00
88b50c5dd7
Remove admin number defination in env example 2024-11-04 15:52:03 +03:00
43a1208cce
Merge branch 'master' into lash/export-to-term 2024-11-04 15:38:53 +03:00
2b865a365b
add back option to view address 2024-11-04 15:24:51 +03:00
c77558689a
ensure swahlili menu template 2024-11-04 15:24:22 +03:00
a9641fd70d
ensure swahili menu template 2024-11-04 15:24:01 +03:00
lash
2c30ccc405
Add voucherdata call to test accountservices 2024-11-04 02:23:30 +00:00
lash
7189235bee
Remove unused data type 2024-11-03 15:14:17 +00:00
lash
0506a8c452
Add voucherdata endpoint 2024-11-03 14:34:26 +00:00
lash
a237b615f2
Export models package 2024-11-03 01:44:57 +00:00
lash
dae12ac498
Separate subprefix db export 2024-11-02 23:41:08 +00:00
lash
1d77ad98dc
Expose subprefix db 2024-11-02 23:33:52 +00:00
lash
10b3083647
WIP Add db dumper tool 2024-11-02 20:29:32 +00:00
lash
3a8a5f40ba
Add test service placeholders for fetchtransactions 2024-11-02 16:42:50 +00:00
lash
14bc11f4bd
Add transaction getter 2024-11-02 16:38:29 +00:00
lash
e29a24b376
Add missing models files 2024-11-02 15:46:46 +00:00
lash
35a090ef42 Merge branch 'pre-mock-remove' into lash/export-to-term 2024-11-02 14:00:16 +00:00
2587882eae Merge pull request 'pin-reset' (#139) from pin-reset into pre-mock-remove
Reviewed-on: urdt/ussd#139
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-11-02 14:54:19 +01:00
24d4b8478e Merge pull request 'Remove db mocks' (#152) from remove-db-mocks into master
Reviewed-on: urdt/ussd#152
2024-11-02 13:44:03 +01:00
6dbe74d12b
use single temporary value 2024-11-01 16:46:09 +03:00
332074375a
wrap in devtools/admin 2024-11-01 16:44:54 +03:00
5e4a9e7567
Merge branch 'master' into pin-reset 2024-11-01 16:26:29 +03:00
eb2c73dce1
remove unused setadmin store 2024-11-01 11:51:50 +03:00
7e448f739a
change fs store path to root 2024-11-01 09:35:48 +03:00
0014693ba8
remove guard pin option 2024-11-01 06:39:37 +03:00
lash
9a528cfd14
Clean up messily failed conflict resolution 2024-11-01 03:17:01 +00:00
lash
8cc46d2782 Merge branch 'master' into lash/export-to-term 2024-10-31 22:58:36 +00:00
lash
bb1a846cb3 Merge remote-tracking branch 'origin/master' into lash/ssh-4 2024-10-31 20:52:09 +00:00
2704069e74
Merge branch 'master' into pin-reset 2024-10-31 21:09:48 +03:00
7d1a04f089
remove from root 2024-10-31 21:01:24 +03:00
53fa6f64ce
define structure of json 2024-10-31 21:01:01 +03:00
7fa38340dd
add command to initialize a list of admin numbers 2024-10-31 21:00:41 +03:00
7aab3cff8c
remove seed 2024-10-31 21:00:16 +03:00
299534ccf1
define seed as a command in the devtool 2024-10-31 20:59:51 +03:00
b2655b7f11
remove seed from executable 2024-10-31 20:59:11 +03:00
5abe9b78cc
attach an admin store for the phone numbers 2024-10-31 20:11:26 +03:00
12825ae08a
setup adminstore in the local handler service 2024-10-31 20:10:46 +03:00
ac0b4b2ed1
pass context 2024-10-31 20:08:30 +03:00
8fe8ff540b
Merge branch 'master' into pin-reset 2024-10-31 16:38:28 +03:00
lash
4bf56c525f
Export voucher related code 2024-10-31 13:19:44 +00:00
lash
33bba73a65 Merge branch 'master' into lash/export-to-term 2024-10-31 12:45:53 +00:00
lash
a17150962e Merge remote-tracking branch 'origin/lash/export-to-term' into lash/export-to-term 2024-10-31 12:39:48 +00:00
lash
3ae75b27a5
WIP replaced check account method but traversal crashes 2024-10-31 12:38:31 +00:00
lash
b2d180e8eb
WIP Trying to clean up account status check 2024-10-31 12:15:07 +00:00
lash
b9c56b04ce
Remove obsolete track account status code 2024-10-31 11:43:27 +00:00
bab3f673eb
Removed unused model 2024-10-31 14:13:21 +03:00
767a3cd64c
remove pin guard menu option 2024-10-31 09:38:51 +03:00
c4078c5280
remove extra spaces 2024-10-31 09:21:46 +03:00
lash
dc198215b1
Remove commented code 2024-10-31 02:03:29 +00:00
lash
a48170321c
Finish refactor result models 2024-10-31 01:51:36 +00:00
lash
1e638238ed
WIP refactor models usage 2024-10-31 01:28:37 +00:00
lash
4e81e2d869
Adapt voucher changes to package exports 2024-10-30 21:09:45 +00:00
d434194021
catch incorrect pin entry 2024-10-30 23:21:15 +03:00
c6ca3f6be4
fix sink error 2024-10-30 22:32:38 +03:00
8093eae61a
use 0 instead of 1 for back 2024-10-30 21:26:52 +03:00
833d52a558
remove print message 2024-10-30 21:26:11 +03:00
8262e14198
Merge branch 'master' into pin-reset 2024-10-30 21:10:12 +03:00
ea4c6d9314
check for phone number validity 2024-10-30 18:01:43 +03:00
7c823e07ca
move to root node after on back 2024-10-30 18:01:20 +03:00
41585f831c
move catch and load to next node 2024-10-30 18:00:38 +03:00
d93a26f9b0
remove function exec after HALT 2024-10-30 17:58:52 +03:00
lash
ff26ccc545
Expose api interface 2024-10-30 13:09:15 +00:00
lash
72c688b885 Merge branch 'master' into lash/export-to-term 2024-10-30 12:02:37 +00:00
lash
14648fec6c
Edit comment 2024-10-30 12:02:16 +00:00
cf523e30f8
update handler in test 2024-10-30 14:50:31 +03:00
888d3befe9
add actual pin reset functionality 2024-10-30 14:50:12 +03:00
017691a40c
define structure for admin numbers 2024-10-30 14:41:14 +03:00
dc418771a7
attach an admin store for phone numbers 2024-10-30 14:33:48 +03:00
c2068db050
update handler functions 2024-10-30 14:33:22 +03:00
c42b1cd66b
provide required handler functions and admin store 2024-10-30 14:32:01 +03:00
b404ae95fb
setup an admin store based on fs 2024-10-30 14:30:38 +03:00
lash
c95b97cb14
Export user data store 2024-10-30 01:45:38 +00:00
lash
dd764a2e24
Export db datatypes,tools 2024-10-30 01:28:55 +00:00
0a97f610a4
catch unregistred number 2024-10-29 22:23:22 +03:00
5a0563df94
group regex,check for valid number against the regex 2024-10-29 22:17:43 +03:00
7597b96dae
remove catch for unregistered number 2024-10-29 22:16:34 +03:00
f37483e2f0
use _ for back navigation 2024-10-29 22:15:31 +03:00
d0ad6395b5
add check for unregistered phone numbers 2024-10-29 17:35:42 +03:00
106983a394
use explicit back to node 2024-10-29 17:35:01 +03:00
91b85af11a
add reset unregistered number 2024-10-29 17:34:34 +03:00
534d756318
catch unregistred phone numbers 2024-10-29 17:18:39 +03:00
6998c30dd1
add node to handle unregistered phone numbers 2024-10-29 17:18:01 +03:00
449f90c95b
add flag to catch unregistred numbers 2024-10-29 17:17:03 +03:00
e96c874300
repeat same node on invalid option input 2024-10-29 15:02:40 +03:00
b35460d3c1
Merge branch 'master' into pin-reset 2024-10-29 14:35:17 +03:00
124049c924
add admin number defination in env 2024-10-29 14:32:17 +03:00
5fd3eb3c29
set admin privilege flag 2024-10-29 14:28:58 +03:00
d83962c0ba
load admin numbers defined in the .env 2024-10-29 14:26:24 +03:00
41da099933
remove the admin flag,setup an admin store 2024-10-29 13:24:13 +03:00
c9bb93ede6
create a simple admin store for phone numbers 2024-10-29 13:15:41 +03:00
ca13d9155c
replace _ with explicit back node 2024-10-29 13:15:22 +03:00
e338ce0025
load and reload only after input 2024-10-29 13:14:49 +03:00
b97965193b
add pin reset for others handling 2024-10-28 16:37:23 +03:00
aec0abb2b6
setup an admin flag 2024-10-28 16:34:33 +03:00
26073c8000
define handler functions required to reset others pin 2024-10-28 15:49:20 +03:00
e4c2f644f3
define a key to hold number during pin reset 2024-10-28 15:47:56 +03:00
3de46cef5e
setup pin reset nodes 2024-10-28 15:45:08 +03:00
0cc0bdf9f7
add pin reset for others nodes 2024-10-28 15:19:35 +03:00
72d5c186dd
add admin privilege flag 2024-10-28 15:18:40 +03:00
fc0043e3f6
Adde the transactions node 2024-10-23 14:52:15 +03:00
d41ba79ae4
Adde the check_statement node 2024-10-23 14:51:59 +03:00
f36847d966
Added a placeholder function to get transactions 2024-10-23 14:51:17 +03:00
lash
967e53d83b Merge branch 'master' into lash/ssh-4 2024-10-14 14:50:12 +01:00
lash
d246cdee51
Rename datatype const name for ssh prefix 2024-09-27 21:25:21 +01:00
lash
d518a76536 Merge branch 'lash/subprefix' into lash/ssh-4 2024-09-27 21:18:25 +01:00
lash
6f65c33be4
Re-add ssh 2024-09-26 15:15:06 +01:00
291 changed files with 1007 additions and 7814 deletions

View File

@ -2,17 +2,19 @@
PORT=7123
HOST=127.0.0.1
#AfricasTalking USSD POST endpoint
AT_ENDPOINT=/ussd/africastalking
#PostgreSQL
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=strongpass
DB_NAME=urdt_ussd
DB_PORT=5432
DB_SSLMODE=disable
DB_TIMEZONE=Africa/Nairobi
DB_CONN=postgres://postgres:strongpass@localhost:5432/urdt_ussd
#DB_TIMEZONE=Africa/Nairobi
#DB_SCHEMA=vise
#External API Calls
CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
TRACK_URL=http://localhost:5003/api/v2/account/status
CUSTODIAL_URL_BASE=http://localhost:5003
BEARER_TOKEN=eyJeSIsInRcCI6IkpXVCJ.yJwdWJsaWNLZXkiOiIwrrrrrr
DATA_URL_BASE=http://localhost:5006
#Language
DEFAULT_LANGUAGE=eng
LANGUAGES=eng, swa

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ go.work*
cmd/.state/
id_*
*.gdbm
*.log

View File

@ -1,9 +0,0 @@
# ussd
> USSD
USSD service.
## License
[AGPL-3.0](LICENSE).

View File

@ -1,180 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"strings"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func init() {
initializers.LoadEnvVariables()
}
type atRequestParser struct{}
func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return "", handlers.ErrInvalidRequest
}
if err := rqv.ParseForm(); err != nil {
return "", fmt.Errorf("failed to parse form data: %v", err)
}
phoneNumber := rqv.FormValue("phoneNumber")
if phoneNumber == "" {
return "", fmt.Errorf("no phone number found")
}
return phoneNumber, nil
}
func (arp *atRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, handlers.ErrInvalidRequest
}
if err := rqv.ParseForm(); err != nil {
return nil, fmt.Errorf("failed to parse form data: %v", err)
}
text := rqv.FormValue("text")
parts := strings.Split(text, "*")
if len(parts) == 0 {
return nil, fmt.Errorf("no input found")
}
return []byte(parts[len(parts)-1]), nil
}
func main() {
config.LoadConfig()
var dbDir string
var resourceDir string
var size uint
var database string
var engineDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database)
pfp := path.Join(scriptDir, "pp.csv")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.GetStateStore(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &atRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.NewATSessionHandler(bsh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}

View File

@ -1,174 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"path"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func init() {
initializers.LoadEnvVariables()
}
type asyncRequestParser struct {
sessionId string
input []byte
}
func (p *asyncRequestParser) GetSessionId(r any) (string, error) {
return p.sessionId, nil
}
func (p *asyncRequestParser) GetInput(r any) ([]byte, error) {
return p.input, nil
}
func main() {
config.LoadConfig()
var sessionId string
var dbDir string
var resourceDir string
var size uint
var database string
var engineDebug bool
var host string
var port uint
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId)
ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database)
pfp := path.Join(scriptDir, "pp.csv")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore)
accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.GetStateStore(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &asyncRequestParser{
sessionId: sessionId,
}
sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
cfg.SessionId = sessionId
rqs := handlers.RequestSession{
Ctx: ctx,
Writer: os.Stdout,
Config: cfg,
}
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
sh.Shutdown()
}()
for true {
rqs, err = sh.Process(rqs)
if err != nil {
logg.ErrorCtxf(ctx, "error in process: %v", "err", err)
fmt.Errorf("error in process: %v", err)
os.Exit(1)
}
rqs, err = sh.Output(rqs)
if err != nil {
logg.ErrorCtxf(ctx, "error in output: %v", "err", err)
fmt.Errorf("error in output: %v", err)
os.Exit(1)
}
rqs, err = sh.Reset(rqs)
if err != nil {
logg.ErrorCtxf(ctx, "error in reset: %v", "err", err)
fmt.Errorf("error in reset: %v", err)
os.Exit(1)
}
fmt.Println("")
_, err = fmt.Scanln(&rqs.Input)
if err != nil {
logg.ErrorCtxf(ctx, "error in input", "err", err)
fmt.Errorf("error in input: %v", err)
os.Exit(1)
}
}
}

View File

@ -1,140 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func init() {
initializers.LoadEnvVariables()
}
func main() {
config.LoadConfig()
var dbDir string
var resourceDir string
var size uint
var database string
var engineDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.StringVar(&database, "db", "gdbm", "database to be used")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", initializers.GetEnv("HOST", "127.0.0.1"), "http host")
flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
ctx = context.WithValue(ctx, "Database", database)
pfp := path.Join(scriptDir, "pp.csv")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(128),
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.GetStateStore(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.ToSessionHandler(bsh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}

View File

@ -1,118 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"path"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func init() {
initializers.LoadEnvVariables()
}
func main() {
config.LoadConfig()
var dbDir string
var size uint
var sessionId string
var database string
var engineDebug bool
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&database, "db", "gdbm", "database to be used")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "outputsize", size)
ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId)
ctx = context.WithValue(ctx, "Database", database)
pfp := path.Join(scriptDir, "pp.csv")
cfg := engine.Config{
Root: "root",
SessionId: sessionId,
OutputSize: uint32(size),
FlagCount: uint32(128),
}
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
err := menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
pe, err := menuStorageService.GetPersister(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdatastore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdatastore)
lhs.SetPersister(pe)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
accountService := server.AccountService{}
hl, err := lhs.GetHandler(&accountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
en := lhs.GetEngine()
en = en.WithFirst(hl.Init)
if engineDebug {
en = en.WithDebug(nil)
}
err = engine.Loop(ctx, en, os.Stdin, os.Stdout, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
os.Exit(1)
}
}

View File

@ -1,18 +0,0 @@
package common
import (
"encoding/hex"
)
func NormalizeHex(s string) (string, error) {
if len(s) >= 2 {
if s[:2] == "0x" {
s = s[2:]
}
}
r, err := hex.DecodeString(s)
if err != nil {
return "", err
}
return hex.EncodeToString(r), nil
}

View File

@ -1,18 +1,59 @@
package config
import "git.grassecon.net/urdt/ussd/initializers"
import (
"strings"
var (
CreateAccountURL string
TrackStatusURL string
BalanceURL string
TrackURL string
"git.grassecon.net/grassrootseconomics/visedriver/env"
)
// LoadConfig initializes the configuration values after environment variables are loaded.
func LoadConfig() {
CreateAccountURL = initializers.GetEnv("CREATE_ACCOUNT_URL", "http://localhost:5003/api/v2/account/create")
TrackStatusURL = initializers.GetEnv("TRACK_STATUS_URL", "https://custodial.sarafu.africa/api/track/")
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/")
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status")
var (
defaultLanguage = "eng"
languages []string
)
var (
DbConn string
DefaultLanguage string
Languages []string
)
func setLanguage() error {
defaultLanguage = env.GetEnv("DEFAULT_LANGUAGE", defaultLanguage)
languages = strings.Split(env.GetEnv("LANGUAGES", defaultLanguage), ",")
haveDefaultLanguage := false
for i, v := range(languages) {
languages[i] = strings.ReplaceAll(v, " ", "")
if languages[i] == defaultLanguage {
haveDefaultLanguage = true
}
}
if !haveDefaultLanguage {
languages = append([]string{defaultLanguage}, languages...)
}
return nil
}
func setConn() error {
DbConn = env.GetEnv("DB_CONN", "")
return nil
}
// LoadConfig initializes the configuration values after environment variables are loaded.
func LoadConfig() error {
err := setConn()
if err != nil {
return err
}
err = setLanguage()
if err != nil {
return err
}
DefaultLanguage = defaultLanguage
Languages = languages
return nil
}

28
doc/data.md Normal file
View File

@ -0,0 +1,28 @@
# Internals
## Version
This document describes component versions:
* `urdt-ussd` `v0.5.0-beta`
* `go-vise` `v0.2.2`
## User profile data
All user profile items are stored under keys matching the user's session id, prefixed with the 8-bit value `git.defalsify.org/vise.git/db.DATATYPE_USERDATA` (32), and followed with a 16-big big-endian value subprefix.
For example, given the sessionId `+254123` and the key `git.grassecon.net/urdt-ussd/common.DATA_PUBLIC_KEY` (2) will be stored under the key:
```
0x322b3235343132330002
prefix sessionid subprefix
32 2b323534313233 0002
```
### Sub-prefixes
All sub-prefixes are defined as constants in the `git.grassecon.net/urdt-ussd/common` package. The constant names have the prefix `DATA_`
Please refer to inline godoc documentation for the `git.grassecon.net/urdt-ussd/common` package for details on each data item.

13
entry/handlers.go Normal file
View File

@ -0,0 +1,13 @@
package entry
import (
"context"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
)
type EntryHandler interface {
Init(context.Context, string, []byte) (resource.Result, error) // HandlerFunc
Exit()
SetPersister(*persist.Persister)
}

View File

@ -1,26 +1,32 @@
package initializers
package env
import (
"log"
"os"
"path"
"strconv"
"github.com/joho/godotenv"
)
func LoadEnvVariables() {
err := godotenv.Load()
LoadEnvVariablesPath(".")
}
func LoadEnvVariablesPath(dir string) {
fp := path.Join(dir, ".env")
err := godotenv.Load(fp)
if err != nil {
log.Fatal("Error loading .env file")
log.Fatal("Error loading .env file", err)
}
}
// Helper to get environment variables with a default fallback
func GetEnv(key, defaultVal string) string {
if value, exists := os.LookupEnv(key); exists {
return value
if value, exists := os.LookupEnv(key); exists {
return value
}
return defaultVal
return defaultVal
}
// Helper to safely convert environment variables to uint

15
errors/errors.go Normal file
View File

@ -0,0 +1,15 @@
package errors
import (
"errors"
)
var (
ErrInvalidRequest = errors.New("invalid request for context")
ErrSessionMissing = errors.New("missing session")
ErrInvalidInput = errors.New("invalid input")
ErrStorage = errors.New("storage retrieval fail")
ErrEngineType = errors.New("incompatible engine")
ErrEngineInit = errors.New("engine init fail")
ErrEngineExec = errors.New("engine exec fail")
)

40
go.mod
View File

@ -1,45 +1,27 @@
module git.grassecon.net/urdt/ussd
module git.grassecon.net/grassrootseconomics/visedriver
go 1.23.0
toolchain go1.23.2
require (
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
github.com/alecthomas/assert/v2 v2.2.2
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
github.com/peteole/testdata-loader v0.3.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b
github.com/jackc/pgx/v5 v5.7.1
github.com/joho/godotenv v1.5.1
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
)
require (
github.com/alecthomas/participle/v2 v2.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.9.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 // indirect
)

29
go.sum
View File

@ -1,29 +1,14 @@
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b h1:dxBplsIlzJHV+5EH+gzB+w08Blt7IJbb2jeRe1OEjLU=
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
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/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b h1:rwWXMtNSn7aqhb4p1oVZkCA1vC7pVdohwW61QQM8fUs=
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/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/ussd-data-service v0.0.0-20241003123429-4904b4438a3a h1:q/YH7nE2j8epNmFnTu0tU1vwtCxtQ6nH+d7hRVV5krU=
github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a/go.mod h1:hdKaKwqiW6/kphK4j/BhmuRlZDLo1+DYo3gYw5O0siw=
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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@ -34,10 +19,6 @@ 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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
@ -47,11 +28,7 @@ github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljM
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/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/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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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=
@ -65,8 +42,6 @@ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,110 +0,0 @@
package handlers
import (
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
)
type HandlerService interface {
GetHandler() (*ussd.Handlers, 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 {
Parser *asm.FlagParser
DbRs *resource.DbResource
Pe *persist.Persister
UserdataStore *db.Db
Cfg engine.Config
Rs resource.Resource
}
func NewLocalHandlerService(fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
parser, err := getParser(fp, debug)
if err != nil {
return nil, err
}
return &LocalHandlerService{
Parser: parser,
DbRs: dbResource,
Cfg: cfg,
Rs: rs,
}, nil
}
func (ls *LocalHandlerService) SetPersister(Pe *persist.Persister) {
ls.Pe = Pe
}
func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
ls.UserdataStore = db
}
func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore,accountService)
if err != nil {
return nil, err
}
ussdHandlers = ussdHandlers.WithPersister(ls.Pe)
ls.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
ls.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
ls.DbRs.AddLocalFunc("verify_create_pin", ussdHandlers.VerifyCreatePin)
ls.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
ls.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
ls.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
ls.DbRs.AddLocalFunc("quit", ussdHandlers.Quit)
ls.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
ls.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
ls.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
ls.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
ls.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
ls.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
ls.DbRs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
ls.DbRs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
ls.DbRs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
ls.DbRs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
ls.DbRs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
ls.DbRs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
ls.DbRs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
ls.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
ls.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
ls.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
ls.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
ls.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
ls.DbRs.AddLocalFunc("fetch_custodial_balances", ussdHandlers.FetchCustodialBalances)
ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher)
ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers)
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
return ussdHandlers, nil
}
// TODO: enable setting of sessionId on engine init time
func (ls *LocalHandlerService) GetEngine() *engine.DefaultEngine {
en := engine.NewEngine(ls.Cfg, ls.Rs)
en = en.WithPersister(ls.Pe)
return en
}

View File

@ -1,185 +0,0 @@
package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/internal/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
)
var (
okResponse api.OKResponse
errResponse api.ErrResponse
)
type AccountServiceInterface interface {
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error)
CreateAccount(ctx context.Context) (*api.OKResponse, error)
CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error)
}
type AccountService struct {
}
// Parameters:
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
// AccountResponse struct can be used here to check the account status during a transaction.
//
// Returns:
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil
func (as *AccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
resp, err := http.Get(config.BalanceURL + trackingId)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var trackResp models.TrackStatusResponse
err = json.Unmarshal(body, &trackResp)
if err != nil {
return nil, err
}
return &trackResp, nil
}
func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
var err error
// Construct the URL with the path parameter
url := fmt.Sprintf("%s/%s", config.TrackURL, publicKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-GE-KEY", "xd")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse)
if err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
}
err = json.Unmarshal([]byte(body), &okResponse)
if err != nil {
return nil, err
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
}
return &okResponse, nil
}
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var balanceResp models.BalanceResponse
err = json.Unmarshal(body, &balanceResp)
if err != nil {
return nil, err
}
return &balanceResp, nil
}
// CreateAccount creates a new account in the custodial system.
// Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
// If there is an error during the request or processing, this will be nil.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil.
func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
var err error
// Create a new request
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-GE-KEY", "xd")
resp, err := http.DefaultClient.Do(req)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse)
if err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
}
err = json.Unmarshal([]byte(body), &okResponse)
if err != nil {
return nil, err
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
}
return &okResponse, nil
}
// FetchVouchers retrieves the token holdings for a given public key from the custodial holdings API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
file, err := os.Open("sample_tokens.json")
if err != nil {
return nil, err
}
defer file.Close()
var holdings models.VoucherHoldingResponse
if err := json.NewDecoder(file).Decode(&holdings); err != nil {
return nil, err
}
return &holdings, nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +0,0 @@
package http
import (
"io"
"net/http"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
type ATSessionHandler struct {
*SessionHandler
}
func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler {
return &ATSessionHandler{
SessionHandler: ToSessionHandler(h),
}
}
func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := ash.GetRequestParser()
cfg := ash.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.writeError(w, 400, err)
return
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.writeError(w, 400, err)
return
}
rqs, err = ash.Process(rqs)
switch err {
case nil: // set code to 200 if no err
code = 200
case handlers.ErrStorage, handlers.ErrEngineInit, handlers.ErrEngineExec, handlers.ErrEngineType:
code = 500
default:
code = 500
}
if code != 200 {
ash.writeError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = ash.Output(rqs)
if err != nil {
ash.writeError(w, 500, err)
return
}
rqs, err = ash.Reset(rqs)
if err != nil {
ash.writeError(w, 500, err)
return
}
}
func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.RequestSession, error) {
var err error
var prefix string
if rqs.Continue {
prefix = "CON "
} else {
prefix = "END "
}
_, err = io.WriteString(rqs.Writer, prefix)
if err != nil {
return rqs, err
}
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err
}

View File

@ -1,449 +0,0 @@
package http
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"git.defalsify.org/vise.git/engine"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/testutil/mocks/httpmocks"
)
// invalidRequestType is a custom type to test invalid request scenarios
type invalidRequestType struct{}
// errorReader is a helper type that always returns an error when Read is called
type errorReader struct{}
func (e *errorReader) Read(p []byte) (n int, err error) {
return 0, errors.New("read error")
}
func TestNewATSessionHandler(t *testing.T) {
mockHandler := &httpmocks.MockRequestHandler{}
ash := NewATSessionHandler(mockHandler)
if ash == nil {
t.Fatal("NewATSessionHandler returned nil")
}
if ash.SessionHandler == nil {
t.Fatal("SessionHandler is nil")
}
}
func TestATSessionHandler_ServeHTTP(t *testing.T) {
tests := []struct {
name string
setupMocks func(*httpmocks.MockRequestHandler, *httpmocks.MockRequestParser, *httpmocks.MockEngine)
formData url.Values
expectedStatus int
expectedBody string
}{
{
name: "Successful request",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
req := rq.(*http.Request)
text := req.FormValue("text")
parts := strings.Split(text, "*")
return []byte(parts[len(parts)-1]), nil
}
mh.ProcessFunc = func(rqs handlers.RequestSession) (handlers.RequestSession, error) {
rqs.Continue = true
rqs.Engine = me
return rqs, nil
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
mh.OutputFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
mh.ResetFunc = func(rs handlers.RequestSession) (handlers.RequestSession, error) { return rs, nil }
me.FlushFunc = func(context.Context, io.Writer) (int, error) { return 0, nil }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusOK,
expectedBody: "CON ",
},
{
name: "GetSessionId error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
return "", errors.New("no phone number found")
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusBadRequest,
expectedBody: "",
},
{
name: "GetInput error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
return nil, errors.New("no input found")
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
},
expectedStatus: http.StatusBadRequest,
expectedBody: "",
},
{
name: "Process error",
setupMocks: func(mh *httpmocks.MockRequestHandler, mrp *httpmocks.MockRequestParser, me *httpmocks.MockEngine) {
mrp.GetSessionIdFunc = func(rq any) (string, error) {
req := rq.(*http.Request)
return req.FormValue("phoneNumber"), nil
}
mrp.GetInputFunc = func(rq any) ([]byte, error) {
req := rq.(*http.Request)
text := req.FormValue("text")
parts := strings.Split(text, "*")
return []byte(parts[len(parts)-1]), nil
}
mh.ProcessFunc = func(rqs handlers.RequestSession) (handlers.RequestSession, error) {
return rqs, handlers.ErrStorage
}
mh.GetConfigFunc = func() engine.Config { return engine.Config{} }
mh.GetRequestParserFunc = func() handlers.RequestParser { return mrp }
},
formData: url.Values{
"phoneNumber": []string{"+1234567890"},
"text": []string{"1*2*3"},
},
expectedStatus: http.StatusInternalServerError,
expectedBody: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockHandler := &httpmocks.MockRequestHandler{}
mockRequestParser := &httpmocks.MockRequestParser{}
mockEngine := &httpmocks.MockEngine{}
tt.setupMocks(mockHandler, mockRequestParser, mockEngine)
ash := NewATSessionHandler(mockHandler)
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.formData.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
ash.ServeHTTP(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
}
if tt.expectedBody != "" && w.Body.String() != tt.expectedBody {
t.Errorf("Expected body %q, got %q", tt.expectedBody, w.Body.String())
}
})
}
}
func TestATSessionHandler_Output(t *testing.T) {
tests := []struct {
name string
input handlers.RequestSession
expectedPrefix string
expectedError bool
}{
{
name: "Continue true",
input: handlers.RequestSession{
Continue: true,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "CON ",
expectedError: false,
},
{
name: "Continue false",
input: handlers.RequestSession{
Continue: false,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, nil
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "END ",
expectedError: false,
},
{
name: "Flush error",
input: handlers.RequestSession{
Continue: true,
Engine: &httpmocks.MockEngine{
FlushFunc: func(context.Context, io.Writer) (int, error) {
return 0, errors.New("write error")
},
},
Writer: &httpmocks.MockWriter{},
},
expectedPrefix: "CON ",
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ash := &ATSessionHandler{}
_, err := ash.Output(tt.input)
if tt.expectedError && err == nil {
t.Error("Expected an error, but got nil")
}
if !tt.expectedError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
mw := tt.input.Writer.(*httpmocks.MockWriter)
if !mw.WriteStringCalled {
t.Error("WriteString was not called")
}
if mw.WrittenString != tt.expectedPrefix {
t.Errorf("Expected prefix %q, got %q", tt.expectedPrefix, mw.WrittenString)
}
})
}
}
func TestSessionHandler_ServeHTTP(t *testing.T) {
tests := []struct {
name string
sessionID string
input []byte
parserErr error
processErr error
outputErr error
resetErr error
expectedStatus int
}{
{
name: "Success",
sessionID: "123",
input: []byte("test input"),
expectedStatus: http.StatusOK,
},
{
name: "Missing Session ID",
sessionID: "",
parserErr: handlers.ErrSessionMissing,
expectedStatus: http.StatusBadRequest,
},
{
name: "Process Error",
sessionID: "123",
input: []byte("test input"),
processErr: handlers.ErrStorage,
expectedStatus: http.StatusInternalServerError,
},
{
name: "Output Error",
sessionID: "123",
input: []byte("test input"),
outputErr: errors.New("output error"),
expectedStatus: http.StatusOK,
},
{
name: "Reset Error",
sessionID: "123",
input: []byte("test input"),
resetErr: errors.New("reset error"),
expectedStatus: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRequestParser := &httpmocks.MockRequestParser{
GetSessionIdFunc: func(any) (string, error) {
return tt.sessionID, tt.parserErr
},
GetInputFunc: func(any) ([]byte, error) {
return tt.input, nil
},
}
mockRequestHandler := &httpmocks.MockRequestHandler{
ProcessFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) {
return rs, tt.processErr
},
OutputFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) {
return rs, tt.outputErr
},
ResetFunc: func(rs handlers.RequestSession) (handlers.RequestSession, error) {
return rs, tt.resetErr
},
GetRequestParserFunc: func() handlers.RequestParser {
return mockRequestParser
},
GetConfigFunc: func() engine.Config {
return engine.Config{}
},
}
sessionHandler := ToSessionHandler(mockRequestHandler)
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input))
req.Header.Set("X-Vise-Session", tt.sessionID)
rr := httptest.NewRecorder()
sessionHandler.ServeHTTP(rr, req)
if status := rr.Code; status != tt.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tt.expectedStatus)
}
})
}
}
func TestSessionHandler_writeError(t *testing.T) {
handler := &SessionHandler{}
mockWriter := &httpmocks.MockWriter{}
err := errors.New("test error")
handler.writeError(mockWriter, http.StatusBadRequest, err)
if mockWriter.WrittenString != "" {
t.Errorf("Expected empty body, got %s", mockWriter.WrittenString)
}
}
func TestDefaultRequestParser_GetSessionId(t *testing.T) {
tests := []struct {
name string
request any
expectedID string
expectedError error
}{
{
name: "Valid Session ID",
request: func() *http.Request {
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("X-Vise-Session", "123456")
return req
}(),
expectedID: "123456",
expectedError: nil,
},
{
name: "Missing Session ID",
request: httptest.NewRequest(http.MethodPost, "/", nil),
expectedID: "",
expectedError: handlers.ErrSessionMissing,
},
{
name: "Invalid Request Type",
request: invalidRequestType{},
expectedID: "",
expectedError: handlers.ErrInvalidRequest,
},
}
parser := &DefaultRequestParser{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
id, err := parser.GetSessionId(tt.request)
if id != tt.expectedID {
t.Errorf("Expected session ID %s, got %s", tt.expectedID, id)
}
if err != tt.expectedError {
t.Errorf("Expected error %v, got %v", tt.expectedError, err)
}
})
}
}
func TestDefaultRequestParser_GetInput(t *testing.T) {
tests := []struct {
name string
request any
expectedInput []byte
expectedError error
}{
{
name: "Valid Input",
request: func() *http.Request {
return httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("test input"))
}(),
expectedInput: []byte("test input"),
expectedError: nil,
},
{
name: "Empty Input",
request: httptest.NewRequest(http.MethodPost, "/", nil),
expectedInput: []byte{},
expectedError: nil,
},
{
name: "Invalid Request Type",
request: invalidRequestType{},
expectedInput: nil,
expectedError: handlers.ErrInvalidRequest,
},
{
name: "Read Error",
request: func() *http.Request {
return httptest.NewRequest(http.MethodPost, "/", &errorReader{})
}(),
expectedInput: nil,
expectedError: errors.New("read error"),
},
}
parser := &DefaultRequestParser{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input, err := parser.GetInput(tt.request)
if !bytes.Equal(input, tt.expectedInput) {
t.Errorf("Expected input %s, got %s", tt.expectedInput, input)
}
if err != tt.expectedError && (err == nil || err.Error() != tt.expectedError.Error()) {
t.Errorf("Expected error %v, got %v", tt.expectedError, err)
}
})
}
}

View File

@ -1,122 +0,0 @@
package http
import (
"io/ioutil"
"net/http"
"strconv"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
var (
logg = logging.NewVanilla().WithDomain("httpserver")
)
type DefaultRequestParser struct {
}
func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return "", handlers.ErrInvalidRequest
}
v := rqv.Header.Get("X-Vise-Session")
if v == "" {
return "", handlers.ErrSessionMissing
}
return v, nil
}
func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, handlers.ErrInvalidRequest
}
defer rqv.Body.Close()
v, err := ioutil.ReadAll(rqv.Body)
if err != nil {
return nil, err
}
return v, nil
}
type SessionHandler struct {
handlers.RequestHandler
}
func ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
return &SessionHandler{
RequestHandler: h,
}
}
func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) {
s := err.Error()
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
w.WriteHeader(code)
_, err = w.Write([]byte{})
if err != nil {
logg.Errorf("error writing error!!", "err", err, "olderr", s)
w.WriteHeader(500)
}
return
}
func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
var perr error
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := f.GetRequestParser()
cfg := f.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
f.writeError(w, 400, err)
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
f.writeError(w, 400, err)
return
}
rqs, err = f.Process(rqs)
switch err {
case handlers.ErrStorage:
code = 500
case handlers.ErrEngineInit:
code = 500
case handlers.ErrEngineExec:
code = 500
default:
code = 200
}
if code != 200 {
f.writeError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = f.Output(rqs)
rqs, perr = f.Reset(rqs)
if err != nil {
f.writeError(w, 500, err)
return
}
if perr != nil {
f.writeError(w, 500, perr)
return
}
}

View File

@ -1,10 +0,0 @@
package models
type AccountResponse struct {
Ok bool `json:"ok"`
Description string `json:"description"` // Include the description field
Result struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
} `json:"result"`
}

View File

@ -1,12 +0,0 @@
package models
import "encoding/json"
type BalanceResponse struct {
Ok bool `json:"ok"`
Result struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
} `json:"result"`
}

View File

@ -1,18 +0,0 @@
package models
type ApiResponse struct {
OK bool `json:"ok"`
Description string `json:"description"`
Result Result `json:"result"`
}
type Result struct {
Holdings []Holding `json:"holdings"`
}
type Holding struct {
ContractAddress string `json:"contractAddress"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
Balance string `json:"balance"`
}

View File

@ -1,26 +0,0 @@
package models
import (
"encoding/json"
"time"
)
type Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
type TrackStatusResponse struct {
Ok bool `json:"ok"`
Result struct {
Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
} `json:"result"`
}

View File

@ -1,14 +0,0 @@
package models
import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
type VoucherHoldingResponse struct {
Ok bool `json:"ok"`
Description string `json:"description"`
Result VoucherResult `json:"result"`
}
// VoucherResult holds the list of token holdings
type VoucherResult struct {
Holdings []dataserviceapi.TokenHoldings `json:"holdings"`
}

View File

@ -1,47 +0,0 @@
package storage
import (
"context"
"git.defalsify.org/vise.git/db"
)
const (
DATATYPE_USERSUB = 64
)
// PrefixDb interface abstracts the database operations.
type PrefixDb interface {
Get(ctx context.Context, key []byte) ([]byte, error)
Put(ctx context.Context, key []byte, val []byte) error
}
var _ PrefixDb = (*SubPrefixDb)(nil)
type SubPrefixDb struct {
store db.Db
pfx []byte
}
func NewSubPrefixDb(store db.Db, pfx []byte) *SubPrefixDb {
return &SubPrefixDb{
store: store,
pfx: pfx,
}
}
func (s *SubPrefixDb) toKey(k []byte) []byte {
return append(s.pfx, k...)
}
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key)
return s.store.Get(ctx, key)
}
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key)
return s.store.Put(ctx, key, val)
}

View File

@ -1,54 +0,0 @@
package storage
import (
"bytes"
"context"
"testing"
memdb "git.defalsify.org/vise.git/db/mem"
)
func TestSubPrefix(t *testing.T) {
ctx := context.Background()
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
if err != nil {
t.Fatal(err)
}
sdba := NewSubPrefixDb(db, []byte("tinkywinky"))
err = sdba.Put(ctx, []byte("foo"), []byte("dipsy"))
if err != nil {
t.Fatal(err)
}
r, err := sdba.Get(ctx, []byte("foo"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(r, []byte("dipsy")) {
t.Fatalf("expected 'dipsy', got %s", r)
}
sdbb := NewSubPrefixDb(db, []byte("lala"))
r, err = sdbb.Get(ctx, []byte("foo"))
if err == nil {
t.Fatal("expected not found")
}
err = sdbb.Put(ctx, []byte("foo"), []byte("pu"))
if err != nil {
t.Fatal(err)
}
r, err = sdbb.Get(ctx, []byte("foo"))
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(r, []byte("pu")) {
t.Fatalf("expected 'pu', got %s", r)
}
r, err = sdba.Get(ctx, []byte("foo"))
if !bytes.Equal(r, []byte("dipsy")) {
t.Fatalf("expected 'dipsy', got %s", r)
}
}

View File

@ -1,152 +0,0 @@
package storage
import (
"context"
"fmt"
"os"
"path"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
"git.defalsify.org/vise.git/db/postgres"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/initializers"
)
var (
logg = logging.NewVanilla().WithDomain("storage")
)
type StorageService interface {
GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) db.Db
GetResource(ctx context.Context) (resource.Resource, error)
EnsureDbDir() error
}
type MenuStorageService struct {
dbDir string
resourceDir string
resourceStore db.Db
stateStore db.Db
userDataStore db.Db
}
func buildConnStr() string {
host := initializers.GetEnv("DB_HOST", "localhost")
user := initializers.GetEnv("DB_USER", "postgres")
password := initializers.GetEnv("DB_PASSWORD", "")
dbName := initializers.GetEnv("DB_NAME", "")
port := initializers.GetEnv("DB_PORT", "5432")
return fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s",
user, password, host, port, dbName,
)
}
func NewMenuStorageService(dbDir string, resourceDir string) *MenuStorageService {
return &MenuStorageService{
dbDir: dbDir,
resourceDir: resourceDir,
}
}
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, fileName string) (db.Db, error) {
database, ok := ctx.Value("Database").(string)
if !ok {
return nil, fmt.Errorf("failed to select the database")
}
if existingDb != nil {
return existingDb, nil
}
var newDb db.Db
var err error
if database == "postgres" {
newDb = postgres.NewPgDb()
connStr := buildConnStr()
err = newDb.Connect(ctx, connStr)
} else {
newDb = NewThreadGdbmDb()
storeFile := path.Join(ms.dbDir, fileName)
err = newDb.Connect(ctx, storeFile)
}
if err != nil {
return nil, err
}
return newDb, nil
}
func (ms *MenuStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
stateStore, err := ms.GetStateStore(ctx)
if err != nil {
return nil, err
}
pr := persist.NewPersister(stateStore)
logg.TraceCtxf(ctx, "menu storage service", "persist", pr, "store", stateStore)
return pr, nil
}
func (ms *MenuStorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
if ms.userDataStore != nil {
return ms.userDataStore, nil
}
userDataStore, err := ms.getOrCreateDb(ctx, ms.userDataStore, "userdata.gdbm")
if err != nil {
return nil, err
}
ms.userDataStore = userDataStore
return ms.userDataStore, nil
}
func (ms *MenuStorageService) GetResource(ctx context.Context) (resource.Resource, error) {
ms.resourceStore = fsdb.NewFsDb()
err := ms.resourceStore.Connect(ctx, ms.resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(ms.resourceStore)
return rfs, nil
}
func (ms *MenuStorageService) GetStateStore(ctx context.Context) (db.Db, error) {
if ms.stateStore != nil {
return ms.stateStore, nil
}
stateStore, err := ms.getOrCreateDb(ctx, ms.stateStore, "state.gdbm")
if err != nil {
return nil, err
}
ms.stateStore = stateStore
return ms.stateStore, nil
}
func (ms *MenuStorageService) EnsureDbDir() error {
err := os.MkdirAll(ms.dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func (ms *MenuStorageService) Close() error {
errA := ms.stateStore.Close()
errB := ms.userDataStore.Close()
errC := ms.resourceStore.Close()
if errA != nil || errB != nil || errC != nil {
return fmt.Errorf("%v %v %v", errA, errB, errC)
}
return nil
}

View File

@ -1,124 +0,0 @@
package testutil
import (
"context"
"fmt"
"os"
"path"
"time"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/internal/testutil/testtag"
testdataloader "github.com/peteole/testdata-loader"
)
var (
baseDir = testdataloader.GetBasePath()
logg = logging.NewVanilla()
scriptDir = path.Join(baseDir, "services", "registration")
)
func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId)
ctx = context.WithValue(ctx, "Database", "gdbm")
pfp := path.Join(scriptDir, "pp.csv")
var eventChannel = make(chan bool)
cfg := engine.Config{
Root: "root",
SessionId: sessionId,
OutputSize: uint32(160),
FlagCount: uint32(128),
}
dbDir := ".test_state"
resourceDir := scriptDir
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
err := menuStorageService.EnsureDbDir()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
rs, err := menuStorageService.GetResource(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
pe, err := menuStorageService.GetPersister(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userDataStore, err := menuStorageService.GetUserdataDb(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userDataStore)
lhs.SetPersister(pe)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
if testtag.AccountService == nil {
testtag.AccountService = &server.AccountService{}
}
switch testtag.AccountService.(type) {
case *testservice.TestAccountService:
go func() {
eventChannel <- false
}()
case *server.AccountService:
go func() {
time.Sleep(5 * time.Second) // Wait for 5 seconds
eventChannel <- true
}()
default:
panic("Unknown account service type")
}
hl, err := lhs.GetHandler(testtag.AccountService)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
en := lhs.GetEngine()
en = en.WithFirst(hl.Init)
cleanFn := func() {
err := en.Finish()
if err != nil {
logg.Errorf(err.Error())
}
err = menuStorageService.Close()
if err != nil {
logg.Errorf(err.Error())
}
logg.Infof("testengine storage closed")
}
return en, cleanFn, eventChannel
}

View File

@ -1,47 +0,0 @@
package httpmocks
import (
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
// MockRequestHandler implements handlers.RequestHandler interface for testing
type MockRequestHandler struct {
ProcessFunc func(handlers.RequestSession) (handlers.RequestSession, error)
GetConfigFunc func() engine.Config
GetEngineFunc func(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
OutputFunc func(rs handlers.RequestSession) (handlers.RequestSession, error)
ResetFunc func(rs handlers.RequestSession) (handlers.RequestSession, error)
ShutdownFunc func()
GetRequestParserFunc func() handlers.RequestParser
}
func (m *MockRequestHandler) Process(rqs handlers.RequestSession) (handlers.RequestSession, error) {
return m.ProcessFunc(rqs)
}
func (m *MockRequestHandler) GetConfig() engine.Config {
return m.GetConfigFunc()
}
func (m *MockRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine {
return m.GetEngineFunc(cfg, rs, pe)
}
func (m *MockRequestHandler) Output(rs handlers.RequestSession) (handlers.RequestSession, error) {
return m.OutputFunc(rs)
}
func (m *MockRequestHandler) Reset(rs handlers.RequestSession) (handlers.RequestSession, error) {
return m.ResetFunc(rs)
}
func (m *MockRequestHandler) Shutdown() {
m.ShutdownFunc()
}
func (m *MockRequestHandler) GetRequestParser() handlers.RequestParser {
return m.GetRequestParserFunc()
}

View File

@ -1,40 +0,0 @@
package mocks
import (
"context"
"git.grassecon.net/urdt/ussd/internal/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"github.com/stretchr/testify/mock"
)
// MockAccountService implements AccountServiceInterface for testing
type MockAccountService struct {
mock.Mock
}
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
args := m.Called()
return args.Get(0).(*api.OKResponse), args.Error(1)
}
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
args := m.Called(publicKey)
return args.Get(0).(*models.BalanceResponse), args.Error(1)
}
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
args := m.Called(trackingId)
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
}
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
args := m.Called(publicKey)
return args.Get(0).(*api.OKResponse), args.Error(1)
}
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
args := m.Called(publicKey)
return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1)
}

View File

@ -1,90 +0,0 @@
package testservice
import (
"context"
"encoding/json"
"time"
"git.grassecon.net/urdt/ussd/internal/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
type TestAccountService struct {
}
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
return &api.OKResponse{
Ok: true,
Description: "Account creation succeeded",
Result: map[string]any{
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
},
}, nil
}
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
balanceResponse := &models.BalanceResponse{
Ok: true,
Result: struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
}
return balanceResponse, nil
}
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
trackResponse := &models.TrackStatusResponse{
Ok: true,
Result: struct {
Transaction struct {
CreatedAt time.Time "json:\"createdAt\""
Status string "json:\"status\""
TransferValue json.Number "json:\"transferValue\""
TxHash string "json:\"txHash\""
TxType string "json:\"txType\""
}
}{
Transaction: models.Transaction{
CreatedAt: time.Now(),
Status: "SUCCESS",
TransferValue: json.Number("0.5"),
TxHash: "0x123abc456def",
TxType: "transfer",
},
},
}
return trackResponse, nil
}
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
return &api.OKResponse{
Ok: true,
Description: "Account creation succeeded",
Result: map[string]any{
"active": true,
},
}, nil
}
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
return &models.VoucherHoldingResponse{
Ok: true,
Result: models.VoucherResult{
Holdings: []dataserviceapi.TokenHoldings{
{
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF",
TokenDecimals: "6",
Balance: "2745987",
},
},
},
}, nil
}

View File

@ -1,12 +0,0 @@
// +build !online
package testtag
import (
"git.grassecon.net/urdt/ussd/internal/handlers/server"
accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
)
var (
AccountService server.AccountServiceInterface = &accountservice.TestAccountService{}
)

View File

@ -1,9 +0,0 @@
// +build online
package testtag
import "git.grassecon.net/urdt/ussd/internal/handlers/server"
var (
AccountService server.AccountServiceInterface
)

View File

@ -1,35 +0,0 @@
package utils
import "time"
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
// It adjusts for cases where the current date is before the birthday in the current year.
func CalculateAge(birthdate, today time.Time) int {
today = today.In(birthdate.Location())
ty, tm, td := today.Date()
today = time.Date(ty, tm, td, 0, 0, 0, 0, time.UTC)
by, bm, bd := birthdate.Date()
birthdate = time.Date(by, bm, bd, 0, 0, 0, 0, time.UTC)
if today.Before(birthdate) {
return 0
}
age := ty - by
anniversary := birthdate.AddDate(age, 0, 0)
if anniversary.After(today) {
age--
}
return age
}
// CalculateAgeWithYOB calculates the age based on the given year of birth (YOB).
// It subtracts the YOB from the current year to determine the age.
//
// Parameters:
// yob: The year of birth as an integer.
//
// Returns:
// The calculated age as an integer.
func CalculateAgeWithYOB(yob int) int {
currentYear := time.Now().Year()
return currentYear - yob
}

View File

@ -1,44 +0,0 @@
package utils
import (
"encoding/binary"
)
type DataTyp uint16
const (
DATA_ACCOUNT DataTyp = iota
DATA_ACCOUNT_CREATED
DATA_TRACKING_ID
DATA_PUBLIC_KEY
DATA_CUSTODIAL_ID
DATA_ACCOUNT_PIN
DATA_ACCOUNT_STATUS
DATA_FIRST_NAME
DATA_FAMILY_NAME
DATA_YOB
DATA_LOCATION
DATA_GENDER
DATA_OFFERINGS
DATA_RECIPIENT
DATA_AMOUNT
DATA_TEMPORARY_VALUE
DATA_VOUCHER_LIST
DATA_ACTIVE_SYM
DATA_ACTIVE_BAL
DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS
)
func typToBytes(typ DataTyp) []byte {
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(typ))
return b[:]
}
func PackKey(typ DataTyp, data []byte) []byte {
v := typToBytes(typ)
return append(v, data...)
}

View File

@ -1,11 +0,0 @@
package utils
var isoCodes = map[string]bool{
"eng": true, // English
"swa": true, // Swahili
}
func IsValidISO639(code string) bool {
return isoCodes[code]
}

View File

@ -1,32 +0,0 @@
package utils
import (
"context"
"git.defalsify.org/vise.git/db"
)
type DataStore interface {
db.Db
ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error)
WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error
}
type UserDataStore struct {
db.Db
}
// ReadEntry retrieves an entry from the store based on the provided parameters.
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
return store.Get(ctx, k)
}
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
return store.Put(ctx, k, value)
}

View File

@ -1,146 +0,0 @@
package utils
import (
"context"
"fmt"
"strings"
"git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
// VoucherMetadata helps organize data fields
type VoucherMetadata struct {
Symbols string
Balances string
Decimals string
Addresses string
}
// ProcessVouchers converts holdings into formatted strings
func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
var data VoucherMetadata
var symbols, balances, decimals, addresses []string
for i, h := range holdings {
symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol))
balances = append(balances, fmt.Sprintf("%d:%s", i+1, h.Balance))
decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals))
addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress))
}
data.Symbols = strings.Join(symbols, "\n")
data.Balances = strings.Join(balances, "\n")
data.Decimals = strings.Join(decimals, "\n")
data.Addresses = strings.Join(addresses, "\n")
return data
}
// GetVoucherData retrieves and matches voucher data
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
keys := []string{"sym", "bal", "deci", "addr"}
data := make(map[string]string)
for _, key := range keys {
value, err := db.Get(ctx, []byte(key))
if err != nil {
return nil, fmt.Errorf("failed to get %s: %v", key, err)
}
data[key] = string(value)
}
symbol, balance, decimal, address := MatchVoucher(input,
data["sym"],
data["bal"],
data["deci"],
data["addr"])
if symbol == "" {
return nil, nil
}
return &dataserviceapi.TokenHoldings{
TokenSymbol: string(symbol),
Balance: string(balance),
TokenDecimals: string(decimal),
ContractAddress: string(address),
}, nil
}
// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input.
func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) {
symList := strings.Split(symbols, "\n")
balList := strings.Split(balances, "\n")
decList := strings.Split(decimals, "\n")
addrList := strings.Split(addresses, "\n")
for i, sym := range symList {
parts := strings.SplitN(sym, ":", 2)
if input == parts[0] || strings.EqualFold(input, parts[1]) {
symbol = parts[1]
if i < len(balList) {
balance = strings.SplitN(balList[i], ":", 2)[1]
}
if i < len(decList) {
decimal = strings.SplitN(decList[i], ":", 2)[1]
}
if i < len(addrList) {
address = strings.SplitN(addrList[i], ":", 2)[1]
}
break
}
}
return
}
// StoreTemporaryVoucher saves voucher metadata as temporary entries in the DataStore.
func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
tempData := fmt.Sprintf("%s,%s,%s,%s", data.TokenSymbol, data.Balance, data.TokenDecimals, data.ContractAddress)
if err := store.WriteEntry(ctx, sessionId, DATA_TEMPORARY_VALUE, []byte(tempData)); err != nil {
return err
}
return nil
}
// GetTemporaryVoucherData retrieves temporary voucher metadata from the DataStore.
func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*dataserviceapi.TokenHoldings, error) {
temp_data, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
if err != nil {
return nil, err
}
values := strings.SplitN(string(temp_data), ",", 4)
data := &dataserviceapi.TokenHoldings{}
data.TokenSymbol = values[0]
data.Balance = values[1]
data.TokenDecimals = values[2]
data.ContractAddress = values[3]
return data, nil
}
// UpdateVoucherData sets the active voucher data in the DataStore.
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
// Active voucher data entries
activeEntries := map[DataTyp][]byte{
DATA_ACTIVE_SYM: []byte(data.TokenSymbol),
DATA_ACTIVE_BAL: []byte(data.Balance),
DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals),
DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress),
}
// Write active data
for key, value := range activeEntries {
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
return err
}
}
return nil
}

View File

@ -1,198 +0,0 @@
package utils
import (
"context"
"fmt"
"testing"
"git.grassecon.net/urdt/ussd/internal/storage"
"github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require"
memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
// InitializeTestDb sets up and returns an in-memory database and store.
func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) {
ctx := context.Background()
// Initialize memDb
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
require.NoError(t, err, "Failed to connect to memDb")
// Create UserDataStore with memDb
store := &UserDataStore{Db: db}
t.Cleanup(func() {
db.Close() // Ensure the DB is closed after each test
})
return ctx, store
}
func TestMatchVoucher(t *testing.T) {
symbols := "1:SRF\n2:MILO"
balances := "1:100\n2:200"
decimals := "1:6\n2:4"
addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa"
// Test for valid voucher
symbol, balance, decimal, address := MatchVoucher("2", symbols, balances, decimals, addresses)
// Assertions for valid voucher
assert.Equal(t, "MILO", symbol)
assert.Equal(t, "200", balance)
assert.Equal(t, "4", decimal)
assert.Equal(t, "0x41c188d63Qa", address)
// Test for non-existent voucher
symbol, balance, decimal, address = MatchVoucher("3", symbols, balances, decimals, addresses)
// Assertions for non-match
assert.Equal(t, "", symbol)
assert.Equal(t, "", balance)
assert.Equal(t, "", decimal)
assert.Equal(t, "", address)
}
func TestProcessVouchers(t *testing.T) {
holdings := []dataserviceapi.TokenHoldings{
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
}
expectedResult := VoucherMetadata{
Symbols: "1:SRF\n2:MILO",
Balances: "1:100\n2:200",
Decimals: "1:6\n2:4",
Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa",
}
result := ProcessVouchers(holdings)
assert.Equal(t, expectedResult, result)
}
func TestGetVoucherData(t *testing.T) {
ctx := context.Background()
db := memdb.NewMemDb()
err := db.Connect(ctx, "")
if err != nil {
t.Fatal(err)
}
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
// Test voucher data
mockData := map[string][]byte{
"sym": []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
}
// Put the data
for key, value := range mockData {
err = spdb.Put(ctx, []byte(key), []byte(value))
if err != nil {
t.Fatal(err)
}
}
result, err := GetVoucherData(ctx, spdb, "1")
assert.NoError(t, err)
assert.Equal(t, "SRF", result.TokenSymbol)
assert.Equal(t, "100", result.Balance)
assert.Equal(t, "6", result.TokenDecimals)
assert.Equal(t, "0xd4c288865Ce", result.ContractAddress)
}
func TestStoreTemporaryVoucher(t *testing.T) {
ctx, store := InitializeTestDb(t)
sessionId := "session123"
// Test data
voucherData := &dataserviceapi.TokenHoldings{
TokenSymbol: "SRF",
Balance: "200",
TokenDecimals: "6",
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
// Execute the function being tested
err := StoreTemporaryVoucher(ctx, store, sessionId, voucherData)
require.NoError(t, err)
// Verify stored data
expectedData := fmt.Sprintf("%s,%s,%s,%s", "SRF", "200", "6", "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9")
storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
require.NoError(t, err)
require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE)
}
func TestGetTemporaryVoucherData(t *testing.T) {
ctx, store := InitializeTestDb(t)
sessionId := "session123"
// Test voucher data
tempData := &dataserviceapi.TokenHoldings{
TokenSymbol: "SRF",
Balance: "200",
TokenDecimals: "6",
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
// Store the data
err := StoreTemporaryVoucher(ctx, store, sessionId, tempData)
require.NoError(t, err)
// Execute the function being tested
data, err := GetTemporaryVoucherData(ctx, store, sessionId)
require.NoError(t, err)
require.Equal(t, tempData, data)
}
func TestUpdateVoucherData(t *testing.T) {
ctx, store := InitializeTestDb(t)
sessionId := "session123"
// New voucher data
newData := &dataserviceapi.TokenHoldings{
TokenSymbol: "SRF",
Balance: "200",
TokenDecimals: "6",
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
// Old temporary data
tempData := &dataserviceapi.TokenHoldings{
TokenSymbol: "OLD",
Balance: "100",
TokenDecimals: "8",
ContractAddress: "0xold",
}
require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData))
// Execute update
err := UpdateVoucherData(ctx, store, sessionId, newData)
require.NoError(t, err)
// Verify active data was stored correctly
activeEntries := map[DataTyp][]byte{
DATA_ACTIVE_SYM: []byte(newData.TokenSymbol),
DATA_ACTIVE_BAL: []byte(newData.Balance),
DATA_ACTIVE_DECIMAL: []byte(newData.TokenDecimals),
DATA_ACTIVE_ADDRESS: []byte(newData.ContractAddress),
}
for key, expectedValue := range activeEntries {
storedValue, err := store.ReadEntry(ctx, sessionId, key)
require.NoError(t, err)
require.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key)
}
}

View File

@ -1,460 +0,0 @@
{
"groups": [
{
"name": "my_account_change_pin",
"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\n0:Back"
},
{
"input": "5",
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n3:Guard my PIN\n0:Back"
},
{
"input": "1",
"expectedContent": "Enter your old PIN\n\n0:Back"
},
{
"input": "1234",
"expectedContent": "Enter a new four number PIN:\n\n0:Back"
},
{
"input": "1234",
"expectedContent": "Confirm your new PIN:\n\n0:Back"
},
{
"input": "1234",
"expectedContent": "Your PIN change request has been successful\n\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_language_change",
"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\n0:Back"
},
{
"input": "2",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Select language:\n0:english\n1:kiswahili"
},
{
"input": "0",
"expectedContent": "Your language change request 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_check_my_balance",
"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\n0:Back"
},
{
"input": "3",
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
},
{
"input": "1",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Your balance is 0.003 CELO\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_check_community_balance",
"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\n0:Back"
},
{
"input": "3",
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
},
{
"input": "2",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Your community balance is 0.003 CELO\n0:Back\n9:Quit"
},
{
"input": "0",
"expectedContent": "Balances:\n1:My balance\n2:Community balance\n0:Back"
},
{
"input": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_firstname",
"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\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": "1",
"expectedContent": "Enter your first names:\n0:Back"
},
{
"input": "foo",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_familyname",
"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\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": "2",
"expectedContent": "Enter family name:\n0:Back"
},
{
"input": "bar",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_gender",
"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\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": "3",
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
},
{
"input": "1",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_yob",
"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\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": "4",
"expectedContent": "Enter your year of birth\n0:Back"
},
{
"input": "1945",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_location",
"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\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": "5",
"expectedContent": "Enter your location:\n0:Back"
},
{
"input": "Kilifi",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_edit_offerings",
"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\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": "6",
"expectedContent": "Enter the services or goods you offer: \n0:Back"
},
{
"input": "Bananas",
"expectedContent": "Please enter your PIN:"
},
{
"input": "1234",
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
},
{
"name": "menu_my_account_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\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": "1234",
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back"
},
{
"input": "0",
"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": "0",
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
},
{
"input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
}
]
}
]
}

View File

@ -1,380 +0,0 @@
package menutraversaltest
import (
"bytes"
"context"
"log"
"math/rand"
"os"
"regexp"
"testing"
"git.grassecon.net/urdt/ussd/internal/testutil"
"git.grassecon.net/urdt/ussd/internal/testutil/driver"
"github.com/gofrs/uuid"
)
var (
testData = driver.ReadData()
testStore = ".test_state"
groupTestFile = "group_test.json"
sessionID string
src = rand.NewSource(42)
g = rand.New(src)
)
func GenerateSessionId() string {
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4()
if err != nil {
panic(err)
}
return v.String()
}
// Extract the public key from the engine response
func extractPublicKey(response []byte) string {
// Regex pattern to match the public key starting with 0x and 40 characters
re := regexp.MustCompile(`0x[a-fA-F0-9]{40}`)
match := re.Find(response)
if match != nil {
return string(match)
}
return ""
}
// Extracts the balance value from the engine response.
func extractBalance(response []byte) string {
// Regex to match "Balance: <amount> <symbol>" followed by a newline
re := regexp.MustCompile(`(?m)^Balance:\s+(\d+(\.\d+)?)\s+([A-Z]+)`)
match := re.FindSubmatch(response)
if match != nil {
return string(match[1]) + " " + string(match[3]) // "<amount> <symbol>"
}
return ""
}
// Extracts the Maximum amount value from the engine response.
func extractMaxAmount(response []byte) string {
// Regex to match "Maximum amount: <amount>" followed by a newline
re := regexp.MustCompile(`(?m)^Maximum amount:\s+(\d+(\.\d+)?)`)
match := re.FindSubmatch(response)
if match != nil {
return string(match[1]) // "<amount>"
}
return ""
}
// Extracts the send amount value from the engine response.
func extractSendAmount(response []byte) string {
// Regex to match the pattern "will receive X.XX SYM from"
re := regexp.MustCompile(`will receive (\d+\.\d{2}\s+[A-Z]+) from`)
match := re.FindSubmatch(response)
if match != nil {
return string(match[1]) // Returns "X.XX SYM"
}
return ""
}
func TestMain(m *testing.M) {
sessionID = GenerateSessionId()
defer func() {
if err := os.RemoveAll(testStore); err != nil {
log.Fatalf("Failed to delete state store %s: %v", testStore, err)
}
}()
m.Run()
}
func TestAccountCreationSuccessful(t *testing.T) {
en, fn, eventChannel := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "account_creation_successful")
for _, group := range groups {
for _, step := range group.Steps {
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 TestAccountRegistrationRejectTerms(t *testing.T) {
// Generate a new UUID for this edge case test
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4()
if err != nil {
t.Fail()
}
edgeCaseSessionID := v.String()
en, fn, _ := testutil.TestEngine(edgeCaseSessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "account_creation_reject_terms")
for _, group := range groups {
for _, step := range group.Steps {
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)
return
}
if !cont {
break
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); 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)
}
}
}
}
}
func TestMainMenuHelp(t *testing.T) {
en, fn, _ := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "main_menu_help")
for _, group := range groups {
for _, step := range group.Steps {
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)
return
}
if !cont {
break
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); err != nil {
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
}
b := w.Bytes()
balance := extractBalance(b)
expectedContent := []byte(step.ExpectedContent)
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
step.ExpectedContent = string(expectedContent)
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)
}
}
}
}
}
func TestMainMenuQuit(t *testing.T) {
en, fn, _ := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "main_menu_quit")
for _, group := range groups {
for _, step := range group.Steps {
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)
return
}
if !cont {
break
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); err != nil {
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
}
b := w.Bytes()
balance := extractBalance(b)
expectedContent := []byte(step.ExpectedContent)
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
step.ExpectedContent = string(expectedContent)
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)
}
}
}
}
}
func TestMyAccount_MyAddress(t *testing.T) {
en, fn, _ := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "menu_my_account_my_address")
for _, group := range groups {
for index, step := range group.Steps {
t.Logf("step %v with input %v", index, step.Input)
cont, err := en.Exec(ctx, []byte(step.Input))
if err != nil {
t.Errorf("Test case '%s' failed at input '%s': %v", group.Name, step.Input, err)
return
}
if !cont {
break
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); err != nil {
t.Errorf("Test case '%s' failed during Flush: %v", group.Name, err)
}
b := w.Bytes()
balance := extractBalance(b)
publicKey := extractPublicKey(b)
expectedContent := []byte(step.ExpectedContent)
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
expectedContent = bytes.Replace(expectedContent, []byte("{public_key}"), []byte(publicKey), -1)
step.ExpectedContent = string(expectedContent)
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", expectedContent, b)
}
}
}
}
}
func TestMainMenuSend(t *testing.T) {
en, fn, _ := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
sessions := testData
for _, session := range sessions {
groups := driver.FilterGroupsByName(session.Groups, "send_with_invalid_inputs")
for _, group := range groups {
for _, step := range group.Steps {
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)
return
}
if !cont {
break
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); err != nil {
t.Fatalf("Test case '%s' failed during Flush: %v", group.Name, err)
}
b := w.Bytes()
balance := extractBalance(b)
max_amount := extractMaxAmount(b)
send_amount := extractSendAmount(b)
expectedContent := []byte(step.ExpectedContent)
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
expectedContent = bytes.Replace(expectedContent, []byte("{max_amount}"), []byte(max_amount), -1)
expectedContent = bytes.Replace(expectedContent, []byte("{send_amount}"), []byte(send_amount), -1)
expectedContent = bytes.Replace(expectedContent, []byte("{session_id}"), []byte(sessionID), -1)
step.ExpectedContent = string(expectedContent)
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)
}
}
}
}
}
func TestGroups(t *testing.T) {
groups, err := driver.LoadTestGroups(groupTestFile)
if err != nil {
log.Fatalf("Failed to load test groups: %v", err)
}
en, fn, _ := testutil.TestEngine(sessionID)
defer fn()
ctx := context.Background()
// Create test cases from loaded groups
tests := driver.CreateTestCases(groups)
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
cont, err := en.Exec(ctx, []byte(tt.Input))
if err != nil {
t.Errorf("Test case '%s' failed at input '%s': %v", tt.Name, tt.Input, err)
return
}
if !cont {
return
}
w := bytes.NewBuffer(nil)
if _, err := en.Flush(ctx, w); err != nil {
t.Errorf("Test case '%s' failed during Flush: %v", tt.Name, err)
}
b := w.Bytes()
balance := extractBalance(b)
expectedContent := []byte(tt.ExpectedContent)
expectedContent = bytes.Replace(expectedContent, []byte("{balance}"), []byte(balance), -1)
tt.ExpectedContent = string(expectedContent)
match, err := tt.MatchesExpectedContent(b)
if err != nil {
t.Fatalf("Error compiling regex for step '%s': %v", tt.Input, err)
}
if !match {
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", tt.ExpectedContent, b)
}
})
}
}

View File

@ -1,153 +0,0 @@
[
{
"name": "session one",
"groups": [
{
"name": "account_creation_successful",
"steps": [
{
"input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
},
{
"input": "0",
"expectedContent": "Please enter a new four number PIN for your account:\n0:Exit"
},
{
"input": "1234",
"expectedContent": "Enter your four number PIN again:"
},
{
"input": "1111",
"expectedContent": "The PIN is not a match. Try again\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Enter your four number PIN again:"
},
{
"input": "1234",
"expectedContent": "Your account is being created...Thank you for using Sarafu. Goodbye!"
}
]
},
{
"name": "account_creation_reject_terms",
"steps": [
{
"input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
},
{
"input": "1",
"expectedContent": "Thank you for using Sarafu. Goodbye!"
}
]
},
{
"name": "send_with_invalid_inputs",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "1",
"expectedContent": "Enter recipient's phone number:\n0:Back"
},
{
"input": "000",
"expectedContent": "000 is not registered or invalid, please try again:\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "Enter recipient's phone number:\n0:Back"
},
{
"input": "065656",
"expectedContent": "{max_amount}\nEnter amount:\n0:Back"
},
{
"input": "10000000",
"expectedContent": "Amount 10000000 is invalid, please try again:\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "{max_amount}\nEnter amount:\n0:Back"
},
{
"input": "1.00",
"expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
},
{
"input": "1222",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
},
{
"input": "1",
"expectedContent": "065656 will receive {send_amount} from {session_id}\nPlease enter your PIN to confirm:\n0:Back\n9:Quit"
},
{
"input": "1234",
"expectedContent": "Your request has been sent. 065656 will receive {send_amount} from {session_id}."
}
]
},
{
"name": "main_menu_help",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "4",
"expectedContent": "For more help,please call: 0757628885"
}
]
},
{
"name": "main_menu_quit",
"steps": [
{
"input": "",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
},
{
"input": "9",
"expectedContent": "Thank you for using Sarafu. Goodbye!"
}
]
},
{
"name": "menu_my_account_my_address",
"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\n0:Back"
},
{
"input": "6",
"expectedContent": "Address: {public_key}\n9:Quit"
},
{
"input": "9",
"expectedContent": "Thank you for using Sarafu. Goodbye!"
}
]
}
]
}
]

View File

@ -1,60 +1,65 @@
package handlers
package request
import (
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/grassrootseconomics/visedriver/storage"
"git.grassecon.net/grassrootseconomics/visedriver/errors"
"git.grassecon.net/grassrootseconomics/visedriver/entry"
)
type BaseSessionHandler struct {
type BaseRequestHandler struct {
cfgTemplate engine.Config
rp RequestParser
rs resource.Resource
hn *ussd.Handlers
hn entry.EntryHandler
provider storage.StorageProvider
}
func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler {
return &BaseSessionHandler{
//func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn *handlers.Handlers) *BaseRequestHandler {
func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn entry.EntryHandler) *BaseRequestHandler {
return &BaseRequestHandler{
cfgTemplate: cfg,
rs: rs,
hn: hn,
rp: rp,
provider: storage.NewSimpleStorageProvider(stateDb, userdataDb),
rs: rs,
hn: hn,
rp: rp,
provider: storage.NewSimpleStorageProvider(stateDb, userdataDb),
}
}
func(f* BaseSessionHandler) Shutdown() {
func (f *BaseRequestHandler) Shutdown() {
err := f.provider.Close()
if err != nil {
logg.Errorf("handler shutdown error", "err", err)
}
}
func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
func (f *BaseRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr)
return en
}
func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) {
func(f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) {
var r bool
var err error
var ok bool
logg.InfoCtxf(rqs.Ctx, "new request", "data", rqs)
rqs.Storage, err = f.provider.Get(rqs.Config.SessionId)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err)
return rqs, ErrStorage
return rqs, errors.ErrStorage
}
f.hn = f.hn.WithPersister(rqs.Storage.Persister)
//f.hn = f.hn.WithPersister(rqs.Storage.Persister)
f.hn.SetPersister(rqs.Storage.Persister)
defer func() {
f.hn.Exit()
}()
eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
en, ok := eni.(*engine.DefaultEngine)
if !ok {
@ -63,7 +68,7 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
}
return rqs, ErrEngineType
return rqs, errors.ErrEngineType
}
en = en.WithFirst(f.hn.Init)
if rqs.Config.EngineDebug {
@ -81,25 +86,25 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
return rqs, err
}
rqs.Continue = r
rqs.Continue = r
return rqs, nil
}
func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) {
func(f *BaseRequestHandler) Output(rqs RequestSession) (RequestSession, error) {
var err error
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
return rqs, err
}
func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) {
func(f *BaseRequestHandler) Reset(rqs RequestSession) (RequestSession, error) {
defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
return rqs, rqs.Engine.Finish()
}
func(f *BaseSessionHandler) GetConfig() engine.Config {
func (f *BaseRequestHandler) GetConfig() engine.Config {
return f.cfgTemplate
}
func(f *BaseSessionHandler) GetRequestParser() RequestParser {
func(f *BaseRequestHandler) GetRequestParser() RequestParser {
return f.rp
}

37
request/http/parse.go Normal file
View File

@ -0,0 +1,37 @@
package http
import (
"context"
"io/ioutil"
"net/http"
"git.grassecon.net/grassrootseconomics/visedriver/errors"
)
type DefaultRequestParser struct {
}
func (rp *DefaultRequestParser) GetSessionId(ctx context.Context, rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return "", errors.ErrInvalidRequest
}
v := rqv.Header.Get("X-Vise-Session")
if v == "" {
return "", errors.ErrSessionMissing
}
return v, nil
}
func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, errors.ErrInvalidRequest
}
defer rqv.Body.Close()
v, err := ioutil.ReadAll(rqv.Body)
if err != nil {
return nil, err
}
return v, nil
}

92
request/http/server.go Normal file
View File

@ -0,0 +1,92 @@
package http
import (
"net/http"
"strconv"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/visedriver/request"
"git.grassecon.net/grassrootseconomics/visedriver/errors"
)
var (
logg = logging.NewVanilla().WithDomain("visedriver.http.session")
)
// HTTPRequestHandler implements the session handler for HTTP
type HTTPRequestHandler struct {
request.RequestHandler
}
func (f *HTTPRequestHandler) WriteError(w http.ResponseWriter, code int, err error) {
s := err.Error()
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
w.WriteHeader(code)
_, err = w.Write([]byte(s))
if err != nil {
logg.Errorf("error writing error!!", "err", err, "olderr", s)
w.WriteHeader(500)
}
}
func NewHTTPRequestHandler(h request.RequestHandler) *HTTPRequestHandler {
return &HTTPRequestHandler{
RequestHandler: h,
}
}
func (hh *HTTPRequestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
var perr error
rqs := request.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := hh.GetRequestParser()
cfg := hh.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req.Context(), req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
hh.WriteError(w, 400, err)
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
hh.WriteError(w, 400, err)
return
}
rqs, err = hh.Process(rqs)
switch err {
case errors.ErrStorage:
code = 500
case errors.ErrEngineInit:
code = 500
case errors.ErrEngineExec:
code = 500
default:
code = 200
}
if code != 200 {
hh.WriteError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = hh.Output(rqs)
rqs, perr = hh.Reset(rqs)
if err != nil {
hh.WriteError(w, 500, err)
return
}
if perr != nil {
hh.WriteError(w, 500, perr)
return
}
}

233
request/http/server_test.go Normal file
View File

@ -0,0 +1,233 @@
package http
import (
"bytes"
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"git.defalsify.org/vise.git/engine"
viseerrors "git.grassecon.net/grassrootseconomics/visedriver/errors"
"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks"
"git.grassecon.net/grassrootseconomics/visedriver/request"
)
// invalidRequestType is a custom type to test invalid request scenarios
type invalidRequestType struct{}
// errorReader is a helper type that always returns an error when Read is called
type errorReader struct{}
func (e *errorReader) Read(p []byte) (n int, err error) {
return 0, errors.New("read error")
}
func TestRequestHandler_ServeHTTP(t *testing.T) {
tests := []struct {
name string
sessionID string
input []byte
parserErr error
processErr error
outputErr error
resetErr error
expectedStatus int
}{
{
name: "Success",
sessionID: "123",
input: []byte("test input"),
expectedStatus: http.StatusOK,
},
{
name: "Missing Session ID",
sessionID: "",
parserErr: viseerrors.ErrSessionMissing,
expectedStatus: http.StatusBadRequest,
},
{
name: "Process Error",
sessionID: "123",
input: []byte("test input"),
processErr: viseerrors.ErrStorage,
expectedStatus: http.StatusInternalServerError,
},
{
name: "Output Error",
sessionID: "123",
input: []byte("test input"),
outputErr: errors.New("output error"),
expectedStatus: http.StatusOK,
},
{
name: "Reset Error",
sessionID: "123",
input: []byte("test input"),
resetErr: errors.New("reset error"),
expectedStatus: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRequestParser := &httpmocks.MockRequestParser{
GetSessionIdFunc: func(any) (string, error) {
return tt.sessionID, tt.parserErr
},
GetInputFunc: func(any) ([]byte, error) {
return tt.input, nil
},
}
mockRequestHandler := &httpmocks.MockRequestHandler{
ProcessFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.processErr
},
OutputFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.outputErr
},
ResetFunc: func(rs request.RequestSession) (request.RequestSession, error) {
return rs, tt.resetErr
},
GetRequestParserFunc: func() request.RequestParser {
return mockRequestParser
},
GetConfigFunc: func() engine.Config {
return engine.Config{}
},
}
sessionHandler := &HTTPRequestHandler{
RequestHandler: mockRequestHandler,
}
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input))
req.Header.Set("X-Vise-Session", tt.sessionID)
rr := httptest.NewRecorder()
sessionHandler.ServeHTTP(rr, req)
if status := rr.Code; status != tt.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v",
status, tt.expectedStatus)
}
})
}
}
func TestRequestHandler_WriteError(t *testing.T) {
handler := &HTTPRequestHandler{}
mockWriter := &httpmocks.MockWriter{}
err := errors.New("test error")
handler.WriteError(mockWriter, http.StatusBadRequest, err)
if mockWriter.WrittenString != "" {
t.Errorf("Expected empty body, got %s", mockWriter.WrittenString)
}
}
func TestDefaultRequestParser_GetSessionId(t *testing.T) {
tests := []struct {
name string
request any
expectedID string
expectedError error
}{
{
name: "Valid Session ID",
request: func() *http.Request {
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("X-Vise-Session", "123456")
return req
}(),
expectedID: "123456",
expectedError: nil,
},
{
name: "Missing Session ID",
request: httptest.NewRequest(http.MethodPost, "/", nil),
expectedID: "",
expectedError: viseerrors.ErrSessionMissing,
},
{
name: "Invalid Request Type",
request: invalidRequestType{},
expectedID: "",
expectedError: viseerrors.ErrInvalidRequest,
},
}
parser := &DefaultRequestParser{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
id, err := parser.GetSessionId(context.Background(),tt.request)
if id != tt.expectedID {
t.Errorf("Expected session ID %s, got %s", tt.expectedID, id)
}
if err != tt.expectedError {
t.Errorf("Expected error %v, got %v", tt.expectedError, err)
}
})
}
}
func TestDefaultRequestParser_GetInput(t *testing.T) {
tests := []struct {
name string
request any
expectedInput []byte
expectedError error
}{
{
name: "Valid Input",
request: func() *http.Request {
return httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("test input"))
}(),
expectedInput: []byte("test input"),
expectedError: nil,
},
{
name: "Empty Input",
request: httptest.NewRequest(http.MethodPost, "/", nil),
expectedInput: []byte{},
expectedError: nil,
},
{
name: "Invalid Request Type",
request: invalidRequestType{},
expectedInput: nil,
expectedError: viseerrors.ErrInvalidRequest,
},
{
name: "Read Error",
request: func() *http.Request {
return httptest.NewRequest(http.MethodPost, "/", &errorReader{})
}(),
expectedInput: nil,
expectedError: errors.New("read error"),
},
}
parser := &DefaultRequestParser{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input, err := parser.GetInput(tt.request)
if !bytes.Equal(input, tt.expectedInput) {
t.Errorf("Expected input %s, got %s", tt.expectedInput, input)
}
if err != tt.expectedError && (err == nil || err.Error() != tt.expectedError.Error()) {
t.Errorf("Expected error %v, got %v", tt.expectedError, err)
}
})
}
}

View File

@ -1,30 +1,18 @@
package handlers
package request
import (
"context"
"errors"
"io"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/grassrootseconomics/visedriver/storage"
)
var (
logg = logging.NewVanilla().WithDomain("handlers")
)
var (
ErrInvalidRequest = errors.New("invalid request for context")
ErrSessionMissing = errors.New("missing session")
ErrInvalidInput = errors.New("invalid input")
ErrStorage = errors.New("storage retrieval fail")
ErrEngineType = errors.New("incompatible engine")
ErrEngineInit = errors.New("engine init fail")
ErrEngineExec = errors.New("engine exec fail")
logg = logging.NewVanilla().WithDomain("visedriver.request")
)
type RequestSession struct {
@ -39,7 +27,7 @@ type RequestSession struct {
// TODO: seems like can remove this.
type RequestParser interface {
GetSessionId(rq any) (string, error)
GetSessionId(ctx context.Context, rq any) (string, error)
GetInput(rq any) ([]byte, error)
}

View File

@ -1,18 +0,0 @@
# Variables to match files in the current directory
INPUTS = $(wildcard ./*.vis)
TXTS = $(wildcard ./*.txt.orig)
VISE_PATH := ../../go-vise
# Rule to build .bin files from .vis files
%.vis:
go run $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
@echo "Built $(basename $@).bin from $(basename $@).vis"
# Rule to copy .orig files to .txt
%.txt.orig:
cp -v $(basename $@).orig $(basename $@)
@echo "Copied $(basename $@).orig to $(basename $@)"
# 'all' target depends on all .vis and .txt.orig files
all: $(INPUTS) $(TXTS)
@echo "Running all: $(INPUTS) $(TXTS)"

View File

@ -1 +0,0 @@
Something went wrong.Please try again

View File

@ -1 +0,0 @@
HALT

View File

@ -1 +0,0 @@
Your account is being created...

View File

@ -1,4 +0,0 @@
RELOAD verify_create_pin
CATCH create_pin_mismatch flag_pin_mismatch 1
LOAD quit 0
HALT

View File

@ -1 +0,0 @@
Your account creation request failed. Please try again later.

View File

@ -1,3 +0,0 @@
MOUT quit 9
HALT
INCMP quit 9

View File

@ -1 +0,0 @@
Ombi lako la kusajiliwa haliwezi kukamilishwa. Tafadhali jaribu tena baadaye.

View File

@ -1 +0,0 @@
Akaunti yako inatengenezwa...

View File

@ -1 +0,0 @@
My Account

View File

@ -1 +0,0 @@
Akaunti yangu

View File

@ -1 +0,0 @@
Your account is still being created.

View File

@ -1,3 +0,0 @@
RELOAD check_account_status
CATCH main flag_account_success 1
HALT

View File

@ -1 +0,0 @@
Akaunti yako bado inatengenezwa

View File

@ -1 +0,0 @@
Address: {{.check_identifier}}

View File

@ -1,6 +0,0 @@
LOAD check_identifier 0
RELOAD check_identifier
MAP check_identifier
MOUT quit 9
HALT
INCMP quit 9

View File

@ -1,2 +0,0 @@
Maximum amount: {{.max_amount}}
Enter amount:

View File

@ -1,15 +0,0 @@
LOAD reset_transaction_amount 0
LOAD max_amount 10
RELOAD max_amount
MAP max_amount
MOUT back 0
HALT
LOAD validate_amount 64
RELOAD validate_amount
CATCH api_failure flag_api_call_error 1
CATCH invalid_amount flag_invalid_amount 1
INCMP _ 0
LOAD get_recipient 12
LOAD get_sender 64
LOAD get_amount 32
INCMP transaction_pin *

View File

@ -1,2 +0,0 @@
Kiwango cha juu: {{.max_amount}}
Weka kiwango:

View File

@ -1 +0,0 @@
Failed to connect to the custodial service.Please try again.

View File

@ -1,5 +0,0 @@
MOUT retry 0
MOUT quit 9
HALT
INCMP _ 0
INCMP quit 9

View File

@ -1 +0,0 @@
Back

View File

@ -1 +0,0 @@
Rudi

View File

@ -1 +0,0 @@
Balances:

View File

@ -1,9 +0,0 @@
LOAD reset_account_authorized 0
RELOAD reset_account_authorized
MOUT my_balance 1
MOUT community_balance 2
MOUT back 0
HALT
INCMP _ 0
INCMP my_balance 1
INCMP community_balance 2

View File

@ -1 +0,0 @@
Salio:

View File

@ -1 +0,0 @@
Select language:

View File

@ -1,10 +0,0 @@
LOAD reset_account_authorized 0
LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0
MOUT english 0
MOUT kiswahili 1
HALT
INCMP set_default 0
INCMP set_swa 1
INCMP . *

View File

@ -1 +0,0 @@
Change language

View File

@ -1 +0,0 @@
Badili lugha

View File

@ -1 +0,0 @@
Chagua lugha:

View File

@ -1 +0,0 @@
Change PIN

View File

@ -1 +0,0 @@
Badili PIN

View File

@ -1 +0,0 @@
Check balances

View File

@ -1 +0,0 @@
Angalia salio

View File

@ -1 +0,0 @@
Check statement

View File

@ -1 +0,0 @@
Taarifa ya matumizi

View File

@ -1 +0,0 @@
Salio la kikundi

View File

@ -1 +0,0 @@
{{.fetch_custodial_balances}}

View File

@ -1,11 +0,0 @@
LOAD reset_incorrect 6
LOAD fetch_custodial_balances 0
CATCH api_failure flag_api_call_error 1
MAP fetch_custodial_balances
CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0
MOUT back 0
MOUT quit 9
HALT
INCMP _ 0
INCMP quit 9

View File

@ -1 +0,0 @@
Community balance

View File

@ -1 +0,0 @@
Salio la kikundi

View File

@ -1 +0,0 @@
Enter your four number PIN again:

View File

@ -1,4 +0,0 @@
LOAD save_temporary_pin 6
HALT
LOAD verify_create_pin 8
INCMP account_creation *

View File

@ -1 +0,0 @@
Weka PIN yako tena:

View File

@ -1 +0,0 @@
Confirm your new PIN:

View File

@ -1,7 +0,0 @@
CATCH invalid_pin flag_valid_pin 0
MOUT back 0
HALT
INCMP _ 0
INCMP * pin_reset_success

View File

@ -1 +0,0 @@
Thibitisha PIN yako mpya:

View File

@ -1 +0,0 @@
Please enter a new four number PIN for your account:

View File

@ -1,9 +0,0 @@
LOAD create_account 0
CATCH account_creation_failed flag_account_creation_failed 1
MOUT exit 0
HALT
LOAD save_temporary_pin 6
RELOAD save_temporary_pin
CATCH . flag_incorrect_pin 1
INCMP quit 0
INCMP confirm_create_pin *

Some files were not shown because too many files have changed in this diff Show More