Compare commits

..

117 Commits

Author SHA1 Message Date
alfred-mk
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: #168
2024-11-08 17:25:46 +01:00
alfred-mk
574807d254 set the africastalking POST route using env 2024-11-08 16:52:19 +03:00
carlos
256ed6491b Merge pull request 'http-logs' (#167) from http-logs into master
Reviewed-on: #167
2024-11-08 12:51:30 +01:00
carlos
7a02ffcf0c Merge pull request 'log-file' (#166) from log-file into http-logs
Reviewed-on: #166
2024-11-08 08:09:15 +01:00
Carlosokumu
dcd8fce59a add log on create account 2024-11-08 10:07:06 +03:00
alfred-mk
64a7b49218 Remove unused Warning logger 2024-11-08 00:21:11 +03:00
alfred-mk
1bcbb2079e Removed unused variable 2024-11-08 00:17:02 +03:00
alfred-mk
e63468433e Merge branch 'http-logs' into log-file 2024-11-08 00:14:34 +03:00
alfred-mk
9c972ffa6b add specific log files per binary 2024-11-07 16:46:12 +03:00
alfred-mk
a11776e1b3 ignore .log files 2024-11-07 16:41:38 +03:00
Carlosokumu
6ac9ac29d8 add simple http logging 2024-11-07 16:41:08 +03:00
46b2b354fd Merge pull request 'swahili-templates-menu' (#158) from swahili-templates-menu into master
Reviewed-on: #158
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-11-05 11:29:07 +01:00
Carlosokumu
7676cfd40c Merge branch 'master' into swahili-templates-menu 2024-11-05 13:25:49 +03:00
Carlosokumu
4e350aa25a update test data file 2024-11-05 10:49:08 +03:00
859de0513a Merge pull request 'api-error-fix' (#161) from api-error-fix into master
Reviewed-on: #161
2024-11-05 00:18:04 +01:00
alfred-mk
266d3d06c3 Check the flag_no_active_voucher before proceeding 2024-11-04 17:56:25 +03:00
alfred-mk
92ea3df4aa Removeearly return statement 2024-11-04 17:38:55 +03:00
Carlosokumu
c46c31ea36 ensure swahili translation 2024-11-04 16:06:33 +03:00
Carlosokumu
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: #157
2024-11-04 13:54:58 +01:00
alfred-mk
88b50c5dd7 Remove admin number defination in env example 2024-11-04 15:52:03 +03:00
alfred-mk
43a1208cce Merge branch 'master' into lash/export-to-term 2024-11-04 15:38:53 +03:00
Carlosokumu
2b865a365b add back option to view address 2024-11-04 15:24:51 +03:00
Carlosokumu
c77558689a ensure swahlili menu template 2024-11-04 15:24:22 +03:00
Carlosokumu
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
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: #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: #152
2024-11-02 13:44:03 +01:00
Carlosokumu
6dbe74d12b use single temporary value 2024-11-01 16:46:09 +03:00
Carlosokumu
332074375a wrap in devtools/admin 2024-11-01 16:44:54 +03:00
Carlosokumu
5e4a9e7567 Merge branch 'master' into pin-reset 2024-11-01 16:26:29 +03:00
Carlosokumu
eb2c73dce1 remove unused setadmin store 2024-11-01 11:51:50 +03:00
Carlosokumu
7e448f739a change fs store path to root 2024-11-01 09:35:48 +03:00
Carlosokumu
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
Carlosokumu
2704069e74 Merge branch 'master' into pin-reset 2024-10-31 21:09:48 +03:00
Carlosokumu
7d1a04f089 remove from root 2024-10-31 21:01:24 +03:00
Carlosokumu
53fa6f64ce define structure of json 2024-10-31 21:01:01 +03:00
Carlosokumu
7fa38340dd add command to initialize a list of admin numbers 2024-10-31 21:00:41 +03:00
Carlosokumu
7aab3cff8c remove seed 2024-10-31 21:00:16 +03:00
Carlosokumu
299534ccf1 define seed as a command in the devtool 2024-10-31 20:59:51 +03:00
Carlosokumu
b2655b7f11 remove seed from executable 2024-10-31 20:59:11 +03:00
Carlosokumu
5abe9b78cc attach an admin store for the phone numbers 2024-10-31 20:11:26 +03:00
Carlosokumu
12825ae08a setup adminstore in the local handler service 2024-10-31 20:10:46 +03:00
Carlosokumu
ac0b4b2ed1 pass context 2024-10-31 20:08:30 +03:00
Carlosokumu
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
alfred-mk
bab3f673eb Removed unused model 2024-10-31 14:13:21 +03:00
Carlosokumu
767a3cd64c remove pin guard menu option 2024-10-31 09:38:51 +03:00
Carlosokumu
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
Carlosokumu
d434194021 catch incorrect pin entry 2024-10-30 23:21:15 +03:00
Carlosokumu
c6ca3f6be4 fix sink error 2024-10-30 22:32:38 +03:00
Carlosokumu
8093eae61a use 0 instead of 1 for back 2024-10-30 21:26:52 +03:00
Carlosokumu
833d52a558 remove print message 2024-10-30 21:26:11 +03:00
Carlosokumu
8262e14198 Merge branch 'master' into pin-reset 2024-10-30 21:10:12 +03:00
Carlosokumu
ea4c6d9314 check for phone number validity 2024-10-30 18:01:43 +03:00
Carlosokumu
7c823e07ca move to root node after on back 2024-10-30 18:01:20 +03:00
Carlosokumu
41585f831c move catch and load to next node 2024-10-30 18:00:38 +03:00
Carlosokumu
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
Carlosokumu
cf523e30f8 update handler in test 2024-10-30 14:50:31 +03:00
Carlosokumu
888d3befe9 add actual pin reset functionality 2024-10-30 14:50:12 +03:00
Carlosokumu
017691a40c define structure for admin numbers 2024-10-30 14:41:14 +03:00
Carlosokumu
dc418771a7 attach an admin store for phone numbers 2024-10-30 14:33:48 +03:00
Carlosokumu
c2068db050 update handler functions 2024-10-30 14:33:22 +03:00
Carlosokumu
c42b1cd66b provide required handler functions and admin store 2024-10-30 14:32:01 +03:00
Carlosokumu
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
Carlosokumu
0a97f610a4 catch unregistred number 2024-10-29 22:23:22 +03:00
Carlosokumu
5a0563df94 group regex,check for valid number against the regex 2024-10-29 22:17:43 +03:00
Carlosokumu
7597b96dae remove catch for unregistered number 2024-10-29 22:16:34 +03:00
Carlosokumu
f37483e2f0 use _ for back navigation 2024-10-29 22:15:31 +03:00
Carlosokumu
d0ad6395b5 add check for unregistered phone numbers 2024-10-29 17:35:42 +03:00
Carlosokumu
106983a394 use explicit back to node 2024-10-29 17:35:01 +03:00
Carlosokumu
91b85af11a add reset unregistered number 2024-10-29 17:34:34 +03:00
Carlosokumu
534d756318 catch unregistred phone numbers 2024-10-29 17:18:39 +03:00
Carlosokumu
6998c30dd1 add node to handle unregistered phone numbers 2024-10-29 17:18:01 +03:00
Carlosokumu
449f90c95b add flag to catch unregistred numbers 2024-10-29 17:17:03 +03:00
Carlosokumu
e96c874300 repeat same node on invalid option input 2024-10-29 15:02:40 +03:00
Carlosokumu
b35460d3c1 Merge branch 'master' into pin-reset 2024-10-29 14:35:17 +03:00
Carlosokumu
124049c924 add admin number defination in env 2024-10-29 14:32:17 +03:00
Carlosokumu
5fd3eb3c29 set admin privilege flag 2024-10-29 14:28:58 +03:00
Carlosokumu
d83962c0ba load admin numbers defined in the .env 2024-10-29 14:26:24 +03:00
Carlosokumu
41da099933 remove the admin flag,setup an admin store 2024-10-29 13:24:13 +03:00
Carlosokumu
c9bb93ede6 create a simple admin store for phone numbers 2024-10-29 13:15:41 +03:00
Carlosokumu
ca13d9155c replace _ with explicit back node 2024-10-29 13:15:22 +03:00
Carlosokumu
e338ce0025 load and reload only after input 2024-10-29 13:14:49 +03:00
Carlosokumu
b97965193b add pin reset for others handling 2024-10-28 16:37:23 +03:00
Carlosokumu
aec0abb2b6 setup an admin flag 2024-10-28 16:34:33 +03:00
Carlosokumu
26073c8000 define handler functions required to reset others pin 2024-10-28 15:49:20 +03:00
Carlosokumu
e4c2f644f3 define a key to hold number during pin reset 2024-10-28 15:47:56 +03:00
Carlosokumu
3de46cef5e setup pin reset nodes 2024-10-28 15:45:08 +03:00
Carlosokumu
0cc0bdf9f7 add pin reset for others nodes 2024-10-28 15:19:35 +03:00
Carlosokumu
72d5c186dd add admin privilege flag 2024-10-28 15:18:40 +03:00
77 changed files with 1284 additions and 611 deletions

View File

@@ -16,3 +16,6 @@ CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/ TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/ BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
TRACK_URL=http://localhost:5003/api/v2/account/status TRACK_URL=http://localhost:5003/api/v2/account/status
#AfricasTalking USSD POST endpoint
AT_ENDPOINT=/ussd/africastalking

1
.gitignore vendored
View File

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

View File

@@ -1,8 +1,91 @@
# ussd # URDT USSD service
> USSD This is a USSD service built using the [go-vise](https://github.com/nolash/go-vise) engine.
USSD service. ## Prerequisites
### 1. [go-vise](https://github.com/nolash/go-vise)
Set up `go-vise` by cloning the repository into a separate directory. The main upstream repository is hosted at: `https://git.defalsify.org/vise.git`
```
git clone https://git.defalsify.org/vise.git
```
## Setup
1. Clone the ussd repo in its own directory
```
git clone https://git.grassecon.net/urdt/ussd.git
```
2. Navigate to the project directory.
3. Enter the `services/registration` subfolder:
```
cd services/registration
```
4. make the .bin files from the .vis files
```
make VISE_PATH=/var/path/to/your/go-vise -B
```
5. Return to the project root (`cd ../..`)
6. Run the USSD menu
```
go run cmd/main.go -session-id=0712345678
```
## Running the different binaries
1. ### CLI:
```
go run cmd/main.go -session-id=0712345678
```
2. ### Africastalking:
```
go run cmd/africastalking/main.go
```
3. ### Async:
```
go run cmd/async/main.go
```
4. ### Http:
```
go run cmd/http/main.go
```
## Flags
Below are the supported flags:
1. `-session-id`:
Specifies the session ID. (CLI only).
Default: `075xx2123`.
Example:
```
go run cmd/main.go -session-id=0712345678
```
2. `-d`:
Enables engine debug output.
Default: `false`.
Example:
```
go run cmd/main.go -session-id=0712345678 -d
```
3. `-db`:
Specifies the database type.
Default: `gdbm`.
Example:
```
go run cmd/main.go -session-id=0712345678 -d -db=postgres
```
>Note: If using `-db=postgres`, ensure PostgreSQL is running with the connection details specified in your `.env` file.
## License ## License

View File

@@ -1,9 +1,13 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io"
"log"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@@ -19,18 +23,34 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
logg = logging.NewVanilla() logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
InfoLogger *log.Logger
ErrorLogger *log.Logger
) )
func init() { func init() {
initializers.LoadEnvVariables() initializers.LoadEnvVariables()
logFile := "urdt-ussd-africastalking.log"
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
// Inject into remote package
remote.InfoLogger = InfoLogger
remote.ErrorLogger = ErrorLogger
} }
type atRequestParser struct{} type atRequestParser struct{}
@@ -38,9 +58,30 @@ type atRequestParser struct{}
func (arp *atRequestParser) GetSessionId(rq any) (string, error) { func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request) rqv, ok := rq.(*http.Request)
if !ok { if !ok {
ErrorLogger.Println("got an invalid request:", rq)
return "", handlers.ErrInvalidRequest return "", handlers.ErrInvalidRequest
} }
// Capture body (if any) for logging
body, err := io.ReadAll(rqv.Body)
if err != nil {
ErrorLogger.Println("failed to read request body:", err)
return "", fmt.Errorf("failed to read request body: %v", err)
}
// Reset the body for further reading
rqv.Body = io.NopCloser(bytes.NewReader(body))
// Log the body as JSON
bodyLog := map[string]string{"body": string(body)}
logBytes, err := json.Marshal(bodyLog)
if err != nil {
ErrorLogger.Println("failed to marshal request body:", err)
} else {
InfoLogger.Println("Received request:", string(logBytes))
}
if err := rqv.ParseForm(); err != nil { if err := rqv.ParseForm(); err != nil {
ErrorLogger.Println("failed to parse form data: %v", err)
return "", fmt.Errorf("failed to parse form data: %v", err) return "", fmt.Errorf("failed to parse form data: %v", err)
} }
@@ -131,7 +172,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {
@@ -139,7 +180,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
@@ -156,9 +197,13 @@ func main() {
rp := &atRequestParser{} rp := &atRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.NewATSessionHandler(bsh) sh := httpserver.NewATSessionHandler(bsh)
mux := http.NewServeMux()
mux.Handle(initializers.GetEnv("AT_ENDPOINT", "/"), sh)
s := &http.Server{ s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh, Handler: mux,
} }
s.RegisterOnShutdown(sh.Shutdown) s.RegisterOnShutdown(sh.Shutdown)

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log"
"os" "os"
"os/signal" "os/signal"
"path" "path"
@@ -16,19 +17,34 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "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/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
logg = logging.NewVanilla() logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
InfoLogger *log.Logger
ErrorLogger *log.Logger
) )
func init() { func init() {
initializers.LoadEnvVariables() initializers.LoadEnvVariables()
}
logFile := "urdt-ussd-async.log"
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
// Inject into remote package
remote.InfoLogger = InfoLogger
remote.ErrorLogger = ErrorLogger
}
type asyncRequestParser struct { type asyncRequestParser struct {
sessionId string sessionId string
input []byte input []byte
@@ -104,9 +120,9 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@@ -18,18 +19,34 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
logg = logging.NewVanilla() logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
InfoLogger *log.Logger
ErrorLogger *log.Logger
) )
func init() { func init() {
initializers.LoadEnvVariables() initializers.LoadEnvVariables()
logFile := "urdt-ussd-http.log"
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
// Inject into remote package
remote.InfoLogger = InfoLogger
remote.ErrorLogger = ErrorLogger
} }
func main() { func main() {
@@ -92,14 +109,15 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{}
accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"log"
"os" "os"
"path" "path"
@@ -13,17 +14,33 @@ import (
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "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/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
logg = logging.NewVanilla() logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
InfoLogger *log.Logger
ErrorLogger *log.Logger
) )
func init() { func init() {
initializers.LoadEnvVariables() initializers.LoadEnvVariables()
logFile := "urdt-ussd-cli.log"
file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
InfoLogger = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
ErrorLogger = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
// Inject into remote package
remote.InfoLogger = InfoLogger
remote.ErrorLogger = ErrorLogger
} }
func main() { func main() {
@@ -88,7 +105,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdatastore) lhs.SetDataStore(&userdatastore)
lhs.SetPersister(pe) lhs.SetPersister(pe)
@@ -97,7 +114,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@@ -1,7 +1,9 @@
package utils package common
import ( import (
"encoding/binary" "encoding/binary"
"git.defalsify.org/vise.git/logging"
) )
type DataTyp uint16 type DataTyp uint16
@@ -23,13 +25,17 @@ const (
DATA_RECIPIENT DATA_RECIPIENT
DATA_AMOUNT DATA_AMOUNT
DATA_TEMPORARY_VALUE DATA_TEMPORARY_VALUE
DATA_VOUCHER_LIST
DATA_ACTIVE_SYM DATA_ACTIVE_SYM
DATA_ACTIVE_BAL DATA_ACTIVE_BAL
DATA_BLOCKED_NUMBER
DATA_PUBLIC_KEY_REVERSE DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS DATA_ACTIVE_ADDRESS
DATA_TRANSACTIONS
)
var (
logg = logging.NewVanilla().WithDomain("urdt-common")
) )
func typToBytes(typ DataTyp) []byte { func typToBytes(typ DataTyp) []byte {

View File

@@ -2,6 +2,7 @@ package common
import ( import (
"encoding/hex" "encoding/hex"
"strings"
) )
func NormalizeHex(s string) (string, error) { func NormalizeHex(s string) (string, error) {
@@ -16,3 +17,15 @@ func NormalizeHex(s string) (string, error) {
} }
return hex.EncodeToString(r), nil return hex.EncodeToString(r), nil
} }
func IsSameHex(left string, right string) bool {
bl, err := NormalizeHex(left)
if err != nil {
return false
}
br, err := NormalizeHex(left)
if err != nil {
return false
}
return strings.Compare(bl, br) == 0
}

52
common/storage.go Normal file
View File

@@ -0,0 +1,52 @@
package common
import (
"context"
"errors"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
"git.grassecon.net/urdt/ussd/internal/storage"
)
func StoreToDb(store *UserDataStore) db.Db {
return store.Db
}
func StoreToPrefixDb(store *UserDataStore, pfx []byte) storage.PrefixDb {
return storage.NewSubPrefixDb(store.Db, pfx)
}
type StorageServices interface {
GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) (db.Db, error)
GetResource(ctx context.Context) (resource.Resource, error)
EnsureDbDir() error
}
type StorageService struct {
svc *storage.MenuStorageService
}
func NewStorageService(dbDir string) *StorageService {
return &StorageService{
svc: storage.NewMenuStorageService(dbDir, ""),
}
}
func(ss *StorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
return ss.svc.GetPersister(ctx)
}
func(ss *StorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
return ss.svc.GetUserdataDb(ctx)
}
func(ss *StorageService) GetResource(ctx context.Context) (resource.Resource, error) {
return nil, errors.New("not implemented")
}
func(ss *StorageService) EnsureDbDir() error {
return ss.svc.EnsureDbDir()
}

View File

@@ -1,4 +1,4 @@
package utils package common
import ( import (
"context" "context"
@@ -16,7 +16,7 @@ type UserDataStore struct {
db.Db db.Db
} }
// ReadEntry retrieves an entry from the store based on the provided parameters. // ReadEntry retrieves an entry to the userdata store.
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) { func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId) store.SetSession(sessionId)
@@ -24,6 +24,8 @@ func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ
return store.Get(ctx, k) return store.Get(ctx, k)
} }
// WriteEntry adds an entry to the userdata store.
// BUG: this uses sessionId twice
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error { func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId) store.SetSession(sessionId)

View File

@@ -1,4 +1,4 @@
package utils package common
import ( import (
"context" "context"
@@ -37,6 +37,15 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
return data return data
} }
//func StoreVouchers(db storage.PrefixDb, data VoucherMetadata) {
// value, err := db.Put(ctx, []byte(key))
// if err != nil {
// return nil, fmt.Errorf("failed to get %s: %v", key, err)
// }
// data[key] = string(value)
// }
//}
// GetVoucherData retrieves and matches voucher data // GetVoucherData retrieves and matches voucher data
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
keys := []string{"sym", "bal", "deci", "addr"} keys := []string{"sym", "bal", "deci", "addr"}
@@ -75,6 +84,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol,
decList := strings.Split(decimals, "\n") decList := strings.Split(decimals, "\n")
addrList := strings.Split(addresses, "\n") addrList := strings.Split(addresses, "\n")
logg.Tracef("found" , "symlist", symList, "syms", symbols, "input", input)
for i, sym := range symList { for i, sym := range symList {
parts := strings.SplitN(sym, ":", 2) parts := strings.SplitN(sym, ":", 2)
@@ -125,8 +135,9 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str
return data, nil return data, nil
} }
// UpdateVoucherData sets the active voucher data in the DataStore. // UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the DataStore.
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
logg.TraceCtxf(ctx, "dtal", "data", data)
// Active voucher data entries // Active voucher data entries
activeEntries := map[DataTyp][]byte{ activeEntries := map[DataTyp][]byte{
DATA_ACTIVE_SYM: []byte(data.TokenSymbol), DATA_ACTIVE_SYM: []byte(data.TokenSymbol),

View File

@@ -1,14 +1,14 @@
package utils package common
import ( import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"git.grassecon.net/urdt/ussd/internal/storage"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"git.grassecon.net/urdt/ussd/internal/storage"
memdb "git.defalsify.org/vise.git/db/mem" memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
@@ -132,7 +132,6 @@ func TestStoreTemporaryVoucher(t *testing.T) {
storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE) storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE) require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE)
} }
func TestGetTemporaryVoucherData(t *testing.T) { func TestGetTemporaryVoucherData(t *testing.T) {

View File

@@ -1,18 +1,70 @@
package config package config
import "git.grassecon.net/urdt/ussd/initializers" import (
"net/url"
"git.grassecon.net/urdt/ussd/initializers"
)
const (
createAccountPath = "/api/v2/account/create"
trackStatusPath = "/api/track"
balancePathPrefix = "/api/account"
trackPath = "/api/v2/account/status"
voucherHoldingsPathPrefix = "/api/v1/holdings"
voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token"
)
var (
custodialURLBase string
dataURLBase string
CustodialAPIKey string
DataAPIKey string
)
var ( var (
CreateAccountURL string CreateAccountURL string
TrackStatusURL string TrackStatusURL string
BalanceURL string BalanceURL string
TrackURL string TrackURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
) )
// LoadConfig initializes the configuration values after environment variables are loaded. func setBase() error {
func LoadConfig() { var err error
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/") custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/") dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status") CustodialAPIKey = initializers.GetEnv("CUSTODIAL_API_KEY", "xd")
DataAPIKey = initializers.GetEnv("DATA_API_KEY", "xd")
_, err = url.JoinPath(custodialURLBase, "/foo")
if err != nil {
return err
}
_, err = url.JoinPath(dataURLBase, "/bar")
if err != nil {
return err
}
return nil
}
// LoadConfig initializes the configuration values after environment variables are loaded.
func LoadConfig() error {
err := setBase()
if err != nil {
return err
}
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath)
BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix)
TrackURL, _ = url.JoinPath(custodialURLBase, trackPath)
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
return nil
} }

View File

@@ -0,0 +1,7 @@
{
"admins": [
{
"phonenumber" : "<replace with any admin number to test with >"
}
]
}

View File

@@ -0,0 +1,47 @@
package commands
import (
"context"
"encoding/json"
"os"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/utils"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type Admin struct {
PhoneNumber string `json:"phonenumber"`
}
type Config struct {
Admins []Admin `json:"admins"`
}
func Seed(ctx context.Context) error {
var config Config
adminstore, err := utils.NewAdminStore(ctx, "../admin_numbers")
store := adminstore.FsStore
if err != nil {
return err
}
defer store.Close()
data, err := os.ReadFile("admin_numbers.json")
if err != nil {
return err
}
if err := json.Unmarshal(data, &config); err != nil {
return err
}
for _, admin := range config.Admins {
err := store.Put(ctx, []byte(admin.PhoneNumber), []byte("1"))
if err != nil {
logg.Printf(logging.LVL_DEBUG, "Failed to insert admin number", admin.PhoneNumber)
return err
}
}
return nil
}

17
devtools/admin/main.go Normal file
View File

@@ -0,0 +1,17 @@
package main
import (
"context"
"log"
"git.grassecon.net/urdt/ussd/devtools/admin/commands"
)
func main() {
ctx := context.Background()
err := commands.Seed(ctx)
if err != nil {
log.Fatalf("Failed to initialize a list of admins with error %s", err)
}
}

View File

@@ -1,13 +1,17 @@
package handlers package handlers
import ( import (
"context"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/remote"
) )
type HandlerService interface { type HandlerService interface {
@@ -28,20 +32,26 @@ type LocalHandlerService struct {
DbRs *resource.DbResource DbRs *resource.DbResource
Pe *persist.Persister Pe *persist.Persister
UserdataStore *db.Db UserdataStore *db.Db
AdminStore *utils.AdminStore
Cfg engine.Config Cfg engine.Config
Rs resource.Resource Rs resource.Resource
} }
func NewLocalHandlerService(fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) { func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
parser, err := getParser(fp, debug) parser, err := getParser(fp, debug)
if err != nil { if err != nil {
return nil, err return nil, err
} }
adminstore, err := utils.NewAdminStore(ctx, "admin_numbers")
if err != nil {
return nil, err
}
return &LocalHandlerService{ return &LocalHandlerService{
Parser: parser, Parser: parser,
DbRs: dbResource, DbRs: dbResource,
Cfg: cfg, AdminStore: adminstore,
Rs: rs, Cfg: cfg,
Rs: rs,
}, nil }, nil
} }
@@ -53,8 +63,8 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
ls.UserdataStore = db ls.UserdataStore = db
} }
func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) { func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore,accountService) ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -98,6 +108,13 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin)
ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch)
ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber)
ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber)
ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber)
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
return ussdHandlers, nil return ussdHandlers, nil
} }

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
}

View File

@@ -20,8 +20,8 @@ import (
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/common" "git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/remote"
"gopkg.in/leonelquinteros/gotext.v1" "gopkg.in/leonelquinteros/gotext.v1"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
@@ -35,6 +35,12 @@ var (
errResponse *api.ErrResponse errResponse *api.ErrResponse
) )
// Define the regex patterns as constants
const (
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
pinPattern = `^\d{4}$`
)
// FlagManager handles centralized flag management // FlagManager handles centralized flag management
type FlagManager struct { type FlagManager struct {
parser *asm.FlagParser parser *asm.FlagParser
@@ -62,17 +68,18 @@ type Handlers struct {
pe *persist.Persister pe *persist.Persister
st *state.State st *state.State
ca cache.Memory ca cache.Memory
userdataStore utils.DataStore userdataStore common.DataStore
adminstore *utils.AdminStore
flagManager *asm.FlagParser flagManager *asm.FlagParser
accountService server.AccountServiceInterface accountService remote.AccountServiceInterface
prefixDb storage.PrefixDb prefixDb storage.PrefixDb
} }
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) { func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) {
if userdataStore == nil { if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store") return nil, fmt.Errorf("cannot create handler with nil userdata store")
} }
userDb := &utils.UserDataStore{ userDb := &common.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
// Instantiate the SubPrefixDb with "vouchers" prefix // Instantiate the SubPrefixDb with "vouchers" prefix
@@ -81,21 +88,24 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService s
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
flagManager: appFlags, flagManager: appFlags,
adminstore: adminstore,
accountService: accountService, accountService: accountService,
prefixDb: prefixDb, prefixDb: prefixDb,
} }
return h, nil return h, nil
} }
// Define the regex pattern as a constant
const pinPattern = `^\d{4}$`
// isValidPIN checks whether the given input is a 4 digit number // isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool { func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin) match, _ := regexp.MatchString(pinPattern, pin)
return match return match
} }
func isValidPhoneNumber(phonenumber string) bool {
match, _ := regexp.MatchString(phoneRegex, phonenumber)
return match
}
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
if h.pe != nil { if h.pe != nil {
panic("persister already set") panic("persister already set")
@@ -106,13 +116,25 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var r resource.Result var r resource.Result
if h.pe == nil { if h.pe == nil {
logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca) logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
return r, nil return r, nil
} }
h.st = h.pe.GetState() h.st = h.pe.GetState()
h.ca = h.pe.GetMemory() h.ca = h.pe.GetMemory()
sessionId, _ := ctx.Value("SessionId").(string)
flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege")
isAdmin, _ := h.adminstore.IsAdmin(sessionId)
if isAdmin {
r.FlagSet = append(r.FlagSet, flag_admin_privilege)
} else {
r.FlagReset = append(r.FlagReset, flag_admin_privilege)
}
if h.st == nil || h.ca == nil { if h.st == nil || h.ca == nil {
logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca) logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
return r, fmt.Errorf("cannot get state and memory for handler") return r, fmt.Errorf("cannot get state and memory for handler")
@@ -148,16 +170,16 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
okResponse, err := h.accountService.CreateAccount(ctx) r, err := h.accountService.CreateAccount(ctx)
if err != nil { if err != nil {
return err return err
} }
trackingId := okResponse.Result["trackingId"].(string) trackingId := r.TrackingId
publicKey := okResponse.Result["publicKey"].(string) publicKey := r.PublicKey
data := map[utils.DataTyp]string{ data := map[common.DataTyp]string{
utils.DATA_TRACKING_ID: trackingId, common.DATA_TRACKING_ID: trackingId,
utils.DATA_PUBLIC_KEY: publicKey, common.DATA_PUBLIC_KEY: publicKey,
} }
store := h.userdataStore store := h.userdataStore
for key, value := range data { for key, value := range data {
@@ -170,7 +192,7 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
if err != nil { if err != nil {
return err return err
} }
err = store.WriteEntry(ctx, publicKeyNormalized, utils.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) err = store.WriteEntry(ctx, publicKeyNormalized, common.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
if err != nil { if err != nil {
return err return err
} }
@@ -190,7 +212,7 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
_, err = store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_CREATED) _, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist") logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist")
@@ -203,6 +225,30 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
if bytes.Equal(temporaryPin, input) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
return res, nil
}
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{} res := resource.Result{}
_, ok := ctx.Value("SessionId").(string) _, ok := ctx.Value("SessionId").(string)
@@ -234,7 +280,6 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
} }
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
accountPIN := string(input) accountPIN := string(input)
// Validate that the PIN is a 4-digit number // Validate that the PIN is a 4-digit number
@@ -242,11 +287,32 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
res.FlagSet = append(res.FlagSet, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
return res, nil return res, nil
} }
res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(accountPIN))
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(accountPIN)) sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
temporaryPin := string(input)
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -263,7 +329,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
store := h.userdataStore store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -272,7 +338,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
} else { } else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch) res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -294,11 +360,10 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil { if err != nil {
return res, err return res, err
} }
if bytes.Equal(input, temporaryPin) { if bytes.Equal(input, temporaryPin) {
res.FlagSet = []uint32{flag_valid_pin} res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch} res.FlagReset = []uint32{flag_pin_mismatch}
@@ -307,7 +372,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
res.FlagSet = []uint32{flag_pin_mismatch} res.FlagSet = []uint32{flag_pin_mismatch}
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -338,13 +403,13 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(temporaryFirstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -361,6 +426,7 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
familyName := string(input) familyName := string(input)
@@ -368,13 +434,13 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -396,13 +462,13 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryYob, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(temporaryYob)) err = store.WriteEntry(ctx, sessionId, common.DATA_YOB, []byte(temporaryYob))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -426,13 +492,13 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(temporaryLocation)) err = store.WriteEntry(ctx, sessionId, common.DATA_LOCATION, []byte(temporaryLocation))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -456,13 +522,13 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryGender, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(temporaryGender)) err = store.WriteEntry(ctx, sessionId, common.DATA_GENDER, []byte(temporaryGender))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(gender)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -479,6 +545,7 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
offerings := string(input) offerings := string(input)
store := h.userdataStore store := h.userdataStore
@@ -486,13 +553,13 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(temporaryOfferings)) err = store.WriteEntry(ctx, sessionId, common.DATA_OFFERINGS, []byte(temporaryOfferings))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -511,6 +578,14 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
return res, nil return res, nil
} }
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
func (h *Handlers) ResetValidPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
res.FlagReset = append(res.FlagReset, flag_valid_pin)
return res, nil
}
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. // ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -528,7 +603,7 @@ func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, _ := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
res.Content = string(publicKey) res.Content = string(publicKey)
@@ -549,7 +624,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
store := h.userdataStore store := h.userdataStore
AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN) AccountPin, err := store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -594,22 +669,23 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey)) r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
if err != nil { if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
return res, err return res, err
} }
res.FlagReset = append(res.FlagReset, flag_api_error) res.FlagReset = append(res.FlagReset, flag_api_error)
isActive := okResponse.Result["active"].(bool)
if !ok { if !ok {
return res, err return res, err
} }
if isActive { if r.Active {
res.FlagSet = append(res.FlagSet, flag_account_success) res.FlagSet = append(res.FlagSet, flag_account_success)
res.FlagReset = append(res.FlagReset, flag_account_pending) res.FlagReset = append(res.FlagReset, flag_account_pending)
} else { } else {
@@ -655,7 +731,6 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
var err error var err error
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
date := string(input) date := string(input)
_, err = strconv.Atoi(date) _, err = strconv.Atoi(date)
if err != nil { if err != nil {
@@ -678,7 +753,6 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by
var res resource.Result var res resource.Result
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
return res, nil return res, nil
} }
@@ -701,7 +775,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
store := h.userdataStore store := h.userdataStore
// get the active sym and active balance // get the active sym and active balance
activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
balance := "0.00" balance := "0.00"
@@ -712,7 +786,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, err return res, err
} }
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -724,6 +798,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
@@ -734,21 +809,19 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
balanceType := strings.Split(symbol, "_")[0] balanceType := strings.Split(symbol, "_")[0]
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey)) balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
if err != nil { if err != nil {
return res, nil
}
if !balanceResponse.Ok {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
return res, nil return res, nil
} }
res.FlagReset = append(res.FlagReset, flag_api_error) res.FlagReset = append(res.FlagReset, flag_api_error)
balance := balanceResponse.Result.Balance
balance := balanceResponse.Balance
switch balanceType { switch balanceType {
case "my": case "my":
@@ -761,6 +834,67 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
return res, nil return res, nil
} }
func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
return res, nil
}
return res, nil
}
func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
return res, nil
}
func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
blockedNumber := string(input)
_, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY)
if !isValidPhoneNumber(blockedNumber) {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
}
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
return res, err
}
}
err = store.WriteEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
if err != nil {
return res, nil
}
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number. // ValidateRecipient validates that the given input is a valid phone number.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -784,7 +918,7 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, nil return res, nil
} }
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(recipient)) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@@ -807,12 +941,12 @@ func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byt
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@@ -834,7 +968,7 @@ func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@@ -856,7 +990,7 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
} }
store := h.userdataStore store := h.userdataStore
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -881,7 +1015,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
var balanceValue float64 var balanceValue float64
// retrieve the active balance // retrieve the active balance
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -907,7 +1041,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
// Format the amount with 2 decimal places before saving // Format the amount with 2 decimal places before saving
formattedAmount := fmt.Sprintf("%.2f", inputAmount) formattedAmount := fmt.Sprintf("%.2f", inputAmount)
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(formattedAmount)) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(formattedAmount))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -925,13 +1059,29 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT) recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT)
res.Content = string(recipient) res.Content = string(recipient)
return res, nil return res, nil
} }
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
func (h *Handlers) RetrieveBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
blockedNumber, _ := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
res.Content = string(blockedNumber)
return res, nil
}
// GetSender returns the sessionId (phoneNumber) // GetSender returns the sessionId (phoneNumber)
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@@ -957,12 +1107,12 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res
store := h.userdataStore store := h.userdataStore
// retrieve the active symbol // retrieve the active symbol
activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
return res, err return res, err
} }
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT) amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT)
res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym)) res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym))
@@ -986,11 +1136,11 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
// Use the amount, recipient and sender to call the API and initialize the transaction // Use the amount, recipient and sender to call the API and initialize the transaction
store := h.userdataStore store := h.userdataStore
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT) amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT)
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT) recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT)
activeSym, _ := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId)) res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId))
@@ -1030,12 +1180,12 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
} }
store := h.userdataStore store := h.userdataStore
// Retrieve user data as strings with fallback to defaultValue // Retrieve user data as strings with fallback to defaultValue
firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FIRST_NAME)) firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME))
familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FAMILY_NAME)) familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME))
yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_YOB)) yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_YOB))
gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_GENDER)) gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_GENDER))
location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_LOCATION)) location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_LOCATION))
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_OFFERINGS)) offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
// Construct the full name // Construct the full name
name := defaultValue name := defaultValue
@@ -1092,11 +1242,11 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher") flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
// check if the user has an active sym // check if the user has an active sym
_, err = store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) _, err = store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -1104,27 +1254,28 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
// Fetch vouchers from the API using the public key // Fetch vouchers from the API using the public key
vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey))
if err != nil { if err != nil {
return res, err res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
return res, nil
} }
// Return if there is no voucher // Return if there is no voucher
if len(vouchersResp.Result.Holdings) == 0 { if len(vouchersResp) == 0 {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher) res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
return res, nil return res, nil
} }
// Use only the first voucher // Use only the first voucher
firstVoucher := vouchersResp.Result.Holdings[0] firstVoucher := vouchersResp[0]
defaultSym := firstVoucher.TokenSymbol defaultSym := firstVoucher.TokenSymbol
defaultBal := firstVoucher.Balance defaultBal := firstVoucher.Balance
// set the active symbol // set the active symbol
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(defaultSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(defaultSym))
if err != nil { if err != nil {
return res, err return res, err
} }
// set the active balance // set the active balance
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(defaultBal)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(defaultBal))
if err != nil { if err != nil {
return res, err return res, err
} }
@@ -1150,7 +1301,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
} }
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, nil return res, nil
} }
@@ -1161,7 +1312,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
data := utils.ProcessVouchers(vouchersResp.Result.Holdings) data := common.ProcessVouchers(vouchersResp)
// Store all voucher data // Store all voucher data
dataMap := map[string]string{ dataMap := map[string]string{
@@ -1211,7 +1362,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, nil return res, nil
} }
metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr) metadata, err := common.GetVoucherData(ctx, h.prefixDb, inputStr)
if err != nil { if err != nil {
return res, fmt.Errorf("failed to retrieve voucher data: %v", err) return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
} }
@@ -1221,7 +1372,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, nil return res, nil
} }
if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { if err := common.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
return res, err return res, err
} }
@@ -1241,13 +1392,13 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re
} }
// Get temporary data // Get temporary data
tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) tempData, err := common.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
if err != nil { if err != nil {
return res, err return res, err
} }
// Set as active and clear temporary data // Set as active and clear temporary data
if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
return res, err return res, err
} }

View File

@@ -12,14 +12,14 @@ import (
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/mocks" "git.grassecon.net/urdt/ussd/internal/testutil/mocks"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/models"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/common"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -33,7 +33,7 @@ var (
) )
// InitializeTestStore sets up and returns an in-memory database and store. // InitializeTestStore sets up and returns an in-memory database and store.
func InitializeTestStore(t *testing.T) (context.Context, *utils.UserDataStore) { func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) {
ctx := context.Background() ctx := context.Background()
// Initialize memDb // Initialize memDb
@@ -42,7 +42,7 @@ func InitializeTestStore(t *testing.T) (context.Context, *utils.UserDataStore) {
require.NoError(t, err, "Failed to connect to memDb") require.NoError(t, err, "Failed to connect to memDb")
// Create UserDataStore with memDb // Create UserDataStore with memDb
store := &utils.UserDataStore{Db: db} store := &common.UserDataStore{Db: db}
t.Cleanup(func() { t.Cleanup(func() {
db.Close() // Ensure the DB is closed after each test db.Close() // Ensure the DB is closed after each test
@@ -71,7 +71,7 @@ func TestNewHandlers(t *testing.T) {
t.Logf(err.Error()) t.Logf(err.Error())
} }
t.Run("Valid UserDataStore", func(t *testing.T) { t.Run("Valid UserDataStore", func(t *testing.T) {
handlers, err := NewHandlers(fm.parser, store, &accountService) handlers, err := NewHandlers(fm.parser, store, nil, &accountService)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
@@ -85,7 +85,7 @@ func TestNewHandlers(t *testing.T) {
// Test case for nil userdataStore // Test case for nil userdataStore
t.Run("Nil UserDataStore", func(t *testing.T) { t.Run("Nil UserDataStore", func(t *testing.T) {
handlers, err := NewHandlers(fm.parser, nil, &accountService) handlers, err := NewHandlers(fm.parser, nil, nil, &accountService)
if err == nil { if err == nil {
t.Fatal("expected an error, got none") t.Fatal("expected an error, got none")
} }
@@ -115,18 +115,14 @@ func TestCreateAccount(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
serverResponse *api.OKResponse serverResponse *models.AccountResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test account creation success", name: "Test account creation success",
serverResponse: &api.OKResponse{ serverResponse: &models.AccountResult{
Ok: true, TrackingId: "1234567890",
Description: "Account creation successed", PublicKey: "0xD3adB33f",
Result: map[string]any{
"trackingId": "1234567890",
"publicKey": "0xD3adB33f",
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_created}, FlagSet: []uint32{flag_account_created},
@@ -180,7 +176,7 @@ func TestSaveFirstname(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -192,7 +188,7 @@ func TestSaveFirstname(t *testing.T) {
// Define test data // Define test data
firstName := "John" firstName := "John"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -211,7 +207,7 @@ func TestSaveFirstname(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
storedFirstName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_FIRST_NAME) storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
assert.Equal(t, firstName, string(storedFirstName)) assert.Equal(t, firstName, string(storedFirstName))
} }
@@ -219,7 +215,7 @@ func TestSaveFamilyname(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -231,7 +227,7 @@ func TestSaveFamilyname(t *testing.T) {
// Define test data // Define test data
familyName := "Doeee" familyName := "Doeee"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -250,7 +246,7 @@ func TestSaveFamilyname(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_FAMILY_NAME) storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
assert.Equal(t, familyName, string(storedFamilyName)) assert.Equal(t, familyName, string(storedFamilyName))
} }
@@ -258,7 +254,7 @@ func TestSaveYoB(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -269,8 +265,8 @@ func TestSaveYoB(t *testing.T) {
// Define test data // Define test data
yob := "1980" yob := "1980"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -289,7 +285,7 @@ func TestSaveYoB(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_YOB entry has been updated with the temporary value // Verify that the DATA_YOB entry has been updated with the temporary value
storedYob, _ := store.ReadEntry(ctx, sessionId, utils.DATA_YOB) storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
assert.Equal(t, yob, string(storedYob)) assert.Equal(t, yob, string(storedYob))
} }
@@ -297,7 +293,7 @@ func TestSaveLocation(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -308,8 +304,8 @@ func TestSaveLocation(t *testing.T) {
// Define test data // Define test data
location := "Kilifi" location := "Kilifi"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -328,7 +324,7 @@ func TestSaveLocation(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_LOCATION entry has been updated with the temporary value // Verify that the DATA_LOCATION entry has been updated with the temporary value
storedLocation, _ := store.ReadEntry(ctx, sessionId, utils.DATA_LOCATION) storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
assert.Equal(t, location, string(storedLocation)) assert.Equal(t, location, string(storedLocation))
} }
@@ -336,7 +332,7 @@ func TestSaveOfferings(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -348,7 +344,7 @@ func TestSaveOfferings(t *testing.T) {
// Define test data // Define test data
offerings := "Bananas" offerings := "Bananas"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -367,7 +363,7 @@ func TestSaveOfferings(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value // Verify that the DATA_OFFERINGS entry has been updated with the temporary value
storedOfferings, _ := store.ReadEntry(ctx, sessionId, utils.DATA_OFFERINGS) storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
assert.Equal(t, offerings, string(storedOfferings)) assert.Equal(t, offerings, string(storedOfferings))
} }
@@ -375,7 +371,7 @@ func TestSaveGender(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@@ -413,7 +409,7 @@ func TestSaveGender(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -433,7 +429,7 @@ func TestSaveGender(t *testing.T) {
assert.Equal(t, resource.Result{}, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_GENDER entry has been updated with the temporary value // Verify that the DATA_GENDER entry has been updated with the temporary value
storedGender, _ := store.ReadEntry(ctx, sessionId, utils.DATA_GENDER) storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
assert.Equal(t, tt.expectedGender, string(storedGender)) assert.Equal(t, tt.expectedGender, string(storedGender))
}) })
} }
@@ -487,7 +483,6 @@ func TestSaveTemporaryPin(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
// Assert that the Result FlagSet has the required flags after language switch // Assert that the Result FlagSet has the required flags after language switch
assert.Equal(t, res, tt.expectedResult, "Result should match expected result") assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
}) })
@@ -518,7 +513,7 @@ func TestCheckIdentifier(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(tt.publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(tt.publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -562,12 +557,12 @@ func TestGetAmount(t *testing.T) {
amount := "0.03" amount := "0.03"
activeSym := "SRF" activeSym := "SRF"
err := store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amount)) err := store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(amount))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(activeSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(activeSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -593,7 +588,7 @@ func TestGetRecipient(t *testing.T) {
recepient := "0xcasgatweksalw1018221" recepient := "0xcasgatweksalw1018221"
err := store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(recepient)) err := store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recepient))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -722,12 +717,12 @@ func TestResetAllowUpdate(t *testing.T) {
func TestResetAccountAuthorized(t *testing.T) { func TestResetAccountAuthorized(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
flag_account_authorized, _ := fm.parser.GetFlag("flag_account_authorized")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
flag_account_authorized, _ := fm.parser.GetFlag("flag_account_authorized")
// Define test cases // Define test cases
tests := []struct { tests := []struct {
name string name string
@@ -745,7 +740,6 @@ func TestResetAccountAuthorized(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Create the Handlers instance with the mock flag manager // Create the Handlers instance with the mock flag manager
h := &Handlers{ h := &Handlers{
flagManager: fm.parser, flagManager: fm.parser,
@@ -904,10 +898,7 @@ func TestAuthorize(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Create context with session ID err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(accountPIN))
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(accountPIN))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1034,7 +1025,7 @@ func TestVerifyCreatePin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte("1234")) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte("1234"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1067,18 +1058,14 @@ func TestCheckAccountStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
publicKey []byte publicKey []byte
serverResponse *api.OKResponse response *models.TrackStatusResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test when account is on the Sarafu network", name: "Test when account is on the Sarafu network",
publicKey: []byte("TrackingId1234"), publicKey: []byte("TrackingId1234"),
serverResponse: &api.OKResponse{ response: &models.TrackStatusResult{
Ok: true, Active: true,
Description: "Account creation succeeded",
Result: map[string]any{
"active": true,
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_success}, FlagSet: []uint32{flag_account_success},
@@ -1088,12 +1075,8 @@ func TestCheckAccountStatus(t *testing.T) {
{ {
name: "Test when the account is not yet on the sarafu network", name: "Test when the account is not yet on the sarafu network",
publicKey: []byte("TrackingId1234"), publicKey: []byte("TrackingId1234"),
serverResponse: &api.OKResponse{ response: &models.TrackStatusResult{
Ok: true, Active: false,
Description: "Account creation succeeded",
Result: map[string]any{
"active": false,
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_pending}, FlagSet: []uint32{flag_account_pending},
@@ -1111,12 +1094,12 @@ func TestCheckAccountStatus(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(tt.publicKey)) err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(tt.publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.serverResponse, nil) mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil)
// Call the method under test // Call the method under test
res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte("")) res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte(""))
@@ -1264,15 +1247,15 @@ func TestInitiateTransaction(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(tt.Amount)) err := store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(tt.Amount))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(tt.Recipient)) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(tt.Recipient))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(tt.ActiveSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(tt.ActiveSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1446,7 +1429,7 @@ func TestValidateAmount(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(tt.activeBal)) err := store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(tt.activeBal))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1550,11 +1533,11 @@ func TestCheckBalance(t *testing.T) {
accountService: mockAccountService, accountService: mockAccountService,
} }
err := store.WriteEntry(ctx, tt.sessionId, utils.DATA_ACTIVE_SYM, []byte(tt.activeSym)) err := store.WriteEntry(ctx, tt.sessionId, common.DATA_ACTIVE_SYM, []byte(tt.activeSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, tt.sessionId, utils.DATA_ACTIVE_BAL, []byte(tt.activeBal)) err = store.WriteEntry(ctx, tt.sessionId, common.DATA_ACTIVE_BAL, []byte(tt.activeBal))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1589,13 +1572,13 @@ func TestGetProfile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
languageCode string languageCode string
keys []utils.DataTyp keys []common.DataTyp
profileInfo []string profileInfo []string
result resource.Result result resource.Result
}{ }{
{ {
name: "Test with full profile information in eng", name: "Test with full profile information in eng",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "eng", languageCode: "eng",
result: resource.Result{ result: resource.Result{
@@ -1607,7 +1590,7 @@ func TestGetProfile(t *testing.T) {
}, },
{ {
name: "Test with with profile information in swa", name: "Test with with profile information in swa",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "swa", languageCode: "swa",
result: resource.Result{ result: resource.Result{
@@ -1619,7 +1602,7 @@ func TestGetProfile(t *testing.T) {
}, },
{ {
name: "Test with with profile information with language that is not yet supported", name: "Test with with profile information with language that is not yet supported",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "nor", languageCode: "nor",
result: resource.Result{ result: resource.Result{
@@ -1728,7 +1711,7 @@ func TestConfirmPin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.temporarypin)) err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(tt.temporarypin))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -1757,43 +1740,21 @@ func TestFetchCustodialBalances(t *testing.T) {
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
err = store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tests := []struct { tests := []struct {
name string name string
balanceResonse *models.BalanceResponse balanceResponse *models.BalanceResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test when fetch custodial balances is not a success", name: "Test when fetch custodial balances is not a success",
balanceResonse: &models.BalanceResponse{ balanceResponse: &models.BalanceResult{
Ok: false, Balance: "0.003 CELO",
Result: struct { Nonce: json.Number("0"),
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_api_error},
},
},
{
name: "Test when fetch custodial balances is a success",
balanceResonse: &models.BalanceResponse{
Ok: true,
Result: struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagReset: []uint32{flag_api_error}, FlagReset: []uint32{flag_api_error},
@@ -1813,7 +1774,7 @@ func TestFetchCustodialBalances(t *testing.T) {
} }
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
mockAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResonse, nil) mockAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResponse, nil)
// Call the method // Call the method
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte("")) res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
@@ -1833,28 +1794,33 @@ func TestSetDefaultVoucher(t *testing.T) {
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
if err != nil {
t.Logf(err.Error())
}
publicKey := "0X13242618721" publicKey := "0X13242618721"
tests := []struct { tests := []struct {
name string name string
vouchersResp *models.VoucherHoldingResponse vouchersResp []dataserviceapi.TokenHoldings
expectedResult resource.Result expectedResult resource.Result
}{ }{
{
name: "Test no vouchers available",
vouchersResp: []dataserviceapi.TokenHoldings{},
expectedResult: resource.Result{
FlagSet: []uint32{flag_no_active_voucher},
},
},
{ {
name: "Test set default voucher when no active voucher is set", name: "Test set default voucher when no active voucher is set",
vouchersResp: &models.VoucherHoldingResponse{ vouchersResp: []dataserviceapi.TokenHoldings{
Ok: true, dataserviceapi.TokenHoldings{
Description: "Vouchers fetched successfully", ContractAddress: "0x123",
Result: models.VoucherResult{ TokenSymbol: "TOKEN1",
Holdings: []dataserviceapi.TokenHoldings{ TokenDecimals: "18",
{ Balance: "100",
ContractAddress: "0x123",
TokenSymbol: "TOKEN1",
TokenDecimals: "18",
Balance: "100",
},
},
}, },
}, },
expectedResult: resource.Result{}, expectedResult: resource.Result{},
@@ -1871,14 +1837,14 @@ func TestSetDefaultVoucher(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("")) res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input"))
assert.NoError(t, err) assert.NoError(t, err)
@@ -1904,13 +1870,12 @@ func TestCheckVouchers(t *testing.T) {
prefixDb: spdb, prefixDb: spdb,
} }
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockVouchersResponse := &models.VoucherHoldingResponse{} mockVouchersResponse := []dataserviceapi.TokenHoldings{
mockVouchersResponse.Result.Holdings = []dataserviceapi.TokenHoldings{
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
} }
@@ -2018,7 +1983,7 @@ func TestSetVoucher(t *testing.T) {
expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.ContractAddress) expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.ContractAddress)
// store the expectedData // store the expectedData
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil {
t.Fatal(err) t.Fatal(err)
} }

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,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

@@ -11,11 +11,11 @@ import (
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers" "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/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/internal/testutil/testtag" "git.grassecon.net/urdt/ussd/internal/testutil/testtag"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@@ -73,7 +73,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userDataStore) lhs.SetDataStore(&userDataStore)
lhs.SetPersister(pe) lhs.SetPersister(pe)
@@ -83,7 +83,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
} }
if testtag.AccountService == nil { if testtag.AccountService == nil {
testtag.AccountService = &server.AccountService{} testtag.AccountService = &remote.AccountService{}
} }
switch testtag.AccountService.(type) { switch testtag.AccountService.(type) {
@@ -91,7 +91,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
go func() { go func() {
eventChannel <- false eventChannel <- false
}() }()
case *server.AccountService: case *remote.AccountService:
go func() { go func() {
time.Sleep(5 * time.Second) // Wait for 5 seconds time.Sleep(5 * time.Second) // Wait for 5 seconds
eventChannel <- true eventChannel <- true

View File

@@ -3,8 +3,8 @@ package mocks
import ( import (
"context" "context"
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@@ -13,28 +13,33 @@ type MockAccountService struct {
mock.Mock mock.Mock
} }
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) { func (m *MockAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
args := m.Called() args := m.Called()
return args.Get(0).(*api.OKResponse), args.Error(1) return args.Get(0).(*models.AccountResult), args.Error(1)
} }
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) { func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*models.BalanceResponse), args.Error(1) return args.Get(0).(*models.BalanceResult), args.Error(1)
} }
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) { func (m *MockAccountService) TrackAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResult, error) {
args := m.Called(trackingId) args := m.Called(trackingId)
return args.Get(0).(*models.TrackStatusResponse), args.Error(1) return args.Get(0).(*models.TrackStatusResult), args.Error(1)
} }
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*api.OKResponse), args.Error(1) return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
} }
func (m *MockAccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1) return args.Get(0).([]dataserviceapi.Last10TxResponse), args.Error(1)
}
func(m MockAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
args := m.Called(address)
return args.Get(0).(*models.VoucherDataResult), args.Error(1)
} }

View File

@@ -3,88 +3,50 @@ package testservice
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"time"
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
type TestAccountService struct { type TestAccountService struct {
} }
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) { func (tas *TestAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
return &api.OKResponse{ return &models.AccountResult {
Ok: true, TrackingId: "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
Description: "Account creation succeeded", PublicKey: "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
Result: map[string]any{
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
},
}, nil }, nil
} }
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) { func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
balanceResponse := &models.BalanceResponse{ balanceResponse := &models.BalanceResult {
Ok: true, Balance: "0.003 CELO",
Result: struct { Nonce: json.Number("0"),
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
} }
return balanceResponse, nil return balanceResponse, nil
} }
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) { func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) {
trackResponse := &models.TrackStatusResponse{ return &models.TrackStatusResult {
Ok: true, Active: 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 }, nil
} }
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) { func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return &models.VoucherHoldingResponse{ return []dataserviceapi.TokenHoldings {
Ok: true, dataserviceapi.TokenHoldings {
Result: models.VoucherResult{ ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
Holdings: []dataserviceapi.TokenHoldings{ TokenSymbol: "SRF",
{ TokenDecimals: "6",
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", Balance: "2745987",
TokenSymbol: "SRF",
TokenDecimals: "6",
Balance: "2745987",
},
},
}, },
}, nil }, nil
}
func (tas *TestAccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
return []dataserviceapi.Last10TxResponse{}, nil
}
func(m TestAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
return &models.VoucherDataResult{}, nil
} }

View File

@@ -3,10 +3,10 @@
package testtag package testtag
import ( import (
"git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/remote"
accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice" accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
) )
var ( var (
AccountService server.AccountServiceInterface = &accountservice.TestAccountService{} AccountService remote.AccountServiceInterface = &accountservice.TestAccountService{}
) )

View File

@@ -0,0 +1,51 @@
package utils
import (
"context"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
"git.defalsify.org/vise.git/logging"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type AdminStore struct {
ctx context.Context
FsStore db.Db
}
func NewAdminStore(ctx context.Context, fileName string) (*AdminStore, error) {
fsStore, err := getFsStore(ctx, fileName)
if err != nil {
return nil, err
}
return &AdminStore{ctx: ctx, FsStore: fsStore}, nil
}
func getFsStore(ctx context.Context, connectStr string) (db.Db, error) {
fsStore := fsdb.NewFsDb()
err := fsStore.Connect(ctx, connectStr)
fsStore.SetPrefix(db.DATATYPE_USERDATA)
if err != nil {
return nil, err
}
return fsStore, nil
}
// Checks if the given sessionId is listed as an admin.
func (as *AdminStore) IsAdmin(sessionId string) (bool, error) {
_, err := as.FsStore.Get(as.ctx, []byte(sessionId))
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Returning false because session id was not found")
return false, nil
} else {
return false, err
}
}
return true, nil
}

View File

@@ -13,7 +13,7 @@
}, },
{ {
"input": "5", "input": "5",
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n3:Guard my PIN\n0:Back" "expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
}, },
{ {
"input": "1", "input": "1",
@@ -54,7 +54,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -95,7 +95,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -141,7 +141,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",

View File

@@ -23,7 +23,7 @@
}, },
{ {
"input": "1111", "input": "1111",
"expectedContent": "The PIN is not a match. Try again\n1:retry\n9:Quit" "expectedContent": "The PIN is not a match. Try again\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -65,7 +65,7 @@
}, },
{ {
"input": "000", "input": "000",
"expectedContent": "000 is not registered or invalid, please try again:\n1:retry\n9:Quit" "expectedContent": "000 is not registered or invalid, please try again:\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -77,7 +77,7 @@
}, },
{ {
"input": "10000000", "input": "10000000",
"expectedContent": "Amount 10000000 is invalid, please try again:\n1:retry\n9:Quit" "expectedContent": "Amount 10000000 is invalid, please try again:\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -89,7 +89,7 @@
}, },
{ {
"input": "1222", "input": "1222",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@@ -140,7 +140,7 @@
}, },
{ {
"input": "6", "input": "6",
"expectedContent": "Address: {public_key}\n9:Quit" "expectedContent": "Address: {public_key}\n0:Back\n9:Quit"
}, },
{ {
"input": "9", "input": "9",

View File

@@ -0,0 +1,6 @@
package models
type AccountResult struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}

View File

@@ -0,0 +1,9 @@
package models
import "encoding/json"
type BalanceResult struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}

View File

@@ -0,0 +1,18 @@
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 TrackStatusResult struct {
Active bool `json:"active"`
}

View File

@@ -0,0 +1,21 @@
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"`
}
type VoucherDataResult struct {
TokenName string `json:"tokenName"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
SinkAddress string `json:"sinkAddress"`
}

243
remote/accountservice.go Normal file
View File

@@ -0,0 +1,243 @@
package remote
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"net/url"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
var (
InfoLogger *log.Logger
ErrorLogger *log.Logger
)
type AccountServiceInterface interface {
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error)
CreateAccount(ctx context.Context) (*models.AccountResult, error)
TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error)
FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error)
FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error)
VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, 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) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) {
var r models.TrackStatusResult
ep, err := url.JoinPath(config.TrackURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, 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.BalanceResult, error) {
var balanceResult models.BalanceResult
ep, err := url.JoinPath(config.BalanceURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &balanceResult)
return &balanceResult, err
}
// 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) (*models.AccountResult, error) {
var r models.AccountResult
// Create a new request
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
log.Printf("Failed to make custodial %s request to endpoint: %s with reason: %s", req.Method, req.URL, err.Error())
return nil, err
}
return &r, nil
}
// FetchVouchers retrieves the token holdings for a given public key from the data indexer API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
var r []dataserviceapi.TokenHoldings
ep, err := url.JoinPath(config.VoucherHoldingsURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// FetchTransactions retrieves the last 10 transactions for a given public key from the data indexer API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
var r []dataserviceapi.Last10TxResponse
ep, err := url.JoinPath(config.VoucherTransfersURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// VoucherData retrieves voucher metadata from the data indexer API endpoint.
// Parameters:
// - address: The voucher address.
func (as *AccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
var voucherDataResult models.VoucherDataResult
ep, err := url.JoinPath(config.VoucherDataURL, address)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &voucherDataResult)
return &voucherDataResult, err
}
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse
var errResponse api.ErrResponse
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
defer resp.Body.Close()
InfoLogger.Printf("Received response for %s: Status Code: %d | Content-Type: %s", req.URL, resp.StatusCode, resp.Header.Get("Content-Type"))
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")
}
v, err := json.Marshal(okResponse.Result)
if err != nil {
return nil, err
}
err = json.Unmarshal(v, &rcpt)
return &okResponse, err
}
func doCustodialRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.CustodialAPIKey)
logRequestDetails(req)
return doRequest(ctx, req, rcpt)
}
func doDataRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.DataAPIKey)
logRequestDetails(req)
return doRequest(ctx, req, rcpt)
}
func logRequestDetails(req *http.Request) {
var bodyBytes []byte
contentType := req.Header.Get("Content-Type")
if req.Body != nil {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
ErrorLogger.Printf("Error reading request body: %s", err)
return
}
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
} else {
bodyBytes = []byte("-")
}
InfoLogger.Printf("URL: %s | Content-Type: %s | Method: %s| Request Body: %s", req.URL, contentType, req.Method, string(bodyBytes))
}

View File

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

View File

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

View File

@@ -0,0 +1 @@
Please confirm new PIN for:{{.retrieve_blocked_number}}

View File

@@ -0,0 +1,14 @@
CATCH pin_entry flag_incorrect_pin 1
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
CATCH invalid_others_pin flag_valid_pin 0
CATCH pin_reset_result flag_account_authorized 1
LOAD save_others_temporary_pin 6
RELOAD save_others_temporary_pin
MOUT back 0
HALT
INCMP _ 0
LOAD check_pin_mismatch 0
RELOAD check_pin_mismatch
CATCH others_pin_mismatch flag_pin_mismatch 1
INCMP pin_entry *

View File

@@ -0,0 +1 @@
Tafadhali thibitisha PIN mpya ya: {{.retrieve_blocked_number}}

View File

@@ -3,5 +3,3 @@ MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
INCMP * pin_reset_success INCMP * pin_reset_success

View File

@@ -0,0 +1 @@
Enter other's phone number:

View File

@@ -0,0 +1,7 @@
CATCH no_admin_privilege flag_admin_privilege 0
LOAD reset_account_authorized 0
RELOAD reset_account_authorized
MOUT back 0
HALT
INCMP _ 0
INCMP enter_others_new_pin *

View File

@@ -0,0 +1 @@
Weka nambari ya simu ili kutuma ombi la kubadilisha nambari ya siri:

View File

@@ -0,0 +1 @@
Please enter new PIN for: {{.retrieve_blocked_number}}

View File

@@ -0,0 +1,12 @@
LOAD validate_blocked_number 6
RELOAD validate_blocked_number
CATCH unregistered_number flag_unregistered_number 1
LOAD retrieve_blocked_number 0
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
MOUT back 0
HALT
LOAD verify_new_pin 6
RELOAD verify_new_pin
INCMP _ 0
INCMP * confirm_others_new_pin

View File

@@ -0,0 +1 @@
Tafadhali weka PIN mpya ya: {{.retrieve_blocked_number}}

View File

@@ -1 +0,0 @@
Guard my PIN

View File

@@ -1 +0,0 @@
Linda PIN yangu

View File

@@ -0,0 +1 @@
The PIN you have entered is invalid.Please try a 4 digit number instead.

View File

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

View File

@@ -0,0 +1 @@
You do not have privileges to perform this action

View File

@@ -0,0 +1,5 @@
MOUT quit 9
MOUT back 0
HALT
INCMP pin_management 0
INCMP quit 9

View File

@@ -0,0 +1 @@
Huna mapendeleo ya kufanya kitendo hiki

View File

@@ -1 +1 @@
You need a voucher to send You need a voucher to proceed

View File

@@ -1 +1 @@
Unahitaji sarafu kutuma Unahitaji sarafu kuendelea

View File

@@ -0,0 +1 @@
The PIN you have entered is not a match

View File

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

View File

@@ -0,0 +1 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@@ -1,8 +1,8 @@
MOUT change_pin 1 MOUT change_pin 1
MOUT reset_pin 2 MOUT reset_pin 2
MOUT guard_pin 3
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP my_account 0
INCMP old_pin 1 INCMP old_pin 1
INCMP enter_other_number 2
INCMP . *

View File

@@ -0,0 +1 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@@ -0,0 +1 @@
PIN reset request for {{.retrieve_blocked_number}} was successful

View File

@@ -0,0 +1,8 @@
LOAD retrieve_blocked_number 0
MAP retrieve_blocked_number
LOAD reset_others_pin 6
MOUT back 0
MOUT quit 9
HALT
INCMP pin_management 0
INCMP quit 9

View File

@@ -0,0 +1 @@
Ombi la kuweka upya PIN ya {{.retrieve_blocked_number}} limefanikiwa

View File

@@ -6,5 +6,3 @@ MOUT quit 9
HALT HALT
INCMP main 0 INCMP main 0
INCMP quit 9 INCMP quit 9

View File

@@ -17,3 +17,5 @@ flag,flag_incorrect_date_format,23,this is set when the given year of birth is i
flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid
flag,flag_api_call_error,25,this is set when communication to an external service fails flag,flag_api_call_error,25,this is set when communication to an external service fails
flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher
flag,flag_admin_privilege,27,this is set when a user has admin privileges.
flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
1 flag flag_language_set 8 checks whether the user has set their prefered language
17 flag flag_incorrect_voucher 24 this is set when the selected voucher is invalid
18 flag flag_api_call_error 25 this is set when communication to an external service fails
19 flag flag_no_active_voucher 26 this is set when a user does not have an active voucher
20 flag flag_admin_privilege 27 this is set when a user has admin privileges.
21 flag flag_unregistered_number 28 this is set when an unregistered phonenumber tries to perform an action

View File

@@ -0,0 +1 @@
Retry

View File

@@ -0,0 +1 @@
Jaribu tena

View File

@@ -1,3 +1,4 @@
CATCH no_voucher flag_no_active_voucher 1
LOAD get_vouchers 0 LOAD get_vouchers 0
MAP get_vouchers MAP get_vouchers
MOUT back 0 MOUT back 0

View File

@@ -0,0 +1 @@
Chagua Sarafu

View File

@@ -0,0 +1 @@
The number you have entered is either not registered with Sarafu or is invalid.

View File

@@ -0,0 +1,7 @@
LOAD reset_unregistered_number 0
RELOAD reset_unregistered_number
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9

View File

@@ -0,0 +1 @@
Nambari uliyoingiza haijasajiliwa na Sarafu au sio sahihi.

View File

@@ -0,0 +1 @@
Maelezo ya Sarafu