Compare commits
	
		
			No commits in common. "master" and "lash/vise-make-var" have entirely different histories.
		
	
	
		
			master
			...
			lash/vise-
		
	
		
							
								
								
									
										20
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								.env.example
									
									
									
									
									
								
							@ -1,20 +0,0 @@
 | 
				
			|||||||
#Serve Http
 | 
					 | 
				
			||||||
PORT=7123
 | 
					 | 
				
			||||||
HOST=127.0.0.1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#AfricasTalking USSD POST endpoint
 | 
					 | 
				
			||||||
AT_ENDPOINT=/ussd/africastalking
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#PostgreSQL
 | 
					 | 
				
			||||||
DB_CONN=postgres://postgres:strongpass@localhost:5432/urdt_ussd
 | 
					 | 
				
			||||||
#DB_TIMEZONE=Africa/Nairobi
 | 
					 | 
				
			||||||
#DB_SCHEMA=vise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#External API Calls
 | 
					 | 
				
			||||||
CUSTODIAL_URL_BASE=http://localhost:5003
 | 
					 | 
				
			||||||
BEARER_TOKEN=eyJeSIsInRcCI6IkpXVCJ.yJwdWJsaWNLZXkiOiIwrrrrrr
 | 
					 | 
				
			||||||
DATA_URL_BASE=http://localhost:5006
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#Language
 | 
					 | 
				
			||||||
DEFAULT_LANGUAGE=eng
 | 
					 | 
				
			||||||
LANGUAGES=eng, swa
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -4,6 +4,3 @@ go.work*
 | 
				
			|||||||
**/*/*.bin
 | 
					**/*/*.bin
 | 
				
			||||||
**/*/.state/
 | 
					**/*/.state/
 | 
				
			||||||
cmd/.state/
 | 
					cmd/.state/
 | 
				
			||||||
id_*
 | 
					 | 
				
			||||||
*.gdbm
 | 
					 | 
				
			||||||
*.log
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					# ussd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> USSD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USSD service.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[AGPL-3.0](LICENSE).
 | 
				
			||||||
							
								
								
									
										257
									
								
								cmd/africastalking/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								cmd/africastalking/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,257 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/asm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						fsdb "git.defalsify.org/vise.git/db/fs"
 | 
				
			||||||
 | 
						gdbmdb "git.defalsify.org/vise.git/db/gdbm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
 | 
				
			||||||
 | 
						httpserver "git.grassecon.net/urdt/ussd/internal/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg = logging.NewVanilla()
 | 
				
			||||||
 | 
						scriptDir = path.Join("services", "registration")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type atRequestParser struct {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(arp *atRequestParser) GetSessionId(rq any) (string, error) {
 | 
				
			||||||
 | 
						rqv, ok := rq.(*http.Request)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "", handlers.ErrInvalidRequest
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := rqv.ParseForm(); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("failed to parse form data: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						phoneNumber := rqv.FormValue("phoneNumber")
 | 
				
			||||||
 | 
						if phoneNumber == "" {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("no phone number found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return phoneNumber, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(arp *atRequestParser) GetInput(rq any) ([]byte, error) {
 | 
				
			||||||
 | 
						rqv, ok := rq.(*http.Request)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, handlers.ErrInvalidRequest
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := rqv.ParseForm(); err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to parse form data: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						text := rqv.FormValue("text")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						parts := strings.Split(text, "*")
 | 
				
			||||||
 | 
						if len(parts) == 0 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("no input found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return []byte(parts[len(parts)-1]), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
 | 
				
			||||||
 | 
						flagParser := asm.NewFlagParser().WithDebug()
 | 
				
			||||||
 | 
						_, err := flagParser.Load(fp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return flagParser, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit", ussdHandlers.Quit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ussdHandlers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureDbDir(dbDir string) error {
 | 
				
			||||||
 | 
						err := os.MkdirAll(dbDir, 0700)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("state dir create exited with error: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "state.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						return store, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getUserdataDb(dbDir string, ctx context.Context) db.Db {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "userdata.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return store
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
 | 
				
			||||||
 | 
						store := fsdb.NewFsDb()
 | 
				
			||||||
 | 
						err := store.Connect(ctx, resourceDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rfs := resource.NewDbResource(store)
 | 
				
			||||||
 | 
						return rfs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var dbDir string
 | 
				
			||||||
 | 
						var resourceDir string
 | 
				
			||||||
 | 
						var size uint
 | 
				
			||||||
 | 
						var engineDebug bool
 | 
				
			||||||
 | 
						var stateDebug bool
 | 
				
			||||||
 | 
						var host string
 | 
				
			||||||
 | 
						var port uint
 | 
				
			||||||
 | 
						flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
 | 
				
			||||||
 | 
						flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
 | 
				
			||||||
 | 
						flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.UintVar(&size, "s", 160, "max size of output")
 | 
				
			||||||
 | 
						flag.StringVar(&host, "h", "127.0.0.1", "http host")
 | 
				
			||||||
 | 
						flag.UintVar(&port, "p", 7123, "http port")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir,  "outputsize", size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						pfp := path.Join(scriptDir, "pp.csv")
 | 
				
			||||||
 | 
						flagParser, err := getFlags(pfp, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := engine.Config{
 | 
				
			||||||
 | 
							Root:       "root",
 | 
				
			||||||
 | 
							OutputSize: uint32(size),
 | 
				
			||||||
 | 
							FlagCount:  uint32(16),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if stateDebug {
 | 
				
			||||||
 | 
							cfg.StateDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if engineDebug {
 | 
				
			||||||
 | 
							cfg.EngineDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rs, err := getResource(resourceDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = ensureDbDir(dbDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userdataStore := getUserdataDb(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer userdataStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbResource, ok := rs.(*resource.DbResource)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hl, err := getHandler(flagParser, dbResource, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stateStore, err := getStateStore(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer stateStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rp := &atRequestParser{}
 | 
				
			||||||
 | 
						bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
 | 
				
			||||||
 | 
						sh := httpserver.NewATSessionHandler(bsh)
 | 
				
			||||||
 | 
						s := &http.Server{
 | 
				
			||||||
 | 
							Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
 | 
				
			||||||
 | 
							Handler: sh,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.RegisterOnShutdown(sh.Shutdown)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cint := make(chan os.Signal)
 | 
				
			||||||
 | 
						cterm := make(chan os.Signal)
 | 
				
			||||||
 | 
						signal.Notify(cint, os.Interrupt, syscall.SIGINT)
 | 
				
			||||||
 | 
						signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case _ = <-cint:
 | 
				
			||||||
 | 
							case _ = <-cterm:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							s.Shutdown(ctx)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						err = s.ListenAndServe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.Infof("Server closed with error", "err", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										251
									
								
								cmd/async/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								cmd/async/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,251 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/asm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						fsdb "git.defalsify.org/vise.git/db/fs"
 | 
				
			||||||
 | 
						gdbmdb "git.defalsify.org/vise.git/db/gdbm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg = logging.NewVanilla()
 | 
				
			||||||
 | 
						scriptDir = path.Join("services", "registration")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type asyncRequestParser struct {
 | 
				
			||||||
 | 
						sessionId string
 | 
				
			||||||
 | 
						input []byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(p *asyncRequestParser) GetSessionId(r any) (string, error) {
 | 
				
			||||||
 | 
						return p.sessionId, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(p *asyncRequestParser) GetInput(r any) ([]byte, error) {
 | 
				
			||||||
 | 
						return p.input, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
 | 
				
			||||||
 | 
						flagParser := asm.NewFlagParser().WithDebug()
 | 
				
			||||||
 | 
						_, err := flagParser.Load(fp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return flagParser, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit", ussdHandlers.Quit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ussdHandlers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureDbDir(dbDir string) error {
 | 
				
			||||||
 | 
						err := os.MkdirAll(dbDir, 0700)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("state dir create exited with error: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "state.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						return store, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getUserdataDb(dbDir string, ctx context.Context) db.Db {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "userdata.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return store
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
 | 
				
			||||||
 | 
						store := fsdb.NewFsDb()
 | 
				
			||||||
 | 
						err := store.Connect(ctx, resourceDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rfs := resource.NewDbResource(store)
 | 
				
			||||||
 | 
						return rfs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var sessionId string
 | 
				
			||||||
 | 
						var dbDir string
 | 
				
			||||||
 | 
						var resourceDir string
 | 
				
			||||||
 | 
						var size uint
 | 
				
			||||||
 | 
						var engineDebug bool
 | 
				
			||||||
 | 
						var stateDebug bool
 | 
				
			||||||
 | 
						var host string
 | 
				
			||||||
 | 
						var port uint
 | 
				
			||||||
 | 
						flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
 | 
				
			||||||
 | 
						flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
 | 
				
			||||||
 | 
						flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
 | 
				
			||||||
 | 
						flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.UintVar(&size, "s", 160, "max size of output")
 | 
				
			||||||
 | 
						flag.StringVar(&host, "h", "127.0.0.1", "http host")
 | 
				
			||||||
 | 
						flag.UintVar(&port, "p", 7123, "http port")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir,  "outputsize", size, "sessionId", sessionId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						pfp := path.Join(scriptDir, "pp.csv")
 | 
				
			||||||
 | 
						flagParser, err := getFlags(pfp, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := engine.Config{
 | 
				
			||||||
 | 
							Root:       "root",
 | 
				
			||||||
 | 
							OutputSize: uint32(size),
 | 
				
			||||||
 | 
							FlagCount:  uint32(16),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if stateDebug {
 | 
				
			||||||
 | 
							cfg.StateDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if engineDebug {
 | 
				
			||||||
 | 
							cfg.EngineDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rs, err := getResource(resourceDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = ensureDbDir(dbDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userdataStore := getUserdataDb(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer userdataStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbResource, ok := rs.(*resource.DbResource)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hl, err := getHandler(flagParser, dbResource, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stateStore, err := getStateStore(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer stateStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rp := &asyncRequestParser{
 | 
				
			||||||
 | 
							sessionId: sessionId,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
 | 
				
			||||||
 | 
						cfg.SessionId = sessionId
 | 
				
			||||||
 | 
						rqs := handlers.RequestSession{
 | 
				
			||||||
 | 
							Ctx: ctx,
 | 
				
			||||||
 | 
							Writer: os.Stdout,
 | 
				
			||||||
 | 
							Config: cfg,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cint := make(chan os.Signal)
 | 
				
			||||||
 | 
						cterm := make(chan os.Signal)
 | 
				
			||||||
 | 
						signal.Notify(cint, os.Interrupt, syscall.SIGINT)
 | 
				
			||||||
 | 
						signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case _ = <-cint:
 | 
				
			||||||
 | 
							case _ = <-cterm:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							sh.Shutdown()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for true {
 | 
				
			||||||
 | 
							rqs, err = sh.Process(rqs)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Errorf("error in process: %v", err)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							rqs, err = sh.Output(rqs)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Errorf("error in output: %v", err)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							rqs, err = sh.Reset(rqs)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Errorf("error in reset: %v", err)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							fmt.Println("")
 | 
				
			||||||
 | 
							_, err = fmt.Scanln(&rqs.Input)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
							fmt.Errorf("error in input: %v", err)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										217
									
								
								cmd/http/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								cmd/http/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,217 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/asm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						fsdb "git.defalsify.org/vise.git/db/fs"
 | 
				
			||||||
 | 
						gdbmdb "git.defalsify.org/vise.git/db/gdbm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
				
			||||||
 | 
						httpserver "git.grassecon.net/urdt/ussd/internal/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg = logging.NewVanilla()
 | 
				
			||||||
 | 
						scriptDir = path.Join("services", "registration")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
 | 
				
			||||||
 | 
						flagParser := asm.NewFlagParser().WithDebug()
 | 
				
			||||||
 | 
						_, err := flagParser.Load(fp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return flagParser, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit", ussdHandlers.Quit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ussdHandlers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureDbDir(dbDir string) error {
 | 
				
			||||||
 | 
						err := os.MkdirAll(dbDir, 0700)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("state dir create exited with error: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "state.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						return store, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getUserdataDb(dbDir string, ctx context.Context) db.Db {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "userdata.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return store
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
 | 
				
			||||||
 | 
						store := fsdb.NewFsDb()
 | 
				
			||||||
 | 
						err := store.Connect(ctx, resourceDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rfs := resource.NewDbResource(store)
 | 
				
			||||||
 | 
						return rfs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var dbDir string
 | 
				
			||||||
 | 
						var resourceDir string
 | 
				
			||||||
 | 
						var size uint
 | 
				
			||||||
 | 
						var engineDebug bool
 | 
				
			||||||
 | 
						var stateDebug bool
 | 
				
			||||||
 | 
						var host string
 | 
				
			||||||
 | 
						var port uint
 | 
				
			||||||
 | 
						flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
 | 
				
			||||||
 | 
						flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
 | 
				
			||||||
 | 
						flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.UintVar(&size, "s", 160, "max size of output")
 | 
				
			||||||
 | 
						flag.StringVar(&host, "h", "127.0.0.1", "http host")
 | 
				
			||||||
 | 
						flag.UintVar(&port, "p", 7123, "http port")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir,  "outputsize", size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						pfp := path.Join(scriptDir, "pp.csv")
 | 
				
			||||||
 | 
						flagParser, err := getFlags(pfp, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := engine.Config{
 | 
				
			||||||
 | 
							Root:       "root",
 | 
				
			||||||
 | 
							OutputSize: uint32(size),
 | 
				
			||||||
 | 
							FlagCount:  uint32(16),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if stateDebug {
 | 
				
			||||||
 | 
							cfg.StateDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if engineDebug {
 | 
				
			||||||
 | 
							cfg.EngineDebug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rs, err := getResource(resourceDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = ensureDbDir(dbDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						userdataStore := getUserdataDb(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer userdataStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbResource, ok := rs.(*resource.DbResource)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hl, err := getHandler(flagParser, dbResource, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stateStore, err := getStateStore(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer stateStore.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rp := &httpserver.DefaultRequestParser{}
 | 
				
			||||||
 | 
						bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
 | 
				
			||||||
 | 
						sh := httpserver.ToSessionHandler(bsh)
 | 
				
			||||||
 | 
						s := &http.Server{
 | 
				
			||||||
 | 
							Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
 | 
				
			||||||
 | 
							Handler: sh,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						s.RegisterOnShutdown(sh.Shutdown)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cint := make(chan os.Signal)
 | 
				
			||||||
 | 
						cterm := make(chan os.Signal)
 | 
				
			||||||
 | 
						signal.Notify(cint, os.Interrupt, syscall.SIGINT)
 | 
				
			||||||
 | 
						signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							case _ = <-cint:
 | 
				
			||||||
 | 
							case _ = <-cterm:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							s.Shutdown(ctx)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						err = s.ListenAndServe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.Infof("Server closed with error", "err", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										198
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,198 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/asm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						fsdb "git.defalsify.org/vise.git/db/fs"
 | 
				
			||||||
 | 
						gdbmdb "git.defalsify.org/vise.git/db/gdbm"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/persist"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg      = logging.NewVanilla()
 | 
				
			||||||
 | 
						scriptDir = path.Join("services", "registration")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getParser(fp string, debug bool) (*asm.FlagParser, error) {
 | 
				
			||||||
 | 
						flagParser := asm.NewFlagParser().WithDebug()
 | 
				
			||||||
 | 
						_, err := flagParser.Load(fp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return flagParser, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ussdHandlers = ussdHandlers.WithPersister(pe)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit", ussdHandlers.Quit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
 | 
				
			||||||
 | 
						rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ussdHandlers, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensureDbDir(dbDir string) error {
 | 
				
			||||||
 | 
						err := os.MkdirAll(dbDir, 0700)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("state dir create exited with error: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) {
 | 
				
			||||||
 | 
						err := ensureDbDir(dbDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "state.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
						pr := persist.NewPersister(store)
 | 
				
			||||||
 | 
						return pr, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getUserdataDb(dbDir string, ctx context.Context) db.Db {
 | 
				
			||||||
 | 
						store := gdbmdb.NewGdbmDb()
 | 
				
			||||||
 | 
						storeFile := path.Join(dbDir, "userdata.gdbm")
 | 
				
			||||||
 | 
						store.Connect(ctx, storeFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return store
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
 | 
				
			||||||
 | 
						store := fsdb.NewFsDb()
 | 
				
			||||||
 | 
						err := store.Connect(ctx, resourceDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rfs := resource.NewDbResource(store)
 | 
				
			||||||
 | 
						return rfs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
 | 
				
			||||||
 | 
						en := engine.NewEngine(cfg, rs)
 | 
				
			||||||
 | 
						en = en.WithPersister(pr)
 | 
				
			||||||
 | 
						return en
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var dbDir string
 | 
				
			||||||
 | 
						var size uint
 | 
				
			||||||
 | 
						var sessionId string
 | 
				
			||||||
 | 
						var debug bool
 | 
				
			||||||
 | 
						flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
 | 
				
			||||||
 | 
						flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
 | 
				
			||||||
 | 
						flag.BoolVar(&debug, "d", false, "use engine debug output")
 | 
				
			||||||
 | 
						flag.UintVar(&size, "s", 160, "max size of output")
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logg.Infof("start command", "dbdir", dbDir, "outputsize", size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						ctx = context.WithValue(ctx, "SessionId", sessionId)
 | 
				
			||||||
 | 
						pfp := path.Join(scriptDir, "pp.csv")
 | 
				
			||||||
 | 
						flagParser, err := getParser(pfp, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cfg := engine.Config{
 | 
				
			||||||
 | 
							Root:       "root",
 | 
				
			||||||
 | 
							SessionId:  sessionId,
 | 
				
			||||||
 | 
							OutputSize: uint32(size),
 | 
				
			||||||
 | 
							FlagCount:  uint32(16),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rs, err := getResource(scriptDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pe, err := getPersister(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := getUserdataDb(dbDir, ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dbResource, ok := rs.(*resource.DbResource)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hl, err := getHandler(flagParser, dbResource, pe, store)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, err.Error())
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						en := getEngine(cfg, rs, pe)
 | 
				
			||||||
 | 
						en = en.WithFirst(hl.Init)
 | 
				
			||||||
 | 
						if debug {
 | 
				
			||||||
 | 
							en = en.WithDebug(nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = en.Init(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err)
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = engine.Loop(ctx, en, os.Stdin, os.Stdout)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										164
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								config/config.go
									
									
									
									
									
								
							@ -1,164 +1,10 @@
 | 
				
			|||||||
package config
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/env"
 | 
					
 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/storage"
 | 
					const (
 | 
				
			||||||
	slogging "github.com/grassrootseconomics/go-vise/slog"
 | 
					    CreateAccountURL = "https://custodial.sarafu.africa/api/account/create"
 | 
				
			||||||
 | 
					    TrackStatusURL   = "https://custodial.sarafu.africa/api/track/"
 | 
				
			||||||
 | 
					    BalanceURL       = "https://custodial.sarafu.africa/api/account/status/"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	logg               = slogging.Get().With("component", "visedriver-config")
 | 
					 | 
				
			||||||
	defaultLanguage    = "eng"
 | 
					 | 
				
			||||||
	languages          []string
 | 
					 | 
				
			||||||
	DefaultLanguage    string
 | 
					 | 
				
			||||||
	dbConn             string
 | 
					 | 
				
			||||||
	dbConnMissing      bool
 | 
					 | 
				
			||||||
	dbConnMode         storage.DbMode
 | 
					 | 
				
			||||||
	stateDbConn        string
 | 
					 | 
				
			||||||
	stateDbConnMode    storage.DbMode
 | 
					 | 
				
			||||||
	resourceDbConn     string
 | 
					 | 
				
			||||||
	resourceDbConnMode storage.DbMode
 | 
					 | 
				
			||||||
	userDbConn         string
 | 
					 | 
				
			||||||
	userDbConnMode     storage.DbMode
 | 
					 | 
				
			||||||
	Languages          []string
 | 
					 | 
				
			||||||
	configManager      *Config
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Override struct {
 | 
					 | 
				
			||||||
	DbConn           string
 | 
					 | 
				
			||||||
	DbConnMode       storage.DbMode
 | 
					 | 
				
			||||||
	StateConn        string
 | 
					 | 
				
			||||||
	StateConnMode    storage.DbMode
 | 
					 | 
				
			||||||
	ResourceConn     string
 | 
					 | 
				
			||||||
	ResourceConnMode storage.DbMode
 | 
					 | 
				
			||||||
	UserConn         string
 | 
					 | 
				
			||||||
	UserConnMode     storage.DbMode
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func setLanguage() error {
 | 
					 | 
				
			||||||
	defaultLanguage = env.GetEnv("DEFAULT_LANGUAGE", defaultLanguage)
 | 
					 | 
				
			||||||
	languages = strings.Split(env.GetEnv("LANGUAGES", defaultLanguage), ",")
 | 
					 | 
				
			||||||
	haveDefaultLanguage := false
 | 
					 | 
				
			||||||
	for i, v := range languages {
 | 
					 | 
				
			||||||
		languages[i] = strings.ReplaceAll(v, " ", "")
 | 
					 | 
				
			||||||
		if languages[i] == defaultLanguage {
 | 
					 | 
				
			||||||
			haveDefaultLanguage = true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !haveDefaultLanguage {
 | 
					 | 
				
			||||||
		languages = append([]string{defaultLanguage}, languages...)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func setConn() error {
 | 
					 | 
				
			||||||
	dbConn = env.GetEnv("DB_CONN", "?")
 | 
					 | 
				
			||||||
	stateDbConn = env.GetEnv("DB_CONN_STATE", dbConn)
 | 
					 | 
				
			||||||
	resourceDbConn = env.GetEnv("DB_CONN_RESOURCE", dbConn)
 | 
					 | 
				
			||||||
	userDbConn = env.GetEnv("DB_CONN_USER", dbConn)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func ApplyConn(override *Override) {
 | 
					 | 
				
			||||||
	if override.DbConn != "?" {
 | 
					 | 
				
			||||||
		dbConn = override.DbConn
 | 
					 | 
				
			||||||
		stateDbConn = override.StateConn
 | 
					 | 
				
			||||||
		resourceDbConn = override.ResourceConn
 | 
					 | 
				
			||||||
		userDbConn = override.UserConn
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	dbConnMode = override.DbConnMode
 | 
					 | 
				
			||||||
	if override.StateConn != "?" {
 | 
					 | 
				
			||||||
		stateDbConn = override.StateConn
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if override.ResourceConn != "?" {
 | 
					 | 
				
			||||||
		resourceDbConn = override.ResourceConn
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if override.UserConn != "?" {
 | 
					 | 
				
			||||||
		userDbConn = override.UserConn
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if dbConn == "?" {
 | 
					 | 
				
			||||||
		dbConn = ""
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if stateDbConn == "?" {
 | 
					 | 
				
			||||||
		stateDbConn = dbConn
 | 
					 | 
				
			||||||
		stateDbConnMode = dbConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if resourceDbConn == "?" {
 | 
					 | 
				
			||||||
		resourceDbConn = dbConn
 | 
					 | 
				
			||||||
		resourceDbConnMode = dbConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if userDbConn == "?" {
 | 
					 | 
				
			||||||
		userDbConn = dbConn
 | 
					 | 
				
			||||||
		userDbConnMode = dbConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logg.Debugf("conns", "conn", dbConn, "user", userDbConn)
 | 
					 | 
				
			||||||
	if override.DbConnMode != storage.DBMODE_ANY {
 | 
					 | 
				
			||||||
		dbConnMode = override.DbConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if override.StateConnMode != storage.DBMODE_ANY {
 | 
					 | 
				
			||||||
		stateDbConnMode = override.StateConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if override.ResourceConnMode != storage.DBMODE_ANY {
 | 
					 | 
				
			||||||
		resourceDbConnMode = override.ResourceConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if override.UserConnMode != storage.DBMODE_ANY {
 | 
					 | 
				
			||||||
		userDbConnMode = override.UserConnMode
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetConns() (storage.Conns, error) {
 | 
					 | 
				
			||||||
	o := storage.NewConns()
 | 
					 | 
				
			||||||
	c, err := storage.ToConnDataMode(stateDbConn, stateDbConnMode)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return o, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	o.Set(c, storage.STORETYPE_STATE)
 | 
					 | 
				
			||||||
	c, err = storage.ToConnDataMode(resourceDbConn, resourceDbConnMode)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return o, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	o.Set(c, storage.STORETYPE_RESOURCE)
 | 
					 | 
				
			||||||
	c, err = storage.ToConnDataMode(userDbConn, userDbConnMode)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return o, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	o.Set(c, storage.STORETYPE_USER)
 | 
					 | 
				
			||||||
	return o, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LoadConfig initializes the configuration values after environment variables are loaded.
 | 
					 | 
				
			||||||
func LoadConfig() error {
 | 
					 | 
				
			||||||
	configManager = NewConfig()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Add configuration keys with validation
 | 
					 | 
				
			||||||
	configManager.AddKey("HOST", "127.0.0.1", false, nil)
 | 
					 | 
				
			||||||
	configManager.AddKey("PORT", "7123", false, func(v string) error {
 | 
					 | 
				
			||||||
		_, err := strconv.Atoi(v)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	configManager.AddKey("DB_CONN", "", true, nil)
 | 
					 | 
				
			||||||
	// ... add other keys ?  or is enough :/ ...
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err := setConn()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = setLanguage()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	DefaultLanguage = defaultLanguage
 | 
					 | 
				
			||||||
	Languages = languages
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Report configuration
 | 
					 | 
				
			||||||
	configManager.Report("INFO")
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,64 +0,0 @@
 | 
				
			|||||||
//go:build configreport
 | 
					 | 
				
			||||||
// +build configreport
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	slogging "github.com/grassrootseconomics/go-vise/slog"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// go test -tags configreport ./config/...   ---> run with tag
 | 
					 | 
				
			||||||
func TestConfig(t *testing.T) {
 | 
					 | 
				
			||||||
	logger := slogging.Get().With("component", "test")
 | 
					 | 
				
			||||||
	cfg := NewConfig(logger)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("Default Values", func(t *testing.T) {
 | 
					 | 
				
			||||||
		cfg.AddKey("TEST_KEY", "default", false, nil)
 | 
					 | 
				
			||||||
		value, err := cfg.GetValue("TEST_KEY")
 | 
					 | 
				
			||||||
		t.Logf("Got value: %q, error: %v", value, err)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			t.Errorf("unexpected error: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if value != "default" {
 | 
					 | 
				
			||||||
			t.Errorf("expected 'default', got '%s'", value)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("Environment Override", func(t *testing.T) {
 | 
					 | 
				
			||||||
		os.Setenv("TEST_ENV_KEY", "override")
 | 
					 | 
				
			||||||
		defer os.Unsetenv("TEST_ENV_KEY")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cfg.AddKey("TEST_ENV_KEY", "default", false, nil)
 | 
					 | 
				
			||||||
		value, err := cfg.GetValue("TEST_ENV_KEY")
 | 
					 | 
				
			||||||
		t.Logf("Got value: %q, error: %v", value, err)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			t.Errorf("unexpected error: %v", err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if value != "override" {
 | 
					 | 
				
			||||||
			t.Errorf("expected 'override', got '%s'", value)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	t.Run("Validation", func(t *testing.T) {
 | 
					 | 
				
			||||||
		validator := func(v string) error {
 | 
					 | 
				
			||||||
			if v != "valid" {
 | 
					 | 
				
			||||||
				return fmt.Errorf("invalid value")
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cfg.AddKey("VALIDATED_KEY", "valid", false, validator)
 | 
					 | 
				
			||||||
		os.Setenv("VALIDATED_KEY", "invalid")
 | 
					 | 
				
			||||||
		defer os.Unsetenv("VALIDATED_KEY")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		value, err := cfg.GetValue("VALIDATED_KEY")
 | 
					 | 
				
			||||||
		t.Logf("Got value: %q, error: %v", value, err)
 | 
					 | 
				
			||||||
		if err == nil {
 | 
					 | 
				
			||||||
			t.Error("expected validation error, got nil")
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,84 +0,0 @@
 | 
				
			|||||||
//go:build configreport
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/env"
 | 
					 | 
				
			||||||
	slogging "github.com/grassrootseconomics/go-vise/slog"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ConfigValue represents a configuration key-value pair
 | 
					 | 
				
			||||||
type ConfigValue struct {
 | 
					 | 
				
			||||||
	Key       string
 | 
					 | 
				
			||||||
	Default   string
 | 
					 | 
				
			||||||
	Validator func(string) error
 | 
					 | 
				
			||||||
	Sensitive bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Config handles configuration management and reporting
 | 
					 | 
				
			||||||
type Config struct {
 | 
					 | 
				
			||||||
	values map[string]ConfigValue
 | 
					 | 
				
			||||||
	logger *slogging.Slog
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewConfig(logging interface{}) *Config {
 | 
					 | 
				
			||||||
	return &Config{
 | 
					 | 
				
			||||||
		values: make(map[string]ConfigValue),
 | 
					 | 
				
			||||||
		logger: slogging.Get().With("component", "visedriver-config-reporter"),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AddKey registers a new configuration key with optional validation
 | 
					 | 
				
			||||||
func (c *Config) AddKey(key string, defaultValue string, sensitive bool, validator func(string) error) {
 | 
					 | 
				
			||||||
	c.values[key] = ConfigValue{
 | 
					 | 
				
			||||||
		Key:       key,
 | 
					 | 
				
			||||||
		Default:   defaultValue,
 | 
					 | 
				
			||||||
		Validator: validator,
 | 
					 | 
				
			||||||
		Sensitive: sensitive,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetValue returns the value for a given key, applying environment override if present
 | 
					 | 
				
			||||||
func (c *Config) GetValue(key string) (string, error) {
 | 
					 | 
				
			||||||
	// Find config value by key
 | 
					 | 
				
			||||||
	var cv ConfigValue
 | 
					 | 
				
			||||||
	for _, v := range c.values {
 | 
					 | 
				
			||||||
		if v.Key == key {
 | 
					 | 
				
			||||||
			cv = v
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if cv.Key == "" {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("configuration key not found: %s", key)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get value from environment or default
 | 
					 | 
				
			||||||
	value := env.GetEnv(key, cv.Default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Validate if validator exists
 | 
					 | 
				
			||||||
	if cv.Validator != nil && cv.Validator(value) != nil {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("invalid value for key %s", key)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return value, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Report outputs all configuration values at the specified log level
 | 
					 | 
				
			||||||
func (c *Config) Report(level string) {
 | 
					 | 
				
			||||||
	for _, cv := range c.values {
 | 
					 | 
				
			||||||
		value, err := c.GetValue(cv.Key)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			c.logger.Errorf("Error getting value for %s: %v", cv.Key, err)
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if cv.Sensitive {
 | 
					 | 
				
			||||||
			value = "****"
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		c.logger.Debugf("config set", cv.Key, value)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,18 +0,0 @@
 | 
				
			|||||||
//go:build !configreport
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Config struct{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewConfig() *Config {
 | 
					 | 
				
			||||||
	return &Config{}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Config) AddKey(key string, defaultValue string, sensitive bool, validator func(string) error) {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Config) GetValue(key string) (string, error) {
 | 
					 | 
				
			||||||
	return "", nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (c *Config) Report(level string) {}
 | 
					 | 
				
			||||||
							
								
								
									
										28
									
								
								doc/data.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								doc/data.md
									
									
									
									
									
								
							@ -1,28 +0,0 @@
 | 
				
			|||||||
# Internals
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This document describes component versions:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `urdt-ussd` `v0.5.0-beta`
 | 
					 | 
				
			||||||
* `go-vise` `v0.2.2`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## User profile data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
All user profile items are stored under keys matching the user's session id, prefixed with the 8-bit value `github.com/grassrootseconomics/go-vise/db.DATATYPE_USERDATA` (32), and followed with a 16-big big-endian value subprefix.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For example, given the sessionId `+254123` and the key `git.grassecon.net/urdt-ussd/common.DATA_PUBLIC_KEY` (2) will be stored under the key:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
0x322b3235343132330002
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
prefix   sessionid       subprefix
 | 
					 | 
				
			||||||
32       2b323534313233  0002
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Sub-prefixes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
All sub-prefixes are defined as constants in the `git.grassecon.net/urdt-ussd/common` package. The constant names have the prefix `DATA_`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Please refer to inline godoc documentation for the `git.grassecon.net/urdt-ussd/common` package for details on each data item.
 | 
					 | 
				
			||||||
@ -1,14 +0,0 @@
 | 
				
			|||||||
package entry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/persist"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/resource"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type EntryHandler interface {
 | 
					 | 
				
			||||||
	Init(context.Context, string, []byte) (resource.Result, error) // HandlerFunc
 | 
					 | 
				
			||||||
	Exit()
 | 
					 | 
				
			||||||
	SetPersister(*persist.Persister)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										40
									
								
								env/load.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								env/load.go
									
									
									
									
										vendored
									
									
								
							@ -1,40 +0,0 @@
 | 
				
			|||||||
package env
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"log"
 | 
					 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/joho/godotenv"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func LoadEnvVariables() {
 | 
					 | 
				
			||||||
	LoadEnvVariablesPath(".")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func LoadEnvVariablesPath(dir string) {
 | 
					 | 
				
			||||||
	fp := path.Join(dir, ".env")
 | 
					 | 
				
			||||||
	err := godotenv.Load(fp)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Fatal("Error loading .env file", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper to get environment variables with a default fallback
 | 
					 | 
				
			||||||
func GetEnv(key, defaultVal string) string {
 | 
					 | 
				
			||||||
	if value, exists := os.LookupEnv(key); exists {
 | 
					 | 
				
			||||||
		return value
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return defaultVal
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Helper to safely convert environment variables to uint
 | 
					 | 
				
			||||||
func GetEnvUint(key string, defaultVal uint) uint {
 | 
					 | 
				
			||||||
	if value, exists := os.LookupEnv(key); exists {
 | 
					 | 
				
			||||||
		if parsed, err := strconv.Atoi(value); err == nil && parsed >= 0 {
 | 
					 | 
				
			||||||
			return uint(parsed)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return defaultVal
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,15 +0,0 @@
 | 
				
			|||||||
package errors
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	ErrInvalidRequest = errors.New("invalid request for context")
 | 
					 | 
				
			||||||
	ErrSessionMissing = errors.New("missing session")
 | 
					 | 
				
			||||||
	ErrInvalidInput   = errors.New("invalid input")
 | 
					 | 
				
			||||||
	ErrStorage        = errors.New("storage retrieval fail")
 | 
					 | 
				
			||||||
	ErrEngineType     = errors.New("incompatible engine")
 | 
					 | 
				
			||||||
	ErrEngineInit     = errors.New("engine init fail")
 | 
					 | 
				
			||||||
	ErrEngineExec     = errors.New("engine exec fail")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
							
								
								
									
										35
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								go.mod
									
									
									
									
									
								
							@ -1,27 +1,26 @@
 | 
				
			|||||||
module git.grassecon.net/grassrootseconomics/visedriver
 | 
					module git.grassecon.net/urdt/ussd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go 1.24
 | 
					go 1.22.6
 | 
				
			||||||
 | 
					 | 
				
			||||||
toolchain go1.24.6
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/grassrootseconomics/go-vise v0.5.0
 | 
						git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f
 | 
				
			||||||
	github.com/jackc/pgx/v5 v5.7.5
 | 
						github.com/alecthomas/assert/v2 v2.2.2
 | 
				
			||||||
	github.com/joho/godotenv v1.5.1
 | 
						github.com/peteole/testdata-loader v0.3.0
 | 
				
			||||||
 | 
						gopkg.in/leonelquinteros/gotext.v1 v1.3.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/alecthomas/participle/v2 v2.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/alecthomas/repr v0.2.0 // indirect
 | 
				
			||||||
	github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
 | 
						github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
 | 
						github.com/fxamacker/cbor/v2 v2.4.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
						github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
 | 
				
			||||||
	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
						github.com/hexops/gotextdiff v1.0.3 // indirect
 | 
				
			||||||
	github.com/jackc/puddle/v2 v2.2.2 // indirect
 | 
						github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
 | 
				
			||||||
	github.com/leonelquinteros/gotext v1.7.2 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 | 
						github.com/stretchr/objx v0.5.2 // indirect
 | 
				
			||||||
	github.com/stretchr/testify v1.9.0 // indirect
 | 
						github.com/stretchr/testify v1.9.0
 | 
				
			||||||
	github.com/x448/float16 v0.8.4 // indirect
 | 
						github.com/x448/float16 v0.8.4 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.40.0 // indirect
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.16.0 // indirect
 | 
					 | 
				
			||||||
	golang.org/x/text v0.27.0 // indirect
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										59
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								go.sum
									
									
									
									
									
								
							@ -1,47 +1,36 @@
 | 
				
			|||||||
 | 
					git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM=
 | 
				
			||||||
 | 
					git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
 | 
				
			||||||
 | 
					github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
 | 
				
			||||||
 | 
					github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
 | 
				
			||||||
 | 
					github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
 | 
				
			||||||
 | 
					github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y=
 | 
				
			||||||
 | 
					github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
 | 
				
			||||||
 | 
					github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 | 
				
			||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
 | 
					github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
 | 
				
			||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
 | 
					github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
 | 
				
			||||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
 | 
					github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
 | 
				
			||||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
 | 
					github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
 | 
				
			||||||
github.com/grassrootseconomics/go-vise v0.5.0 h1:FRg2de55Eb5SisrgTBeFWfWX+sXwp5q9r7YWtKWDwsk=
 | 
					github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
 | 
				
			||||||
github.com/grassrootseconomics/go-vise v0.5.0/go.mod h1:b2/q4jfTu2i1wyUwYUu7FYq4m2f1AZv8MpiDM3ZcoGo=
 | 
					github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 | 
				
			||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 | 
					github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 | 
				
			||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 | 
					github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
 | 
				
			||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
 | 
					github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
 | 
				
			||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 | 
					 | 
				
			||||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
 | 
					 | 
				
			||||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
 | 
					 | 
				
			||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
 | 
					 | 
				
			||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 | 
					 | 
				
			||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
					 | 
				
			||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
					 | 
				
			||||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
 | 
					 | 
				
			||||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
 | 
					 | 
				
			||||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
 | 
					 | 
				
			||||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
 | 
					 | 
				
			||||||
github.com/pashagolub/pgxmock/v4 v4.7.0 h1:de2ORuFYyjwOQR7NBm57+321RnZxpYiuUjsmqRiqgh8=
 | 
					 | 
				
			||||||
github.com/pashagolub/pgxmock/v4 v4.7.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM=
 | 
					 | 
				
			||||||
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
 | 
					github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
 | 
				
			||||||
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
 | 
					github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 | 
					github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					 | 
				
			||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
					 | 
				
			||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
					 | 
				
			||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
					github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
				
			||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
					github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
				
			||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 | 
					github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 | 
				
			||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 | 
					github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 | 
				
			||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
					 | 
				
			||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
 | 
					 | 
				
			||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
					 | 
				
			||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
 | 
					 | 
				
			||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
 | 
					 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
 | 
				
			||||||
 | 
					gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								internal/handlers/base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								internal/handlers/base.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/persist"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/storage"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type BaseSessionHandler struct {
 | 
				
			||||||
 | 
						cfgTemplate engine.Config
 | 
				
			||||||
 | 
						rp RequestParser
 | 
				
			||||||
 | 
						rs resource.Resource
 | 
				
			||||||
 | 
						hn *ussd.Handlers
 | 
				
			||||||
 | 
						provider storage.StorageProvider
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler {
 | 
				
			||||||
 | 
						return &BaseSessionHandler{
 | 
				
			||||||
 | 
							cfgTemplate: cfg,
 | 
				
			||||||
 | 
							rs: rs,
 | 
				
			||||||
 | 
							hn: hn,
 | 
				
			||||||
 | 
							rp: rp,
 | 
				
			||||||
 | 
							provider: storage.NewSimpleStorageProvider(stateDb, userdataDb),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f* BaseSessionHandler) Shutdown() {
 | 
				
			||||||
 | 
						err := f.provider.Close()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.Errorf("handler shutdown error", "err", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
 | 
				
			||||||
 | 
						en := engine.NewEngine(cfg, rs)
 | 
				
			||||||
 | 
						en = en.WithPersister(pr)
 | 
				
			||||||
 | 
						return en
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) {
 | 
				
			||||||
 | 
						var r bool
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var ok bool
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						logg.InfoCtxf(rqs.Ctx, "new request",  rqs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs.Storage, err = f.provider.Get(rqs.Config.SessionId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err)
 | 
				
			||||||
 | 
							return rqs, ErrStorage
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f.hn = f.hn.WithPersister(rqs.Storage.Persister)
 | 
				
			||||||
 | 
						eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
 | 
				
			||||||
 | 
						en, ok := eni.(*engine.DefaultEngine)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
 | 
				
			||||||
 | 
							rqs.Storage = nil
 | 
				
			||||||
 | 
							if perr != nil {
 | 
				
			||||||
 | 
								logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return rqs, ErrEngineType
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						en = en.WithFirst(f.hn.Init)
 | 
				
			||||||
 | 
						if rqs.Config.EngineDebug {
 | 
				
			||||||
 | 
							en = en.WithDebug(nil)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rqs.Engine = en
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r, err = rqs.Engine.Init(rqs.Ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
 | 
				
			||||||
 | 
							rqs.Storage = nil
 | 
				
			||||||
 | 
							if perr != nil {
 | 
				
			||||||
 | 
								logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return rqs, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if r && len(rqs.Input) > 0 {
 | 
				
			||||||
 | 
							r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
 | 
				
			||||||
 | 
							rqs.Storage = nil
 | 
				
			||||||
 | 
							if perr != nil {
 | 
				
			||||||
 | 
								logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return rqs, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs.Continue = r 
 | 
				
			||||||
 | 
						return rqs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession,  error) {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer)
 | 
				
			||||||
 | 
						return rqs, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) {
 | 
				
			||||||
 | 
						defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
 | 
				
			||||||
 | 
						return rqs, rqs.Engine.Finish()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) GetConfig() engine.Config {
 | 
				
			||||||
 | 
						return f.cfgTemplate
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *BaseSessionHandler) GetRequestParser() RequestParser {
 | 
				
			||||||
 | 
						return f.rp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								internal/handlers/server/accountservice.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								internal/handlers/server/accountservice.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					package server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/config"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/models"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AccountServiceInterface interface {
 | 
				
			||||||
 | 
						CheckBalance(publicKey string) (string, error)
 | 
				
			||||||
 | 
						CreateAccount() (*models.AccountResponse, error)
 | 
				
			||||||
 | 
						CheckAccountStatus(trackingId string) (string, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AccountService struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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(trackingId string) (string, error) {
 | 
				
			||||||
 | 
						resp, err := http.Get(config.TrackStatusURL + trackingId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var trackResp models.TrackStatusResponse
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &trackResp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						status := trackResp.Result.Transaction.Status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return status, 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(publicKey string) (string, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := http.Get(config.BalanceURL + publicKey)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "0.0", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "0.0", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var balanceResp models.BalanceResponse
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &balanceResp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "0.0", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						balance := balanceResp.Result.Balance
 | 
				
			||||||
 | 
						return balance, 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() (*models.AccountResponse, error) {
 | 
				
			||||||
 | 
						resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var accountResp models.AccountResponse
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &accountResp)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &accountResp, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								internal/handlers/single.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								internal/handlers/single.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/engine"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/persist"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/storage"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg = logging.NewVanilla().WithDomain("handlers")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ErrInvalidRequest = errors.New("invalid request for context")
 | 
				
			||||||
 | 
						ErrSessionMissing = errors.New("missing session")
 | 
				
			||||||
 | 
						ErrInvalidInput = errors.New("invalid input")
 | 
				
			||||||
 | 
						ErrStorage = errors.New("storage retrieval fail")
 | 
				
			||||||
 | 
						ErrEngineType = errors.New("incompatible engine")
 | 
				
			||||||
 | 
						ErrEngineInit = errors.New("engine init fail")
 | 
				
			||||||
 | 
						ErrEngineExec = errors.New("engine exec fail")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RequestSession struct {
 | 
				
			||||||
 | 
						Ctx context.Context
 | 
				
			||||||
 | 
						Config engine.Config
 | 
				
			||||||
 | 
						Engine engine.Engine
 | 
				
			||||||
 | 
						Input []byte
 | 
				
			||||||
 | 
						Storage *storage.Storage
 | 
				
			||||||
 | 
						Writer io.Writer
 | 
				
			||||||
 | 
						Continue bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: seems like can remove this.
 | 
				
			||||||
 | 
					type RequestParser interface {
 | 
				
			||||||
 | 
						GetSessionId(rq any) (string, error)
 | 
				
			||||||
 | 
						GetInput(rq any) ([]byte, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RequestHandler interface {
 | 
				
			||||||
 | 
						GetConfig() engine.Config
 | 
				
			||||||
 | 
						GetRequestParser() RequestParser
 | 
				
			||||||
 | 
						GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine 
 | 
				
			||||||
 | 
						Process(rs RequestSession) (RequestSession, error)
 | 
				
			||||||
 | 
						Output(rs RequestSession) (RequestSession, error)
 | 
				
			||||||
 | 
						Reset(rs RequestSession) (RequestSession, error)
 | 
				
			||||||
 | 
						Shutdown()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										970
									
								
								internal/handlers/ussd/menuhandler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										970
									
								
								internal/handlers/ussd/menuhandler.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,970 @@
 | 
				
			|||||||
 | 
					package ussd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/asm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/cache"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/lang"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/persist"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/resource"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/state"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers/server"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/utils"
 | 
				
			||||||
 | 
						"gopkg.in/leonelquinteros/gotext.v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg           = logging.NewVanilla().WithDomain("ussdmenuhandler")
 | 
				
			||||||
 | 
						scriptDir      = path.Join("services", "registration")
 | 
				
			||||||
 | 
						translationDir = path.Join(scriptDir, "locale")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FSData struct {
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
						St   *state.State
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FlagManager handles centralized flag management
 | 
				
			||||||
 | 
					type FlagManager struct {
 | 
				
			||||||
 | 
						parser *asm.FlagParser
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewFlagManager creates a new FlagManager instance
 | 
				
			||||||
 | 
					func NewFlagManager(csvPath string) (*FlagManager, error) {
 | 
				
			||||||
 | 
						parser := asm.NewFlagParser()
 | 
				
			||||||
 | 
						_, err := parser.Load(csvPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to load flag parser: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &FlagManager{
 | 
				
			||||||
 | 
							parser: parser,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetFlag retrieves a flag value by its label
 | 
				
			||||||
 | 
					func (fm *FlagManager) GetFlag(label string) (uint32, error) {
 | 
				
			||||||
 | 
						return fm.parser.GetFlag(label)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Handlers struct {
 | 
				
			||||||
 | 
						pe             *persist.Persister
 | 
				
			||||||
 | 
						st             *state.State
 | 
				
			||||||
 | 
						ca             cache.Memory
 | 
				
			||||||
 | 
						userdataStore  utils.DataStore
 | 
				
			||||||
 | 
						flagManager    *asm.FlagParser
 | 
				
			||||||
 | 
						accountService server.AccountServiceInterface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, error) {
 | 
				
			||||||
 | 
						if userdataStore == nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("cannot create handler with nil userdata store")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						userDb := &utils.UserDataStore{
 | 
				
			||||||
 | 
							Db: userdataStore,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h := &Handlers{
 | 
				
			||||||
 | 
							userdataStore:  userDb,
 | 
				
			||||||
 | 
							flagManager:    appFlags,
 | 
				
			||||||
 | 
							accountService: &server.AccountService{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					func isValidPIN(pin string) bool {
 | 
				
			||||||
 | 
						match, _ := regexp.MatchString(pinPattern, pin)
 | 
				
			||||||
 | 
						return match
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
 | 
				
			||||||
 | 
						if h.pe != nil {
 | 
				
			||||||
 | 
							panic("persister already set")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h.pe = pe
 | 
				
			||||||
 | 
						return h
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var r resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if h.pe == nil {
 | 
				
			||||||
 | 
							logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
 | 
				
			||||||
 | 
							return r, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h.st = h.pe.GetState()
 | 
				
			||||||
 | 
						h.ca = h.pe.GetMemory()
 | 
				
			||||||
 | 
						if h.st == nil || h.ca == nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
 | 
				
			||||||
 | 
							return r, fmt.Errorf("cannot get state and memory for handler")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h.pe = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return r, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetLanguage sets the language across the menu
 | 
				
			||||||
 | 
					func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inputStr := string(input)
 | 
				
			||||||
 | 
						switch inputStr {
 | 
				
			||||||
 | 
						case "0":
 | 
				
			||||||
 | 
							res.FlagSet = []uint32{state.FLAG_LANG}
 | 
				
			||||||
 | 
							res.Content = "eng"
 | 
				
			||||||
 | 
						case "1":
 | 
				
			||||||
 | 
							res.FlagSet = []uint32{state.FLAG_LANG}
 | 
				
			||||||
 | 
							res.Content = "swa"
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.FlagSet = append(res.FlagSet, languageSetFlag)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
 | 
				
			||||||
 | 
						accountResp, err := h.accountService.CreateAccount()
 | 
				
			||||||
 | 
						data := map[utils.DataTyp]string{
 | 
				
			||||||
 | 
							utils.DATA_TRACKING_ID:  accountResp.Result.TrackingId,
 | 
				
			||||||
 | 
							utils.DATA_PUBLIC_KEY:   accountResp.Result.PublicKey,
 | 
				
			||||||
 | 
							utils.DATA_CUSTODIAL_ID: accountResp.Result.CustodialId.String(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for key, value := range data {
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err := store.WriteEntry(ctx, sessionId, key, []byte(value))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
 | 
				
			||||||
 | 
						res.FlagSet = append(res.FlagSet, flag_account_created)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateAccount checks if any account exists on the JSON data file, and if not
 | 
				
			||||||
 | 
					// creates an account on the API,
 | 
				
			||||||
 | 
					// sets the default values and flags
 | 
				
			||||||
 | 
					func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						_, err = store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_CREATED)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if db.IsNotFound(err) {
 | 
				
			||||||
 | 
								logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist")
 | 
				
			||||||
 | 
								err = h.createAccountNoExist(ctx, sessionId, &res)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return res, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SavePin persists the user's PIN choice into the filesystem
 | 
				
			||||||
 | 
					func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						accountPIN := string(input)
 | 
				
			||||||
 | 
						// Validate that the PIN is a 4-digit number
 | 
				
			||||||
 | 
						if !isValidPIN(accountPIN) {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(accountPIN))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetResetSingleEdit sets and resets  flags to allow gradual editing of profile information.
 | 
				
			||||||
 | 
					func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						menuOption := string(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
 | 
				
			||||||
 | 
						flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch menuOption {
 | 
				
			||||||
 | 
						case "2":
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_allow_update)
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_single_edit)
 | 
				
			||||||
 | 
						case "3":
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_allow_update)
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_single_edit)
 | 
				
			||||||
 | 
						case "4":
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_allow_update)
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_single_edit)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_single_edit)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VerifyPin checks whether the confirmation PIN is similar to the account PIN
 | 
				
			||||||
 | 
					// If similar, it sets the USERFLAG_PIN_SET flag allowing the user
 | 
				
			||||||
 | 
					// to access the main menu
 | 
				
			||||||
 | 
					func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
 | 
				
			||||||
 | 
						flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
 | 
				
			||||||
 | 
						flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN)
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if bytes.Equal(input, AccountPin) {
 | 
				
			||||||
 | 
							res.FlagSet = []uint32{flag_valid_pin}
 | 
				
			||||||
 | 
							res.FlagReset = []uint32{flag_pin_mismatch}
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_pin_set)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res.FlagSet = []uint32{flag_pin_mismatch}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// codeFromCtx retrieves language codes from the context that can be used for handling translations
 | 
				
			||||||
 | 
					func codeFromCtx(ctx context.Context) string {
 | 
				
			||||||
 | 
						var code string
 | 
				
			||||||
 | 
						if ctx.Value("Language") != nil {
 | 
				
			||||||
 | 
							lang := ctx.Value("Language").(lang.Language)
 | 
				
			||||||
 | 
							code = lang.Code
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return code
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveFirstname updates the first name in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) > 0 {
 | 
				
			||||||
 | 
							firstName := string(input)
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(firstName))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveFamilyname updates the family name in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) > 0 {
 | 
				
			||||||
 | 
							familyName := string(input)
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(familyName))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("a family name cannot be less than one character")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) == 4 {
 | 
				
			||||||
 | 
							yob := string(input)
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(yob))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveLocation updates the location in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) > 0 {
 | 
				
			||||||
 | 
							location := string(input)
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(location))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveGender updates the gender in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) > 0 {
 | 
				
			||||||
 | 
							gender := string(input)
 | 
				
			||||||
 | 
							switch gender {
 | 
				
			||||||
 | 
							case "1":
 | 
				
			||||||
 | 
								gender = "Male"
 | 
				
			||||||
 | 
							case "2":
 | 
				
			||||||
 | 
								gender = "Female"
 | 
				
			||||||
 | 
							case "3":
 | 
				
			||||||
 | 
								gender = "Unspecified"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input.
 | 
				
			||||||
 | 
					func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(input) > 0 {
 | 
				
			||||||
 | 
							offerings := string(input)
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(offerings))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetAllowUpdate resets the allowupdate flag that allows a user to update  profile data.
 | 
				
			||||||
 | 
					func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_allow_update)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
 | 
				
			||||||
 | 
					func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckIdentifier retrieves the PublicKey from the JSON data file.
 | 
				
			||||||
 | 
					func (h *Handlers) CheckIdentifier(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
 | 
				
			||||||
 | 
						publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = string(publicKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN.
 | 
				
			||||||
 | 
					// It sets the required flags that control the flow.
 | 
				
			||||||
 | 
					func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
				
			||||||
 | 
						flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
				
			||||||
 | 
						flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							if len(input) == 4 {
 | 
				
			||||||
 | 
								if bytes.Equal(input, AccountPin) {
 | 
				
			||||||
 | 
									if h.st.MatchFlag(flag_account_authorized, false) {
 | 
				
			||||||
 | 
										res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
 | 
				
			||||||
 | 
										res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										res.FlagSet = append(res.FlagSet, flag_allow_update)
 | 
				
			||||||
 | 
										res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
 | 
				
			||||||
 | 
									res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
				
			||||||
 | 
									return res, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetIncorrectPin resets the incorrect pin flag  after a new PIN attempt.
 | 
				
			||||||
 | 
					func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckAccountStatus queries the API using the TrackingId and sets flags
 | 
				
			||||||
 | 
					// based on the account status
 | 
				
			||||||
 | 
					func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_account_success, _ := h.flagManager.GetFlag("flag_account_success")
 | 
				
			||||||
 | 
						flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						trackingId, err := store.ReadEntry(ctx, sessionId, utils.DATA_TRACKING_ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						status, err := h.accountService.CheckAccountStatus(string(trackingId))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							fmt.Println("Error checking account status:", err)
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if status == "SUCCESS" {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_account_success)
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_account_pending)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_account_success)
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_account_pending)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Quit displays the Thank you message and exits the menu
 | 
				
			||||||
 | 
					func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						code := codeFromCtx(ctx)
 | 
				
			||||||
 | 
						l := gotext.NewLocale(translationDir, code)
 | 
				
			||||||
 | 
						l.AddDomain("default")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = l.Get("Thank you for using Sarafu. Goodbye!")
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VerifyYob verifies the length of the given input
 | 
				
			||||||
 | 
					func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						date := string(input)
 | 
				
			||||||
 | 
						_, err = strconv.Atoi(date)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// If conversion fails, input is not numeric
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(date) == 4 {
 | 
				
			||||||
 | 
							res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetIncorrectYob resets the incorrect date format flag after a new attempt
 | 
				
			||||||
 | 
					func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckBalance retrieves the balance from the API using the "PublicKey" and sets
 | 
				
			||||||
 | 
					// the balance as the result content
 | 
				
			||||||
 | 
					func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						balance, err := h.accountService.CheckBalance(string(publicKey))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.Content = balance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						recipient := string(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if recipient != "0" {
 | 
				
			||||||
 | 
							// mimic invalid number check
 | 
				
			||||||
 | 
							if recipient == "000" {
 | 
				
			||||||
 | 
								res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
 | 
				
			||||||
 | 
								res.Content = recipient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return res, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							store := h.userdataStore
 | 
				
			||||||
 | 
							err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(recipient))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return res, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TransactionReset resets the previous transaction data (Recipient and Amount)
 | 
				
			||||||
 | 
					// as well as the invalid flags
 | 
				
			||||||
 | 
					func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
 | 
				
			||||||
 | 
						flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(""))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(""))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResetTransactionAmount resets the transaction amount and invalid flag
 | 
				
			||||||
 | 
					func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(""))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_invalid_amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MaxAmount gets the current balance from the API and sets it as
 | 
				
			||||||
 | 
					// the result content.
 | 
				
			||||||
 | 
					func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						balance, err := h.accountService.CheckBalance(string(publicKey))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = balance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateAmount ensures that the given input is a valid amount and that
 | 
				
			||||||
 | 
					// it is not more than the current balance.
 | 
				
			||||||
 | 
					func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						amountStr := string(input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						balanceStr, err := h.accountService.CheckBalance(string(publicKey))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.Content = balanceStr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the balance
 | 
				
			||||||
 | 
						balanceParts := strings.Split(balanceStr, " ")
 | 
				
			||||||
 | 
						if len(balanceParts) != 2 {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("unexpected balance format: %s", balanceStr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						balanceValue, err := strconv.ParseFloat(balanceParts[0], 64)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("failed to parse balance: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Extract numeric part from input
 | 
				
			||||||
 | 
						re := regexp.MustCompile(`^(\d+(\.\d+)?)\s*(?:CELO)?$`)
 | 
				
			||||||
 | 
						matches := re.FindStringSubmatch(strings.TrimSpace(amountStr))
 | 
				
			||||||
 | 
						if len(matches) < 2 {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_invalid_amount)
 | 
				
			||||||
 | 
							res.Content = amountStr
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						inputAmount, err := strconv.ParseFloat(matches[1], 64)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_invalid_amount)
 | 
				
			||||||
 | 
							res.Content = amountStr
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if inputAmount > balanceValue {
 | 
				
			||||||
 | 
							res.FlagSet = append(res.FlagSet, flag_invalid_amount)
 | 
				
			||||||
 | 
							res.Content = amountStr
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places
 | 
				
			||||||
 | 
						err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amountStr))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetRecipient returns the transaction recipient from the gdbm.
 | 
				
			||||||
 | 
					func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = string(recipient)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSender retrieves the public key from the Gdbm Db
 | 
				
			||||||
 | 
					func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = string(publicKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetAmount retrieves the amount from teh Gdbm Db
 | 
				
			||||||
 | 
					func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = string(amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// QuickWithBalance retrieves the balance for a given public key from the custodial balance API endpoint before
 | 
				
			||||||
 | 
					// gracefully exiting the session.
 | 
				
			||||||
 | 
					func (h *Handlers) QuitWithBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						code := codeFromCtx(ctx)
 | 
				
			||||||
 | 
						l := gotext.NewLocale(translationDir, code)
 | 
				
			||||||
 | 
						l.AddDomain("default")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						balance, err := h.accountService.CheckBalance(string(publicKey))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res.Content = l.Get("Your account balance is %s", balance)
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InitiateTransaction returns a confirmation and resets the transaction data
 | 
				
			||||||
 | 
					// on the gdbm store.
 | 
				
			||||||
 | 
					func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | 
				
			||||||
 | 
						var res resource.Result
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						sessionId, ok := ctx.Value("SessionId").(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return res, fmt.Errorf("missing session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						code := codeFromCtx(ctx)
 | 
				
			||||||
 | 
						l := gotext.NewLocale(translationDir, code)
 | 
				
			||||||
 | 
						l.AddDomain("default")
 | 
				
			||||||
 | 
						// TODO
 | 
				
			||||||
 | 
						// Use the amount, recipient and sender to call the API and initialize the transaction
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.Content = l.Get("Your request has been sent. %s will receive %s from %s.", string(recipient), string(amount), string(publicKey))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return res, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res.FlagReset = append(res.FlagReset, account_authorized_flag)
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetProfileInfo retrieves and formats the profile information of a user from a Gdbm backed storage.
 | 
				
			||||||
 | 
					func (h *Handlers) GetProfileInfo(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")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Default value when an entry is not found
 | 
				
			||||||
 | 
						defaultValue := "Not Provided"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Helper function to handle nil byte slices and convert them to string
 | 
				
			||||||
 | 
						getEntryOrDefault := func(entry []byte, err error) string {
 | 
				
			||||||
 | 
							if err != nil || entry == nil {
 | 
				
			||||||
 | 
								return defaultValue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return string(entry)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						store := h.userdataStore
 | 
				
			||||||
 | 
						// Retrieve user data as strings with fallback to defaultValue
 | 
				
			||||||
 | 
						firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FIRST_NAME))
 | 
				
			||||||
 | 
						familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FAMILY_NAME))
 | 
				
			||||||
 | 
						yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_YOB))
 | 
				
			||||||
 | 
						gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_GENDER))
 | 
				
			||||||
 | 
						location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_LOCATION))
 | 
				
			||||||
 | 
						offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_OFFERINGS))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct the full name
 | 
				
			||||||
 | 
						name := defaultValue
 | 
				
			||||||
 | 
						if familyName != defaultValue {
 | 
				
			||||||
 | 
							if firstName == defaultValue {
 | 
				
			||||||
 | 
								name = familyName
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								name = firstName + " " + familyName
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate age from year of birth
 | 
				
			||||||
 | 
						age := defaultValue
 | 
				
			||||||
 | 
						if yob != defaultValue {
 | 
				
			||||||
 | 
							if yobInt, err := strconv.Atoi(yob); err == nil {
 | 
				
			||||||
 | 
								age = strconv.Itoa(utils.CalculateAgeWithYOB(yobInt))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return res, fmt.Errorf("invalid year of birth: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Format the result
 | 
				
			||||||
 | 
						res.Content = fmt.Sprintf(
 | 
				
			||||||
 | 
							"Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
 | 
				
			||||||
 | 
							name, gender, age, location, offerings,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1698
									
								
								internal/handlers/ussd/menuhandler_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1698
									
								
								internal/handlers/ussd/menuhandler_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										59
									
								
								internal/handlers/ussd/mocks/dbmock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								internal/handlers/ussd/mocks/dbmock.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					package mocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/lang"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/mock"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MockDb struct {
 | 
				
			||||||
 | 
						mock.Mock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) SetPrefix(prefix uint8) {
 | 
				
			||||||
 | 
						m.Called(prefix)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Prefix() uint8 {
 | 
				
			||||||
 | 
						args := m.Called()
 | 
				
			||||||
 | 
						return args.Get(0).(uint8)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Safe() bool {
 | 
				
			||||||
 | 
						args := m.Called()
 | 
				
			||||||
 | 
						return args.Get(0).(bool)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) SetLanguage(language *lang.Language) {
 | 
				
			||||||
 | 
						m.Called(language)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) SetLock(uint8, bool) error {
 | 
				
			||||||
 | 
						args := m.Called()
 | 
				
			||||||
 | 
						return args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Connect(ctx context.Context, connectionStr string) error {
 | 
				
			||||||
 | 
						args := m.Called(ctx, connectionStr)
 | 
				
			||||||
 | 
						return args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) SetSession(sessionId string) {
 | 
				
			||||||
 | 
						m.Called(sessionId)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Put(ctx context.Context, key, value []byte) error {
 | 
				
			||||||
 | 
						args := m.Called(ctx, key, value)
 | 
				
			||||||
 | 
						return args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Get(ctx context.Context, key []byte) ([]byte, error) {
 | 
				
			||||||
 | 
						args := m.Called(ctx, key)
 | 
				
			||||||
 | 
						return nil, args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockDb) Close() error {
 | 
				
			||||||
 | 
						args := m.Called(nil)
 | 
				
			||||||
 | 
						return args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								internal/handlers/ussd/mocks/servicemock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/handlers/ussd/mocks/servicemock.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package mocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/models"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/mock"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MockAccountService implements AccountServiceInterface for testing
 | 
				
			||||||
 | 
					type MockAccountService struct {
 | 
				
			||||||
 | 
						mock.Mock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
 | 
				
			||||||
 | 
						args := m.Called()
 | 
				
			||||||
 | 
						return args.Get(0).(*models.AccountResponse), args.Error(1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockAccountService) CheckBalance(publicKey string) (string, error) {
 | 
				
			||||||
 | 
						args := m.Called(publicKey)
 | 
				
			||||||
 | 
						return args.String(0), args.Error(1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockAccountService) CheckAccountStatus(trackingId string) (string, error) {
 | 
				
			||||||
 | 
						args := m.Called(trackingId)
 | 
				
			||||||
 | 
						return args.String(0), args.Error(1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								internal/handlers/ussd/mocks/userdbmock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								internal/handlers/ussd/mocks/userdbmock.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package mocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/utils"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/mock"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MockUserDataStore struct {
 | 
				
			||||||
 | 
						db.Db
 | 
				
			||||||
 | 
						mock.Mock
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockUserDataStore) ReadEntry(ctx context.Context, sessionId string, typ utils.DataTyp) ([]byte, error) {
 | 
				
			||||||
 | 
						args := m.Called(ctx, sessionId, typ)
 | 
				
			||||||
 | 
						return args.Get(0).([]byte), args.Error(1)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *MockUserDataStore) WriteEntry(ctx context.Context, sessionId string, typ utils.DataTyp, value []byte) error {
 | 
				
			||||||
 | 
						args := m.Called(ctx, sessionId, typ, value)
 | 
				
			||||||
 | 
						return args.Error(0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										93
									
								
								internal/http/at_session_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								internal/http/at_session_handler.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					package http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ATSessionHandler struct {
 | 
				
			||||||
 | 
						*SessionHandler
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler {
 | 
				
			||||||
 | 
						return &ATSessionHandler{
 | 
				
			||||||
 | 
							SessionHandler: ToSessionHandler(h),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						var code int
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs := handlers.RequestSession{
 | 
				
			||||||
 | 
							Ctx:    req.Context(),
 | 
				
			||||||
 | 
							Writer: w,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rp := ash.GetRequestParser()
 | 
				
			||||||
 | 
						cfg := ash.GetConfig()
 | 
				
			||||||
 | 
						cfg.SessionId, err = rp.GetSessionId(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
				
			||||||
 | 
							ash.writeError(w, 400, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rqs.Config = cfg
 | 
				
			||||||
 | 
						rqs.Input, err = rp.GetInput(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
				
			||||||
 | 
							ash.writeError(w, 400, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs, err = ash.Process(rqs)
 | 
				
			||||||
 | 
						switch err {
 | 
				
			||||||
 | 
						case handlers.ErrStorage:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						case handlers.ErrEngineInit:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						case handlers.ErrEngineExec:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							code = 200
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if code != 200 {
 | 
				
			||||||
 | 
							ash.writeError(w, 500, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.WriteHeader(200)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "text/plain")
 | 
				
			||||||
 | 
						rqs, err = ash.Output(rqs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ash.writeError(w, 500, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs, err = ash.Reset(rqs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ash.writeError(w, 500, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.RequestSession, error) {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var prefix string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if rqs.Continue {
 | 
				
			||||||
 | 
							prefix = "CON "
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							prefix = "END "
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = io.WriteString(rqs.Writer, prefix)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return rqs, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer)
 | 
				
			||||||
 | 
						return rqs, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										122
									
								
								internal/http/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								internal/http/server.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					package http
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/logging"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						logg = logging.NewVanilla().WithDomain("httpserver")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DefaultRequestParser struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) {
 | 
				
			||||||
 | 
						rqv, ok := rq.(*http.Request)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "", handlers.ErrInvalidRequest
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						v := rqv.Header.Get("X-Vise-Session")
 | 
				
			||||||
 | 
						if v == "" {
 | 
				
			||||||
 | 
							return "", handlers.ErrSessionMissing
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
 | 
				
			||||||
 | 
						rqv, ok := rq.(*http.Request)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, handlers.ErrInvalidRequest
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer rqv.Body.Close()
 | 
				
			||||||
 | 
						v, err := ioutil.ReadAll(rqv.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return v, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SessionHandler struct {
 | 
				
			||||||
 | 
						handlers.RequestHandler
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
 | 
				
			||||||
 | 
						return &SessionHandler{
 | 
				
			||||||
 | 
							RequestHandler: h,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) {
 | 
				
			||||||
 | 
						s := err.Error()
 | 
				
			||||||
 | 
						w.Header().Set("Content-Length", strconv.Itoa(len(s)))
 | 
				
			||||||
 | 
						w.WriteHeader(code)
 | 
				
			||||||
 | 
						_, err = w.Write([]byte{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.Errorf("error writing error!!", "err", err, "olderr", s)
 | 
				
			||||||
 | 
							w.WriteHeader(500)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						var code int
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var perr error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs := handlers.RequestSession{
 | 
				
			||||||
 | 
							Ctx: req.Context(),
 | 
				
			||||||
 | 
							Writer: w,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rp := f.GetRequestParser()
 | 
				
			||||||
 | 
						cfg := f.GetConfig()
 | 
				
			||||||
 | 
						cfg.SessionId, err = rp.GetSessionId(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
				
			||||||
 | 
							f.writeError(w, 400, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						rqs.Config = cfg
 | 
				
			||||||
 | 
						rqs.Input, err = rp.GetInput(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
				
			||||||
 | 
							f.writeError(w, 400, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rqs, err = f.Process(rqs)
 | 
				
			||||||
 | 
						switch err {
 | 
				
			||||||
 | 
						case handlers.ErrStorage:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						case handlers.ErrEngineInit:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						case handlers.ErrEngineExec:
 | 
				
			||||||
 | 
							code = 500
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							code = 200
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if code != 200 {
 | 
				
			||||||
 | 
							f.writeError(w, 500, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w.WriteHeader(200)
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "text/plain")
 | 
				
			||||||
 | 
						rqs, err = f.Output(rqs)
 | 
				
			||||||
 | 
						rqs, perr = f.Reset(rqs)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							f.writeError(w, 500, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if perr != nil {
 | 
				
			||||||
 | 
							f.writeError(w, 500, perr)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								internal/models/accountresponse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								internal/models/accountresponse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AccountResponse struct {
 | 
				
			||||||
 | 
						Ok     bool `json:"ok"`
 | 
				
			||||||
 | 
						Result struct {
 | 
				
			||||||
 | 
							CustodialId json.Number `json:"custodialId"`
 | 
				
			||||||
 | 
							PublicKey   string      `json:"publicKey"`
 | 
				
			||||||
 | 
							TrackingId  string      `json:"trackingId"`
 | 
				
			||||||
 | 
						} `json:"result"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								internal/models/balanceresponse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								internal/models/balanceresponse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					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"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								internal/models/trackstatusresponse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								internal/models/trackstatusresponse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								internal/storage/storage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								internal/storage/storage.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/persist"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Storage struct {
 | 
				
			||||||
 | 
						Persister *persist.Persister
 | 
				
			||||||
 | 
						UserdataDb db.Db	
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StorageProvider interface {
 | 
				
			||||||
 | 
						Get(sessionId string) (*Storage, error)
 | 
				
			||||||
 | 
						Put(sessionId string, storage *Storage) error
 | 
				
			||||||
 | 
						Close() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SimpleStorageProvider struct {
 | 
				
			||||||
 | 
						*Storage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider {
 | 
				
			||||||
 | 
						pe := persist.NewPersister(stateStore)
 | 
				
			||||||
 | 
						pe = pe.WithFlush()
 | 
				
			||||||
 | 
						return &SimpleStorageProvider{
 | 
				
			||||||
 | 
							Storage: &Storage{
 | 
				
			||||||
 | 
								Persister: pe,
 | 
				
			||||||
 | 
								UserdataDb: userdataStore,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *SimpleStorageProvider) Get(sessionId string) (*Storage, error) {
 | 
				
			||||||
 | 
						return p.Storage, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *SimpleStorageProvider) Put(sessionId string, storage *Storage) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *SimpleStorageProvider) Close() error {
 | 
				
			||||||
 | 
						return p.Storage.UserdataDb.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								internal/utils/age.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								internal/utils/age.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
 | 
				
			||||||
 | 
					// It adjusts for cases where the current date is before the birthday in the current year.
 | 
				
			||||||
 | 
					func CalculateAge(birthdate, today time.Time) int {
 | 
				
			||||||
 | 
						today = today.In(birthdate.Location())
 | 
				
			||||||
 | 
						ty, tm, td := today.Date()
 | 
				
			||||||
 | 
						today = time.Date(ty, tm, td, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						by, bm, bd := birthdate.Date()
 | 
				
			||||||
 | 
						birthdate = time.Date(by, bm, bd, 0, 0, 0, 0, time.UTC)
 | 
				
			||||||
 | 
						if today.Before(birthdate) {
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						age := ty - by
 | 
				
			||||||
 | 
						anniversary := birthdate.AddDate(age, 0, 0)
 | 
				
			||||||
 | 
						if anniversary.After(today) {
 | 
				
			||||||
 | 
							age--
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return age
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CalculateAgeWithYOB calculates the age based on the given year of birth (YOB).
 | 
				
			||||||
 | 
					// It subtracts the YOB from the current year to determine the age.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Parameters:
 | 
				
			||||||
 | 
					//   yob: The year of birth as an integer.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Returns:
 | 
				
			||||||
 | 
					//   The calculated age as an integer.
 | 
				
			||||||
 | 
					func CalculateAgeWithYOB(yob int) int {
 | 
				
			||||||
 | 
					    currentYear := time.Now().Year()
 | 
				
			||||||
 | 
					    return currentYear - yob
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								internal/utils/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/utils/db.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/binary"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DataTyp uint16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						DATA_ACCOUNT DataTyp = iota
 | 
				
			||||||
 | 
						DATA_ACCOUNT_CREATED
 | 
				
			||||||
 | 
						DATA_TRACKING_ID
 | 
				
			||||||
 | 
						DATA_PUBLIC_KEY
 | 
				
			||||||
 | 
						DATA_CUSTODIAL_ID
 | 
				
			||||||
 | 
						DATA_ACCOUNT_PIN
 | 
				
			||||||
 | 
						DATA_ACCOUNT_STATUS
 | 
				
			||||||
 | 
						DATA_FIRST_NAME
 | 
				
			||||||
 | 
						DATA_FAMILY_NAME
 | 
				
			||||||
 | 
						DATA_YOB
 | 
				
			||||||
 | 
						DATA_LOCATION
 | 
				
			||||||
 | 
						DATA_GENDER
 | 
				
			||||||
 | 
						DATA_OFFERINGS
 | 
				
			||||||
 | 
						DATA_RECIPIENT
 | 
				
			||||||
 | 
						DATA_AMOUNT
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func typToBytes(typ DataTyp) []byte {
 | 
				
			||||||
 | 
						var b [2]byte
 | 
				
			||||||
 | 
						binary.BigEndian.PutUint16(b[:], uint16(typ))
 | 
				
			||||||
 | 
						return b[:]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func PackKey(typ DataTyp, data []byte) []byte {
 | 
				
			||||||
 | 
						v := typToBytes(typ)
 | 
				
			||||||
 | 
						return append(v, data...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								internal/utils/userStore.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								internal/utils/userStore.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.defalsify.org/vise.git/db"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DataStore interface {
 | 
				
			||||||
 | 
						db.Db
 | 
				
			||||||
 | 
						ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error)
 | 
				
			||||||
 | 
						WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserDataStore struct {
 | 
				
			||||||
 | 
						db.Db
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ReadEntry retrieves an entry from the store based on the provided parameters.
 | 
				
			||||||
 | 
					func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
 | 
				
			||||||
 | 
						store.SetPrefix(db.DATATYPE_USERDATA)
 | 
				
			||||||
 | 
						store.SetSession(sessionId)
 | 
				
			||||||
 | 
						k := PackKey(typ, []byte(sessionId))
 | 
				
			||||||
 | 
						return store.Get(ctx, k)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
 | 
				
			||||||
 | 
						store.SetPrefix(db.DATATYPE_USERDATA)
 | 
				
			||||||
 | 
						store.SetSession(sessionId)
 | 
				
			||||||
 | 
						k := PackKey(typ, []byte(sessionId))
 | 
				
			||||||
 | 
						return store.Put(ctx, k, value)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								request/base.go
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								request/base.go
									
									
									
									
									
								
							@ -1,113 +0,0 @@
 | 
				
			|||||||
package request
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/db"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/engine"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/persist"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/resource"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/entry"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/errors"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/storage"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type EngineFunc func(engine.Config, resource.Resource, *persist.Persister) engine.Engine
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type BaseRequestHandler struct {
 | 
					 | 
				
			||||||
	cfgTemplate engine.Config
 | 
					 | 
				
			||||||
	rp          RequestParser
 | 
					 | 
				
			||||||
	rs          resource.Resource
 | 
					 | 
				
			||||||
	hn          entry.EntryHandler
 | 
					 | 
				
			||||||
	provider    storage.StorageProvider
 | 
					 | 
				
			||||||
	engineFunc	EngineFunc
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn entry.EntryHandler) *BaseRequestHandler {
 | 
					 | 
				
			||||||
	h := &BaseRequestHandler{
 | 
					 | 
				
			||||||
		cfgTemplate: cfg,
 | 
					 | 
				
			||||||
		rs:          rs,
 | 
					 | 
				
			||||||
		hn:          hn,
 | 
					 | 
				
			||||||
		rp:          rp,
 | 
					 | 
				
			||||||
		provider:    storage.NewSimpleStorageProvider(stateDb, userdataDb),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	h.engineFunc = h.getDefaultEngine
 | 
					 | 
				
			||||||
	return h
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) WithEngineFunc(fn EngineFunc) *BaseRequestHandler {
 | 
					 | 
				
			||||||
	f.engineFunc = fn
 | 
					 | 
				
			||||||
	return f
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) Shutdown(ctx context.Context) {
 | 
					 | 
				
			||||||
	err := f.provider.Close(ctx)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logg.Errorf("handler shutdown error", "err", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
 | 
					 | 
				
			||||||
	return f.engineFunc(cfg, rs, pr)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) getDefaultEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
 | 
					 | 
				
			||||||
	en := engine.NewEngine(cfg, rs)
 | 
					 | 
				
			||||||
	en = en.WithPersister(pr)
 | 
					 | 
				
			||||||
	en = en.WithFirst(f.hn.Init)
 | 
					 | 
				
			||||||
	if f.cfgTemplate.EngineDebug {
 | 
					 | 
				
			||||||
		en = en.WithDebug(nil)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return en
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) {
 | 
					 | 
				
			||||||
	var r bool
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logg.InfoCtxf(rqs.Ctx, "new request", "data", rqs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rqs.Storage, err = f.provider.Get(rqs.Ctx, rqs.Config.SessionId)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err)
 | 
					 | 
				
			||||||
		return rqs, errors.ErrStorage
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	f.hn.SetPersister(rqs.Storage.Persister)
 | 
					 | 
				
			||||||
	defer func() {
 | 
					 | 
				
			||||||
		f.hn.Exit()
 | 
					 | 
				
			||||||
	}()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rqs.Engine = f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
 | 
					 | 
				
			||||||
	r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		perr := f.provider.Put(rqs.Ctx, rqs.Config.SessionId, rqs.Storage)
 | 
					 | 
				
			||||||
		rqs.Storage = nil
 | 
					 | 
				
			||||||
		if perr != nil {
 | 
					 | 
				
			||||||
			logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return rqs, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rqs.Continue = r
 | 
					 | 
				
			||||||
	return rqs, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) Output(rqs RequestSession) (RequestSession, error) {
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
 | 
					 | 
				
			||||||
	return rqs, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) Reset(ctx context.Context, rqs RequestSession) (RequestSession, error) {
 | 
					 | 
				
			||||||
	defer f.provider.Put(ctx, rqs.Config.SessionId, rqs.Storage)
 | 
					 | 
				
			||||||
	return rqs, rqs.Engine.Finish(ctx)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) GetConfig() engine.Config {
 | 
					 | 
				
			||||||
	return f.cfgTemplate
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *BaseRequestHandler) GetRequestParser() RequestParser {
 | 
					 | 
				
			||||||
	return f.rp
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,37 +0,0 @@
 | 
				
			|||||||
package http
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/errors"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type DefaultRequestParser struct {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (rp *DefaultRequestParser) GetSessionId(ctx context.Context, rq any) (string, error) {
 | 
					 | 
				
			||||||
	rqv, ok := rq.(*http.Request)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return "", errors.ErrInvalidRequest
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	v := rqv.Header.Get("X-Vise-Session")
 | 
					 | 
				
			||||||
	if v == "" {
 | 
					 | 
				
			||||||
		return "", errors.ErrSessionMissing
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return v, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
 | 
					 | 
				
			||||||
	rqv, ok := rq.(*http.Request)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return nil, errors.ErrInvalidRequest
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer rqv.Body.Close()
 | 
					 | 
				
			||||||
	v, err := ioutil.ReadAll(rqv.Body)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return v, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,92 +0,0 @@
 | 
				
			|||||||
package http
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/errors"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/request"
 | 
					 | 
				
			||||||
	slogging "github.com/grassrootseconomics/go-vise/slog"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	logg = slogging.Get().With("component", "visedriver.http.session")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// HTTPRequestHandler implements the session handler for HTTP
 | 
					 | 
				
			||||||
type HTTPRequestHandler struct {
 | 
					 | 
				
			||||||
	request.RequestHandler
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (f *HTTPRequestHandler) WriteError(w http.ResponseWriter, code int, err error) {
 | 
					 | 
				
			||||||
	s := err.Error()
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Length", strconv.Itoa(len(s)))
 | 
					 | 
				
			||||||
	w.WriteHeader(code)
 | 
					 | 
				
			||||||
	_, err = w.Write([]byte(s))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logg.Errorf("error writing error!!", "err", err, "olderr", s)
 | 
					 | 
				
			||||||
		w.WriteHeader(500)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewHTTPRequestHandler(h request.RequestHandler) *HTTPRequestHandler {
 | 
					 | 
				
			||||||
	return &HTTPRequestHandler{
 | 
					 | 
				
			||||||
		RequestHandler: h,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (hh *HTTPRequestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 | 
					 | 
				
			||||||
	var code int
 | 
					 | 
				
			||||||
	var err error
 | 
					 | 
				
			||||||
	var perr error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rqs := request.RequestSession{
 | 
					 | 
				
			||||||
		Ctx:    req.Context(),
 | 
					 | 
				
			||||||
		Writer: w,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rp := hh.GetRequestParser()
 | 
					 | 
				
			||||||
	cfg := hh.GetConfig()
 | 
					 | 
				
			||||||
	cfg.SessionId, err = rp.GetSessionId(req.Context(), req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
					 | 
				
			||||||
		hh.WriteError(w, 400, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	rqs.Config = cfg
 | 
					 | 
				
			||||||
	rqs.Input, err = rp.GetInput(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
 | 
					 | 
				
			||||||
		hh.WriteError(w, 400, err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	rqs, err = hh.Process(rqs)
 | 
					 | 
				
			||||||
	switch err {
 | 
					 | 
				
			||||||
	case errors.ErrStorage:
 | 
					 | 
				
			||||||
		code = 500
 | 
					 | 
				
			||||||
	case errors.ErrEngineInit:
 | 
					 | 
				
			||||||
		code = 500
 | 
					 | 
				
			||||||
	case errors.ErrEngineExec:
 | 
					 | 
				
			||||||
		code = 500
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		code = 200
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if code != 200 {
 | 
					 | 
				
			||||||
		hh.WriteError(w, 500, err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	w.WriteHeader(200)
 | 
					 | 
				
			||||||
	w.Header().Set("Content-Type", "text/plain")
 | 
					 | 
				
			||||||
	rqs, err = hh.Output(rqs)
 | 
					 | 
				
			||||||
	rqs, perr = hh.Reset(rqs.Ctx, rqs)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		hh.WriteError(w, 500, err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if perr != nil {
 | 
					 | 
				
			||||||
		hh.WriteError(w, 500, perr)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,233 +0,0 @@
 | 
				
			|||||||
package http
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"errors"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"net/http/httptest"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/engine"
 | 
					 | 
				
			||||||
	viseerrors "git.grassecon.net/grassrootseconomics/visedriver/errors"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/request"
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// invalidRequestType is a custom type to test invalid request scenarios
 | 
					 | 
				
			||||||
type invalidRequestType struct{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// errorReader is a helper type that always returns an error when Read is called
 | 
					 | 
				
			||||||
type errorReader struct{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (e *errorReader) Read(p []byte) (n int, err error) {
 | 
					 | 
				
			||||||
	return 0, errors.New("read error")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestRequestHandler_ServeHTTP(t *testing.T) {
 | 
					 | 
				
			||||||
	tests := []struct {
 | 
					 | 
				
			||||||
		name           string
 | 
					 | 
				
			||||||
		sessionID      string
 | 
					 | 
				
			||||||
		input          []byte
 | 
					 | 
				
			||||||
		parserErr      error
 | 
					 | 
				
			||||||
		processErr     error
 | 
					 | 
				
			||||||
		outputErr      error
 | 
					 | 
				
			||||||
		resetErr       error
 | 
					 | 
				
			||||||
		expectedStatus int
 | 
					 | 
				
			||||||
	}{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:           "Success",
 | 
					 | 
				
			||||||
			sessionID:      "123",
 | 
					 | 
				
			||||||
			input:          []byte("test input"),
 | 
					 | 
				
			||||||
			expectedStatus: http.StatusOK,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:           "Missing Session ID",
 | 
					 | 
				
			||||||
			sessionID:      "",
 | 
					 | 
				
			||||||
			parserErr:      viseerrors.ErrSessionMissing,
 | 
					 | 
				
			||||||
			expectedStatus: http.StatusBadRequest,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:           "Process Error",
 | 
					 | 
				
			||||||
			sessionID:      "123",
 | 
					 | 
				
			||||||
			input:          []byte("test input"),
 | 
					 | 
				
			||||||
			processErr:     viseerrors.ErrStorage,
 | 
					 | 
				
			||||||
			expectedStatus: http.StatusInternalServerError,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:           "Output Error",
 | 
					 | 
				
			||||||
			sessionID:      "123",
 | 
					 | 
				
			||||||
			input:          []byte("test input"),
 | 
					 | 
				
			||||||
			outputErr:      errors.New("output error"),
 | 
					 | 
				
			||||||
			expectedStatus: http.StatusOK,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:           "Reset Error",
 | 
					 | 
				
			||||||
			sessionID:      "123",
 | 
					 | 
				
			||||||
			input:          []byte("test input"),
 | 
					 | 
				
			||||||
			resetErr:       errors.New("reset error"),
 | 
					 | 
				
			||||||
			expectedStatus: http.StatusOK,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, tt := range tests {
 | 
					 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
					 | 
				
			||||||
			mockRequestParser := &httpmocks.MockRequestParser{
 | 
					 | 
				
			||||||
				GetSessionIdFunc: func(any) (string, error) {
 | 
					 | 
				
			||||||
					return tt.sessionID, tt.parserErr
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				GetInputFunc: func(any) ([]byte, error) {
 | 
					 | 
				
			||||||
					return tt.input, nil
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			mockRequestHandler := &httpmocks.MockRequestHandler{
 | 
					 | 
				
			||||||
				ProcessFunc: func(rs request.RequestSession) (request.RequestSession, error) {
 | 
					 | 
				
			||||||
					return rs, tt.processErr
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				OutputFunc: func(rs request.RequestSession) (request.RequestSession, error) {
 | 
					 | 
				
			||||||
					return rs, tt.outputErr
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				ResetFunc: func(ctx context.Context, rs request.RequestSession) (request.RequestSession, error) {
 | 
					 | 
				
			||||||
					return rs, tt.resetErr
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				GetRequestParserFunc: func() request.RequestParser {
 | 
					 | 
				
			||||||
					return mockRequestParser
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				GetConfigFunc: func() engine.Config {
 | 
					 | 
				
			||||||
					return engine.Config{}
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			sessionHandler := &HTTPRequestHandler{
 | 
					 | 
				
			||||||
				RequestHandler: mockRequestHandler,
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(tt.input))
 | 
					 | 
				
			||||||
			req.Header.Set("X-Vise-Session", tt.sessionID)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			rr := httptest.NewRecorder()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			sessionHandler.ServeHTTP(rr, req)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if status := rr.Code; status != tt.expectedStatus {
 | 
					 | 
				
			||||||
				t.Errorf("handler returned wrong status code: got %v want %v",
 | 
					 | 
				
			||||||
					status, tt.expectedStatus)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestRequestHandler_WriteError(t *testing.T) {
 | 
					 | 
				
			||||||
	handler := &HTTPRequestHandler{}
 | 
					 | 
				
			||||||
	mockWriter := &httpmocks.MockWriter{}
 | 
					 | 
				
			||||||
	err := errors.New("test error")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	handler.WriteError(mockWriter, http.StatusBadRequest, err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if mockWriter.WrittenString != "" {
 | 
					 | 
				
			||||||
		t.Errorf("Expected empty body, got %s", mockWriter.WrittenString)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDefaultRequestParser_GetSessionId(t *testing.T) {
 | 
					 | 
				
			||||||
	tests := []struct {
 | 
					 | 
				
			||||||
		name          string
 | 
					 | 
				
			||||||
		request       any
 | 
					 | 
				
			||||||
		expectedID    string
 | 
					 | 
				
			||||||
		expectedError error
 | 
					 | 
				
			||||||
	}{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: "Valid Session ID",
 | 
					 | 
				
			||||||
			request: func() *http.Request {
 | 
					 | 
				
			||||||
				req := httptest.NewRequest(http.MethodPost, "/", nil)
 | 
					 | 
				
			||||||
				req.Header.Set("X-Vise-Session", "123456")
 | 
					 | 
				
			||||||
				return req
 | 
					 | 
				
			||||||
			}(),
 | 
					 | 
				
			||||||
			expectedID:    "123456",
 | 
					 | 
				
			||||||
			expectedError: nil,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:          "Missing Session ID",
 | 
					 | 
				
			||||||
			request:       httptest.NewRequest(http.MethodPost, "/", nil),
 | 
					 | 
				
			||||||
			expectedID:    "",
 | 
					 | 
				
			||||||
			expectedError: viseerrors.ErrSessionMissing,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:          "Invalid Request Type",
 | 
					 | 
				
			||||||
			request:       invalidRequestType{},
 | 
					 | 
				
			||||||
			expectedID:    "",
 | 
					 | 
				
			||||||
			expectedError: viseerrors.ErrInvalidRequest,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	parser := &DefaultRequestParser{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, tt := range tests {
 | 
					 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
					 | 
				
			||||||
			id, err := parser.GetSessionId(context.Background(), tt.request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if id != tt.expectedID {
 | 
					 | 
				
			||||||
				t.Errorf("Expected session ID %s, got %s", tt.expectedID, id)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if err != tt.expectedError {
 | 
					 | 
				
			||||||
				t.Errorf("Expected error %v, got %v", tt.expectedError, err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestDefaultRequestParser_GetInput(t *testing.T) {
 | 
					 | 
				
			||||||
	tests := []struct {
 | 
					 | 
				
			||||||
		name          string
 | 
					 | 
				
			||||||
		request       any
 | 
					 | 
				
			||||||
		expectedInput []byte
 | 
					 | 
				
			||||||
		expectedError error
 | 
					 | 
				
			||||||
	}{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: "Valid Input",
 | 
					 | 
				
			||||||
			request: func() *http.Request {
 | 
					 | 
				
			||||||
				return httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString("test input"))
 | 
					 | 
				
			||||||
			}(),
 | 
					 | 
				
			||||||
			expectedInput: []byte("test input"),
 | 
					 | 
				
			||||||
			expectedError: nil,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:          "Empty Input",
 | 
					 | 
				
			||||||
			request:       httptest.NewRequest(http.MethodPost, "/", nil),
 | 
					 | 
				
			||||||
			expectedInput: []byte{},
 | 
					 | 
				
			||||||
			expectedError: nil,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name:          "Invalid Request Type",
 | 
					 | 
				
			||||||
			request:       invalidRequestType{},
 | 
					 | 
				
			||||||
			expectedInput: nil,
 | 
					 | 
				
			||||||
			expectedError: viseerrors.ErrInvalidRequest,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			name: "Read Error",
 | 
					 | 
				
			||||||
			request: func() *http.Request {
 | 
					 | 
				
			||||||
				return httptest.NewRequest(http.MethodPost, "/", &errorReader{})
 | 
					 | 
				
			||||||
			}(),
 | 
					 | 
				
			||||||
			expectedInput: nil,
 | 
					 | 
				
			||||||
			expectedError: errors.New("read error"),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	parser := &DefaultRequestParser{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, tt := range tests {
 | 
					 | 
				
			||||||
		t.Run(tt.name, func(t *testing.T) {
 | 
					 | 
				
			||||||
			input, err := parser.GetInput(tt.request)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if !bytes.Equal(input, tt.expectedInput) {
 | 
					 | 
				
			||||||
				t.Errorf("Expected input %s, got %s", tt.expectedInput, input)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if err != tt.expectedError && (err == nil || err.Error() != tt.expectedError.Error()) {
 | 
					 | 
				
			||||||
				t.Errorf("Expected error %v, got %v", tt.expectedError, err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,42 +0,0 @@
 | 
				
			|||||||
package request
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"git.grassecon.net/grassrootseconomics/visedriver/storage"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/engine"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/persist"
 | 
					 | 
				
			||||||
	"github.com/grassrootseconomics/go-vise/resource"
 | 
					 | 
				
			||||||
	slogging "github.com/grassrootseconomics/go-vise/slog"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var (
 | 
					 | 
				
			||||||
	logg = slogging.Get().With("component", "visedriver.request")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type RequestSession struct {
 | 
					 | 
				
			||||||
	Ctx      context.Context
 | 
					 | 
				
			||||||
	Config   engine.Config
 | 
					 | 
				
			||||||
	Engine   engine.Engine
 | 
					 | 
				
			||||||
	Input    []byte
 | 
					 | 
				
			||||||
	Storage  *storage.Storage
 | 
					 | 
				
			||||||
	Writer   io.Writer
 | 
					 | 
				
			||||||
	Continue bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: seems like can remove this.
 | 
					 | 
				
			||||||
type RequestParser interface {
 | 
					 | 
				
			||||||
	GetSessionId(context.Context, any) (string, error)
 | 
					 | 
				
			||||||
	GetInput(any) ([]byte, error)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type RequestHandler interface {
 | 
					 | 
				
			||||||
	GetConfig() engine.Config
 | 
					 | 
				
			||||||
	GetRequestParser() RequestParser
 | 
					 | 
				
			||||||
	GetEngine(engine.Config, resource.Resource, *persist.Persister) engine.Engine
 | 
					 | 
				
			||||||
	Process(RequestSession) (RequestSession, error)
 | 
					 | 
				
			||||||
	Output(RequestSession) (RequestSession, error)
 | 
					 | 
				
			||||||
	Reset(context.Context, RequestSession) (RequestSession, error)
 | 
					 | 
				
			||||||
	Shutdown(ctx context.Context)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,44 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
    "ok": true,
 | 
					 | 
				
			||||||
    "description": "Token holdings with current balances",
 | 
					 | 
				
			||||||
    "result": {
 | 
					 | 
				
			||||||
      "holdings": [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
 | 
					 | 
				
			||||||
          "tokenSymbol": "FSPTST",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "8869964242"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0x724F2910D790B54A39a7638282a45B1D83564fFA",
 | 
					 | 
				
			||||||
          "tokenSymbol": "GEO",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "9884"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0x2105a206B7bec31E2F90acF7385cc8F7F5f9D273",
 | 
					 | 
				
			||||||
          "tokenSymbol": "MFNK",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "19788697"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0x63DE2Ac8D1008351Cc69Fb8aCb94Ba47728a7E83",
 | 
					 | 
				
			||||||
          "tokenSymbol": "MILO",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "75"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
 | 
					 | 
				
			||||||
          "tokenSymbol": "SOHAIL",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "27874115"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          "contractAddress": "0x45d747172e77d55575c197CbA9451bC2CD8F4958",
 | 
					 | 
				
			||||||
          "tokenSymbol": "SRF",
 | 
					 | 
				
			||||||
          "tokenDecimals": "6",
 | 
					 | 
				
			||||||
          "balance": "2745987"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
							
								
								
									
										18
									
								
								services/registration/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								services/registration/Makefile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					# Variables to match files in the current directory
 | 
				
			||||||
 | 
					INPUTS = $(wildcard ./*.vis)
 | 
				
			||||||
 | 
					TXTS = $(wildcard ./*.txt.orig)
 | 
				
			||||||
 | 
					VISE_PATH := ../../go-vise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Rule to build .bin files from .vis files
 | 
				
			||||||
 | 
					%.vis:
 | 
				
			||||||
 | 
						go run  $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
 | 
				
			||||||
 | 
						@echo "Built $(basename $@).bin from $(basename $@).vis"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Rule to copy .orig files to .txt
 | 
				
			||||||
 | 
					%.txt.orig:
 | 
				
			||||||
 | 
						cp -v $(basename $@).orig $(basename $@)
 | 
				
			||||||
 | 
						@echo "Copied $(basename $@).orig to $(basename $@)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 'all' target depends on all .vis and .txt.orig files
 | 
				
			||||||
 | 
					all: $(INPUTS) $(TXTS)
 | 
				
			||||||
 | 
						@echo "Running all: $(INPUTS) $(TXTS)"
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_creation
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_creation
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Your account is being created...
 | 
				
			||||||
							
								
								
									
										4
									
								
								services/registration/account_creation.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								services/registration/account_creation.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					RELOAD verify_pin
 | 
				
			||||||
 | 
					CATCH create_pin_mismatch flag_pin_mismatch 1
 | 
				
			||||||
 | 
					LOAD quit 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_creation_failed
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_creation_failed
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Your account creation request failed. Please try again later.
 | 
				
			||||||
							
								
								
									
										3
									
								
								services/registration/account_creation_failed.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								services/registration/account_creation_failed.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					MOUT quit 9
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP quit 9
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_creation_failed_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_creation_failed_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Ombi lako la kusajiliwa haliwezi kukamilishwa. Tafadhali jaribu tena baadaye.
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_creation_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_creation_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Akaunti yako inatengenezwa...
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					My Account
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Akaunti yangu
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_pending
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_pending
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Your account is still being created.
 | 
				
			||||||
							
								
								
									
										3
									
								
								services/registration/account_pending.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								services/registration/account_pending.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					RELOAD check_account_status
 | 
				
			||||||
 | 
					CATCH main flag_account_success 1
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/account_pending_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/account_pending_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Akaunti yako bado inatengenezwa
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/address
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/address
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Address: {{.check_identifier}}
 | 
				
			||||||
							
								
								
									
										6
									
								
								services/registration/address.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								services/registration/address.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					LOAD check_identifier 0
 | 
				
			||||||
 | 
					RELOAD check_identifier
 | 
				
			||||||
 | 
					MAP check_identifier
 | 
				
			||||||
 | 
					MOUT quit 9
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP quit 9
 | 
				
			||||||
							
								
								
									
										2
									
								
								services/registration/amount
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/amount
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Maximum amount: {{.max_amount}}
 | 
				
			||||||
 | 
					Enter amount:
 | 
				
			||||||
							
								
								
									
										12
									
								
								services/registration/amount.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								services/registration/amount.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					LOAD reset_transaction_amount 0
 | 
				
			||||||
 | 
					LOAD max_amount 10
 | 
				
			||||||
 | 
					MAP max_amount
 | 
				
			||||||
 | 
					MOUT back 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					LOAD validate_amount 64
 | 
				
			||||||
 | 
					RELOAD validate_amount
 | 
				
			||||||
 | 
					CATCH invalid_amount flag_invalid_amount 1
 | 
				
			||||||
 | 
					INCMP _ 0
 | 
				
			||||||
 | 
					LOAD get_recipient 12
 | 
				
			||||||
 | 
					LOAD get_sender 64
 | 
				
			||||||
 | 
					INCMP transaction_pin *
 | 
				
			||||||
							
								
								
									
										2
									
								
								services/registration/amount_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/amount_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Kiwango cha juu: {{.max_amount}}
 | 
				
			||||||
 | 
					Weka kiwango:
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/back_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/back_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Back
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/back_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/back_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Rudi
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/balances
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/balances
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Balances:
 | 
				
			||||||
							
								
								
									
										8
									
								
								services/registration/balances.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								services/registration/balances.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					LOAD reset_account_authorized 0
 | 
				
			||||||
 | 
					MOUT my_balance 1
 | 
				
			||||||
 | 
					MOUT community_balance 2
 | 
				
			||||||
 | 
					MOUT back 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP _ 0
 | 
				
			||||||
 | 
					INCMP my_balance 1
 | 
				
			||||||
 | 
					INCMP community_balance 2
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/balances_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/balances_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Salio
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/change_language_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/change_language_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Change language
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/change_language_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/change_language_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Badili lugha
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/change_pin_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/change_pin_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Change PIN
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/change_pin_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/change_pin_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Badili PIN
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/check_balance_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/check_balance_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Check balances
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/check_balance_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/check_balance_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Angalia salio
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/check_statement_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/check_statement_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Check statement
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/check_statement_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/check_statement_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Taarifa ya matumizi
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/comminity_balance_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/comminity_balance_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Salio la kikundi
 | 
				
			||||||
							
								
								
									
										2
									
								
								services/registration/community_balance
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								services/registration/community_balance
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					Your community balance is: 0.00SRF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								services/registration/community_balance.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								services/registration/community_balance.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					LOAD reset_incorrect 0
 | 
				
			||||||
 | 
					CATCH incorrect_pin flag_incorrect_pin 1
 | 
				
			||||||
 | 
					CATCH pin_entry flag_account_authorized 0
 | 
				
			||||||
 | 
					LOAD quit_with_balance 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/community_balance_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/community_balance_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Community  balance
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/community_balance_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/community_balance_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Salio la kikundi
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/confirm_create_pin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/confirm_create_pin
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Enter your four number PIN again:
 | 
				
			||||||
							
								
								
									
										4
									
								
								services/registration/confirm_create_pin.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								services/registration/confirm_create_pin.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					LOAD save_pin 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					LOAD verify_pin 8
 | 
				
			||||||
 | 
					INCMP account_creation *
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/confirm_create_pin_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/confirm_create_pin_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka PIN yako tena:
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/create_pin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/create_pin
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Please enter a new four number PIN for your account:
 | 
				
			||||||
							
								
								
									
										9
									
								
								services/registration/create_pin.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								services/registration/create_pin.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					LOAD create_account 0
 | 
				
			||||||
 | 
					CATCH account_creation_failed flag_account_creation_failed 1
 | 
				
			||||||
 | 
					MOUT exit 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					LOAD save_pin 0
 | 
				
			||||||
 | 
					RELOAD save_pin
 | 
				
			||||||
 | 
					CATCH . flag_incorrect_pin 1
 | 
				
			||||||
 | 
					INCMP quit 0
 | 
				
			||||||
 | 
					INCMP confirm_create_pin *
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/create_pin_mismatch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/create_pin_mismatch
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					The PIN is not a match. Try again
 | 
				
			||||||
							
								
								
									
										5
									
								
								services/registration/create_pin_mismatch.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								services/registration/create_pin_mismatch.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					MOUT retry 1
 | 
				
			||||||
 | 
					MOUT quit 9
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP confirm_create_pin 1
 | 
				
			||||||
 | 
					INCMP quit 9
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/create_pin_mismatch_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/create_pin_mismatch_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					PIN uliyoweka haifanani. Jaribu tena
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/create_pin_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/create_pin_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Tafadhali weka PIN mpya yenye nambari nne kwa akaunti yako:
 | 
				
			||||||
							
								
								
									
										5
									
								
								services/registration/display_profile_info
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								services/registration/display_profile_info
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					Wasifu wangu
 | 
				
			||||||
 | 
					Name: Not provided
 | 
				
			||||||
 | 
					Gender: Not provided
 | 
				
			||||||
 | 
					Age: Not provided
 | 
				
			||||||
 | 
					Location: Not provided
 | 
				
			||||||
							
								
								
									
										3
									
								
								services/registration/display_profile_info.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								services/registration/display_profile_info.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					MOUT back 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP _ 0
 | 
				
			||||||
							
								
								
									
										0
									
								
								services/registration/display_profile_info_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								services/registration/display_profile_info_swa
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										1
									
								
								services/registration/edit_gender_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_gender_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Edit gender
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_gender_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_gender_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka jinsia
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_location_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_location_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Edit location
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_location_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_location_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka eneo
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_name_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_name_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Edit name
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_name_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_name_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka jina
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_offerings_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_offerings_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Edit offerings
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_offerings_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_offerings_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka unachouza
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_profile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_profile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					My profile
 | 
				
			||||||
							
								
								
									
										20
									
								
								services/registration/edit_profile.vis
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								services/registration/edit_profile.vis
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					LOAD reset_account_authorized 16
 | 
				
			||||||
 | 
					LOAD reset_allow_update 0
 | 
				
			||||||
 | 
					RELOAD reset_allow_update
 | 
				
			||||||
 | 
					MOUT edit_name 1
 | 
				
			||||||
 | 
					MOUT edit_gender 2
 | 
				
			||||||
 | 
					MOUT edit_yob 3
 | 
				
			||||||
 | 
					MOUT edit_location 4
 | 
				
			||||||
 | 
					MOUT edit_offerings 5
 | 
				
			||||||
 | 
					MOUT view 6
 | 
				
			||||||
 | 
					MOUT back 0
 | 
				
			||||||
 | 
					HALT
 | 
				
			||||||
 | 
					INCMP _ 0
 | 
				
			||||||
 | 
					LOAD set_reset_single_edit 0
 | 
				
			||||||
 | 
					RELOAD set_reset_single_edit
 | 
				
			||||||
 | 
					INCMP enter_name 1
 | 
				
			||||||
 | 
					INCMP select_gender 2
 | 
				
			||||||
 | 
					INCMP enter_yob 3
 | 
				
			||||||
 | 
					INCMP enter_location 4
 | 
				
			||||||
 | 
					INCMP enter_offerings 5
 | 
				
			||||||
 | 
					INCMP view_profile 6
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_profile_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_profile_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Wasifu wangu
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_yob_menu
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_yob_menu
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Edit year of birth
 | 
				
			||||||
							
								
								
									
										1
									
								
								services/registration/edit_yob_menu_swa
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								services/registration/edit_yob_menu_swa
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Weka mwaka wa kuzaliwa
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user