Compare commits

..

50 Commits

Author SHA1 Message Date
Carlosokumu
2cd0b4434f clean -up tests 2024-09-18 15:51:44 +03:00
Carlosokumu
c735bf688d update tests 2024-09-18 11:26:18 +03:00
4098ac8a19 Merge pull request 'change-language' (#58) from change-language into master
Reviewed-on: #58
2024-09-17 18:31:10 +02:00
2982f08b41 Merge branch 'master' into change-language 2024-09-17 18:30:52 +02:00
alfred-mk
75ab9c66a3 run TestSetLanguage with execPath instead of input 2024-09-17 18:54:07 +03:00
95f02231b3 Merge pull request 'Unit tests' (#62) from wip-unit-tests into master
Reviewed-on: #62
2024-09-17 15:55:33 +02:00
Carlosokumu
599f7a2857 add more tests 2024-09-17 15:44:22 +03:00
alfred-mk
2e7c07e6f4 added missing functions to the other getHandlers 2024-09-17 15:31:44 +03:00
alfred-mk
01f7571185 use dedicated nodes to set the language 2024-09-17 15:29:21 +03:00
alfred-mk
065c8e5c1d use a single set_language function for setting and changing language 2024-09-17 15:26:50 +03:00
alfred-mk
dc14480519 remove extra space 2024-09-16 17:29:27 +03:00
alfred-mk
aaf4923f64 ensure a PIN is set before changing the language 2024-09-16 17:29:27 +03:00
alfred-mk
e9c645bd87 added the profile info on the swa menu template 2024-09-16 17:29:27 +03:00
alfred-mk
1be6da9139 remove new lines on templates and menus to clean up the empty spaces on the menu 2024-09-16 17:29:27 +03:00
alfred-mk
fa2930d93a added quit menu to handle eng and swa 2024-09-16 17:29:27 +03:00
alfred-mk
a409e292ab add the change_language functionality to the account menu 2024-09-16 17:29:27 +03:00
alfred-mk
00c86a2850 change language 2024-09-16 17:29:27 +03:00
alfred-mk
4daac7e90b added change_language node and templates 2024-09-16 17:29:27 +03:00
alfred-mk
06e23565df added the SetNewLanguage function 2024-09-16 17:29:27 +03:00
b19188165b Merge pull request 'wip-menu-help' (#56) from wip-menu-help into master
Reviewed-on: #56
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-16 16:21:49 +02:00
Carlosokumu
2ed9f083bb add quit with helpline info
add quit with helpline info
2024-09-16 15:17:56 +01:00
e323ffa078 Merge pull request 'Pin reset' (#59) from wip-pin-reset into master
Reviewed-on: #59
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-16 16:14:52 +02:00
68de83af97 Merge pull request 'Allow setting go-vise path for asm executable in makefile' (#57) from lash/vise-make-var into master
Reviewed-on: #57
2024-09-16 15:49:58 +02:00
Carlosokumu
a3f2b23128 add pin reset templates for swahili 2024-09-16 15:17:15 +03:00
Carlosokumu
09f970db9b navigate to main menu after successful pin reset 2024-09-16 14:59:26 +03:00
Carlosokumu
04edd90c21 fix sink issue onback option 2024-09-16 14:58:56 +03:00
Carlosokumu
a3f410875a define temporary key value 2024-09-16 14:39:42 +03:00
Carlosokumu
3f3e98e637 add pin reset handlers 2024-09-16 14:39:01 +03:00
Carlosokumu
660f8b7aa2 add pin reset nodes 2024-09-16 14:30:47 +03:00
lash
8ae3372c36 Allow setting go-vise path for asm executable in makefile 2024-09-14 21:34:11 +01:00
3a7c3ffc67 Merge pull request 'wip-unit-tests' (#55) from wip-unit-tests into master
Reviewed-on: #55
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-14 22:29:52 +02:00
Carlosokumu
78c033e61c remove deprecated code 2024-09-14 20:59:08 +03:00
Carlosokumu
b5ef483f34 clean up tests 2024-09-14 20:07:17 +03:00
Carlosokumu
006eef0a28 add testdata loader 2024-09-14 20:02:21 +03:00
Carlosokumu
e99a788c60 add tests 2024-09-14 20:01:58 +03:00
b5d33a98f0 Merge pull request 'Enable CLI driver of async session' (#49) from lash/async-driver into master
Reviewed-on: #49
2024-09-14 16:52:06 +02:00
f4f475bd45 Merge pull request 'africas-talking' (#50) from africas-talking into lash/async-driver
Reviewed-on: #50
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-14 16:28:05 +02:00
6fe2e7287f Update the func name to Output
Signed-off-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-09-14 16:01:18 +02:00
alfred-mk
1e9c9cf6ad removed AT specific code 2024-09-14 15:57:16 +03:00
alfred-mk
3cb0b099e4 use the new AtSessionHandler 2024-09-14 15:55:45 +03:00
alfred-mk
b53658e038 have AtOutput as an option 2024-09-13 16:03:31 +03:00
alfred-mk
512460fdeb Added Custom AtOutput to append CON or END to output 2024-09-13 16:02:04 +03:00
alfred-mk
762f90adf6 Added Continue bool to track whether the execution should be terminated 2024-09-12 23:52:35 +03:00
lash
9b4a4eeaf4 Temporary solution for make sure storage object gets put back in all cases of execution 2024-09-12 16:46:11 +01:00
alfred-mk
5e4e6a21a0 Added africastalking binary 2024-09-12 15:56:57 +03:00
alfred-mk
ffeb28e851 Use latest go-vise package 2024-09-12 15:56:34 +03:00
lash
9c751aff30 Update go-vise 2024-09-12 04:19:13 +01:00
lash
b31d3b907a Add shutdown to async, rehabilitate http cmd 2024-09-12 04:13:57 +01:00
lash
d49f866ca4 Factor out methods common to http and async cli 2024-09-12 04:07:55 +01:00
lash
dde9f552a6 Isolate http specific parts to minimal 2024-09-12 03:30:23 +01:00
89 changed files with 1296 additions and 646 deletions

261
cmd/africastalking/main.go Normal file
View File

@@ -0,0 +1,261 @@
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("set_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)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
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)
}
}

255
cmd/async/main.go Normal file
View File

@@ -0,0 +1,255 @@
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("set_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)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
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)
}
}
}

View File

@@ -20,6 +20,7 @@ import (
"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"
)
@@ -43,7 +44,7 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore
if err != nil {
return nil, err
}
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
@@ -75,6 +76,10 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}
@@ -112,6 +117,7 @@ func getResource(resourceDir string, ctx context.Context) (resource.Resource, er
return rfs, nil
}
func main() {
var dbDir string
var resourceDir string
@@ -189,8 +195,8 @@ func main() {
defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{}
//sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init)
sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
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,

View File

@@ -39,7 +39,7 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P
return nil, err
}
ussdHandlers = ussdHandlers.WithPersister(pe)
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
@@ -71,7 +71,10 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}

4
go.mod
View File

@@ -3,14 +3,12 @@ module git.grassecon.net/urdt/ussd
go 1.22.6
require (
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f
github.com/alecthomas/assert/v2 v2.2.2
github.com/peteole/testdata-loader v0.3.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)
require gopkg.in/dnaeon/go-vcr.v4 v4.0.1 // indirect
require (
github.com/alecthomas/participle/v2 v2.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect

6
go.sum
View File

@@ -1,5 +1,5 @@
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7 h1:embPZDx0Sgpq6jp9vcZ1GVI0eum3PsPCmAfxAa/1KLI=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
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=
@@ -30,8 +30,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/dnaeon/go-vcr.v4 v4.0.1 h1:dIFuOqqDZIJ9BTcK+DXmElzypQ6PV9fBQZSIwY+J1yM=
gopkg.in/dnaeon/go-vcr.v4 v4.0.1/go.mod h1:65yxh9goQVrudqofKtHA4JNFWd6XZRkWfKN4YpMx7KI=
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=

117
internal/handlers/base.go Normal file
View 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
}

View File

@@ -1,129 +0,0 @@
//go:build !online
// +build !online
package server
import (
"testing"
"github.com/alecthomas/assert/v2"
"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
)
func TestCheckBalanceOffline(t *testing.T) {
r, err := recorder.New("custodial/balance")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
tests := []struct {
name string
balance string
publicKey string
}{
{
name: "Test check balance with correct public key",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF002",
balance: "3.06000000003 CELO",
},
{
name: "Test check balance with public key that doesn't exist in the custodial system",
balance: "",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF00",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
balance, err := as.CheckBalance(tt.publicKey)
if err != nil {
t.Fatalf("Failed to get balance with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, balance, tt.balance, "Expected balance and actual balance should be equal")
})
}
}
func TestCheckAccountStatusOffline(t *testing.T) {
r, err := recorder.New("custodial/status")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
tests := []struct {
name string
status string
trackingId string
}{
{
name: "Test check status with tracking id that exists in the custodial system",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e18",
status: "SUCCESS",
},
{
name: "Test check status with tracking id that doesn't exist in the custodial system",
status: "",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, err := as.CheckAccountStatus(tt.trackingId)
if err != nil {
t.Fatalf("Failed to account status with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, status, tt.status, "Expected status and actual status should be equal")
})
}
}
func TestCreateAccountOffline(t *testing.T) {
r, err := recorder.New("custodial/create")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
accountRes, err := as.CreateAccount()
if err != nil {
t.Fatalf("Failed to create an account with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, accountRes.Ok, true, "account response status is true")
}

View File

@@ -1,130 +0,0 @@
//go:build online
// +build online
package server
import (
"net/http"
"testing"
"github.com/alecthomas/assert/v2"
"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
)
func TestCheckBalance(t *testing.T) {
r, err := recorder.New("custodial/balance")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := &http.Client{}
as := AccountService{
Client: client,
}
tests := []struct {
name string
balance string
publicKey string
}{
{
name: "Test check balance with correct public key",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF002",
balance: "3.06000000003 CELO",
},
{
name: "Test check balance with public key that doesn't exist in the custodial system",
balance: "",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF00",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
balance, err := as.CheckBalance(tt.publicKey)
if err != nil {
t.Fatalf("Failed to get balance with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, balance, tt.balance, "Expected balance and actual balance should be equal")
})
}
}
func TestCheckAccountStatus(t *testing.T) {
r, err := recorder.New("custodial/status")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := &http.Client{}
as := AccountService{
Client: client,
}
tests := []struct {
name string
status string
trackingId string
}{
{
name: "Test check status with tracking id that exists in the custodial system",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e18",
status: "SUCCESS",
},
{
name: "Test check status with tracking id that doesn't exist in the custodial system",
status: "",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, err := as.CheckAccountStatus(tt.trackingId)
if err != nil {
t.Fatalf("Failed to account status with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, status, tt.status, "Expected status and actual status should be equal")
})
}
}
func TestCreateAccount(t *testing.T) {
r, err := recorder.New("custodial/create")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := &http.Client{}
as := AccountService{
Client: client,
}
accountRes, err := as.CreateAccount()
if err != nil {
t.Fatalf("Failed to create an account with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, accountRes.Ok, true, "account response status is true")
}

View File

@@ -16,7 +16,6 @@ type AccountServiceInterface interface {
}
type AccountService struct {
Client *http.Client
}
@@ -35,8 +34,7 @@ type AccountService struct {
// If no error occurs, this will be nil.
//
func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) {
resp,err := as.Client.Get(config.TrackStatusURL + trackingId)
// resp, err := http.Get(config.TrackStatusURL + trackingId)
resp, err := http.Get(config.TrackStatusURL + trackingId)
if err != nil {
return "", err
}
@@ -64,8 +62,7 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (string, error)
// - 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)
resp, err := as.Client.Get(config.BalanceURL + publicKey)
resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil {
return "0.0", err
}
@@ -94,8 +91,7 @@ func (as *AccountService) CheckBalance(publicKey string) (string, error) {
// - 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)
resp, err := as.Client.Post(config.CreateAccountURL, "application/json", nil)
resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
if err != nil {
return nil, err
}

View File

@@ -1,126 +0,0 @@
package server
import (
"testing"
"github.com/alecthomas/assert/v2"
"gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
)
func TestCheckBalance(t *testing.T) {
r, err := recorder.New("custodial/balance")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
tests := []struct {
name string
balance string
publicKey string
}{
{
name: "Test check balance with correct public key",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF002",
balance: "3.06000000003 CELO",
},
{
name: "Test check balance with public key that doesn't exist in the custodial system",
balance: "",
publicKey: "0x216a4A64E1e699F9d65Dd9CbD0058dAB21DeF00",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
balance, err := as.CheckBalance(tt.publicKey)
if err != nil {
t.Fatalf("Failed to get balance with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, balance, tt.balance, "Expected balance and actual balance should be equal")
})
}
}
func TestCheckAccountStatus(t *testing.T) {
r, err := recorder.New("custodial/status")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
tests := []struct {
name string
status string
trackingId string
}{
{
name: "Test check status with tracking id that exists in the custodial system",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e18",
status: "SUCCESS",
},
{
name: "Test check status with tracking id that doesn't exist in the custodial system",
status: "",
trackingId: "bb23945b-65cd-4110-ac2e-a5df40572e1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, err := as.CheckAccountStatus(tt.trackingId)
if err != nil {
t.Fatalf("Failed to account status with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, status, tt.status, "Expected status and actual status should be equal")
})
}
}
func TestCreateAccount(t *testing.T) {
r, err := recorder.New("custodial/create")
if err != nil {
t.Fatal(err)
}
defer r.Stop()
client := r.GetDefaultClient()
as := AccountService{
Client: client,
}
accountRes, err := as.CreateAccount()
if err != nil {
t.Fatalf("Failed to create an account with error %s", err)
}
if err != nil {
return
}
assert.NoError(t, err)
assert.Equal(t, accountRes.Ok, true, "account response status is true")
}

View 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()
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"fmt"
"net/http"
"path"
"regexp"
"strconv"
@@ -74,13 +73,10 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, erro
userDb := &utils.UserDataStore{
Db: userdataStore,
}
client := &http.Client{}
h := &Handlers{
userdataStore: userDb,
flagManager: appFlags,
accountService: &server.AccountService{
Client: client,
},
userdataStore: userDb,
flagManager: appFlags,
accountService: &server.AccountService{},
}
return h, nil
}
@@ -125,15 +121,15 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
// 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}
sym, _ = h.st.Where()
switch sym {
case "set_default":
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = "eng"
case "1":
res.FlagSet = []uint32{state.FLAG_LANG}
case "set_swa":
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = "swa"
default:
}
@@ -220,6 +216,74 @@ func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resou
return res, nil
}
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
_, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
pinInput := string(input)
// Validate that the PIN is a 4-digit number
if isValidPIN(pinInput) {
res.FlagSet = append(res.FlagSet, flag_valid_pin)
} else {
res.FlagReset = append(res.FlagReset, flag_valid_pin)
}
return res, nil
}
func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_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
}
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(accountPIN))
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
if err != nil {
return res, err
}
if bytes.Equal(temporaryPin, input) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin))
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
@@ -326,9 +390,6 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
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")
}
@@ -502,7 +563,6 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
} else {
return res, nil
}
return res, nil
}
@@ -544,6 +604,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
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)
@@ -569,6 +630,22 @@ func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource
return res, nil
}
// QuitWithHelp displays helpline information then exits the menu
func (h *Handlers) QuitWithHelp(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("For more help,please call: 0757628885")
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
@@ -758,7 +835,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
if err != nil {
return res, err
}
res.Content = balanceStr
// Parse the balance
@@ -766,7 +842,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
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)
@@ -776,7 +851,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
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
@@ -784,7 +858,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
inputAmount, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
res.Content = amountStr
return res, nil
@@ -797,7 +870,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
}
res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places
store = h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amountStr))
if err != nil {
return res, err

View File

@@ -349,7 +349,7 @@ func TestSaveGender(t *testing.T) {
}
// Call the method
_, err := h.SaveGender(ctx, "someSym", tt.input)
_, err := h.SaveGender(ctx, "save_gender", tt.input)
// Assert no error
assert.NoError(t, err)
@@ -538,64 +538,56 @@ func TestSetLanguage(t *testing.T) {
}
// Define test cases
tests := []struct {
name string
input []byte
expectedFlags []uint32
expectedResult resource.Result
flagManagerResponse uint32
flagManagerError error
name string
execPath []string
expectedResult resource.Result
}{
{
name: "English language",
input: []byte("0"),
expectedFlags: []uint32{state.FLAG_LANG, 123},
name: "Set Default Language (English)",
execPath: []string{"set_default"},
expectedResult: resource.Result{
FlagSet: []uint32{state.FLAG_LANG, 8},
Content: "eng",
},
flagManagerResponse: 123,
flagManagerError: nil,
},
{
name: "Swahili language",
input: []byte("1"),
expectedFlags: []uint32{state.FLAG_LANG, 123},
name: "Set Swahili Language",
execPath: []string{"set_swa"},
expectedResult: resource.Result{
FlagSet: []uint32{state.FLAG_LANG, 8},
Content: "swa",
},
flagManagerResponse: 123,
flagManagerError: nil,
},
{
name: "Unhandled Input",
input: []byte("3"),
expectedFlags: []uint32{123},
name: "Unhandled path",
execPath: []string{""},
expectedResult: resource.Result{
FlagSet: []uint32{8},
},
flagManagerResponse: 123,
flagManagerError: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockState := state.NewState(16)
// Set the ExecPath
mockState.ExecPath = tt.execPath
// Create the Handlers instance with the mock flag manager
h := &Handlers{
flagManager: fm.parser,
st: mockState,
}
// Call the method
res, err := h.SetLanguage(context.Background(), "set_language", tt.input)
res, err := h.SetLanguage(context.Background(), "set_language", nil)
if err != nil {
t.Error(err)
}
// Assert that the Result FlagSet has the required flags after language switch
assert.Equal(t, res, tt.expectedResult, "Flags should be equal to account created")
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
})
}
@@ -1109,27 +1101,26 @@ func TestCheckAccountStatus(t *testing.T) {
FlagReset: []uint32{flag_account_pending},
},
},
// {
// name: "Test when account status is not Success",
// input: []byte("TrackingId1234"),
// status: "REVERTED",
// expectedResult: resource.Result{
// FlagReset: []uint32{flag_account_pending},
// FlagSet: []uint32{flag_account_success},
// },
// },
{
name: "Test when account status is not a success",
input: []byte("TrackingId12"),
status: "REVERTED",
expectedResult: resource.Result{
FlagSet: []uint32{flag_account_success},
FlagReset: []uint32{flag_account_pending},
},
},
}
typ := utils.DATA_TRACKING_ID
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.status, nil)
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(tt.status)).Return(nil).Maybe()
// Define expected interactions with the mock
mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return(tt.input, nil)
mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.status, nil)
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(tt.status)).Return(nil)
// Call the method under test
res, _ := h.CheckAccountStatus(ctx, "check_status", tt.input)
@@ -1306,7 +1297,6 @@ func TestInitiateTransaction(t *testing.T) {
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.PublicKey, nil)
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_AMOUNT).Return(tt.Amount, nil)
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_RECIPIENT).Return(tt.Recipient, nil)
//mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, []byte("")).Return(nil)
// Call the method under test
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input)
@@ -1497,7 +1487,7 @@ func TestValidateAmount(t *testing.T) {
if err != nil {
t.Logf(err.Error())
}
//flag_invalid_amount, _ := fm.parser.GetFlag("flag_invalid_amount")
flag_invalid_amount, _ := fm.parser.GetFlag("flag_invalid_amount")
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
@@ -1526,26 +1516,26 @@ func TestValidateAmount(t *testing.T) {
Content: "0.001",
},
},
// {
// name: "Test with amount larger than balance",
// input: []byte("0.02"),
// balance: "0.003 CELO",
// publicKey: []byte("0xrqeqrequuq"),
// expectedResult: resource.Result{
// FlagSet: []uint32{flag_invalid_amount},
// Content: "0.02",
// },
// },
// {
// name: "Test with invalid amount",
// input: []byte("0.02ms"),
// balance: "0.003 CELO",
// publicKey: []byte("0xrqeqrequuq"),
// expectedResult: resource.Result{
// FlagSet: []uint32{flag_invalid_amount},
// Content: "0.02ms",
// },
// },
{
name: "Test with amount larger than balance",
input: []byte("0.02"),
balance: "0.003 CELO",
publicKey: []byte("0xrqeqrequuq"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_amount},
Content: "0.02",
},
},
{
name: "Test with invalid amount",
input: []byte("0.02ms"),
balance: "0.003 CELO",
publicKey: []byte("0xrqeqrequuq"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_amount},
Content: "0.02ms",
},
},
}
for _, tt := range tests {
@@ -1553,7 +1543,7 @@ func TestValidateAmount(t *testing.T) {
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.publicKey, nil)
mockCreateAccountService.On("CheckBalance", string(tt.publicKey)).Return(tt.balance, nil)
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, tt.input).Return(nil)
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, tt.input).Return(nil).Maybe()
// Call the method under test
res, _ := h.ValidateAmount(ctx, "test_validate_amount", tt.input)
@@ -1688,17 +1678,6 @@ func TestGetProfile(t *testing.T) {
),
},
},
// {
// name: "Test with yob not provided",
// keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB},
// profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "Not Provided"},
// result: resource.Result{
// Content: fmt.Sprintf(
// "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n",
// "John Doee", "Male", "Not Provided", "Kilifi", "Bananas",
// ),
// },
// },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -1716,3 +1695,156 @@ func TestGetProfile(t *testing.T) {
})
}
}
func TestVerifyNewPin(t *testing.T) {
sessionId := "session123"
fm, _ := NewFlagManager(flagsPath)
flag_valid_pin, _ := fm.parser.GetFlag("flag_valid_pin")
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
h := &Handlers{
userdataStore: mockDataStore,
flagManager: fm.parser,
accountService: mockCreateAccountService,
}
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct {
name string
input []byte
expectedResult resource.Result
}{
{
name: "Test with valid pin",
input: []byte("1234"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_valid_pin},
},
},
{
name: "Test with invalid pin",
input: []byte("123"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_valid_pin},
},
},
{
name: "Test with invalid pin",
input: []byte("12345"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_valid_pin},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
//Call the function under test
res, _ := h.VerifyNewPin(ctx, "verify_new_pin", tt.input)
// Assert that expectations were met
mockDataStore.AssertExpectations(t)
//Assert that the result set to content is what was expected
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
})
}
}
func TestSaveTemporaryPIn(t *testing.T) {
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
// Create a new instance of UserDataStore
mockStore := new(mocks.MockUserDataStore)
// Define test data
sessionId := "session123"
PIN := "1234"
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
// Set up the expected behavior of the mock
mockStore.On("WriteEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN, []byte(PIN)).Return(nil)
// Create the Handlers instance with the mock store
h := &Handlers{
userdataStore: mockStore,
flagManager: fm.parser,
}
// Call the method
res, err := h.SaveTemporaryPin(ctx, "save_temporary_pin", []byte(PIN))
// Assert results
assert.NoError(t, err)
assert.Equal(t, resource.Result{}, res)
// Assert all expectations were met
mockStore.AssertExpectations(t)
}
func TestConfirmPin(t *testing.T) {
sessionId := "session123"
fm, _ := NewFlagManager(flagsPath)
flag_pin_mismatch, _ := fm.parser.GetFlag("flag_pin_mismatch")
mockDataStore := new(mocks.MockUserDataStore)
mockCreateAccountService := new(mocks.MockAccountService)
h := &Handlers{
userdataStore: mockDataStore,
flagManager: fm.parser,
accountService: mockCreateAccountService,
}
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
tests := []struct {
name string
input []byte
temporarypin []byte
expectedResult resource.Result
}{
{
name: "Test with correct pin confirmation",
input: []byte("1234"),
temporarypin: []byte("1234"),
expectedResult: resource.Result{
FlagReset: []uint32{flag_pin_mismatch},
},
},
{
name: "Test with different pin confirmation",
input: []byte("1234"),
temporarypin: []byte("12345"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_pin_mismatch},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set up the expected behavior of the mock
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(tt.temporarypin)).Return(nil).Maybe()
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TEMPORARY_PIN).Return(tt.temporarypin, nil)
//Call the function under test
res, _ := h.ConfirmPinChange(ctx, "confirm_pin_change", tt.temporarypin)
// Assert that expectations were met
mockDataStore.AssertExpectations(t)
//Assert that the result set to content is what was expected
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
})
}
}

View 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
}

View File

@@ -1,42 +1,42 @@
package http
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"git.defalsify.org/vise.git/db"
"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"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
var (
logg = logging.NewVanilla().WithDomain("httpserver")
)
type RequestParser interface {
GetSessionId(rq *http.Request) (string, error)
GetInput(rq *http.Request) ([]byte, error)
}
type DefaultRequestParser struct {
}
func(rp *DefaultRequestParser) GetSessionId(rq *http.Request) (string, error) {
v := rq.Header.Get("X-Vise-Session")
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 "", fmt.Errorf("no session found")
return "", handlers.ErrSessionMissing
}
return v, nil
}
func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
defer rq.Body.Close()
v, err := ioutil.ReadAll(rq.Body)
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
}
@@ -44,107 +44,79 @@ func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
}
type SessionHandler struct {
cfgTemplate engine.Config
rp RequestParser
rs resource.Resource
//first resource.EntryFunc
hn *ussd.Handlers
provider StorageProvider
handlers.RequestHandler
}
//func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, first resource.EntryFunc) *SessionHandler {
func NewSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *SessionHandler {
func ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
return &SessionHandler{
cfgTemplate: cfg,
rs: rs,
//first: first,
hn: hn,
rp: rp,
provider: NewSimpleStorageProvider(stateDb, userdataDb),
RequestHandler: h,
}
}
func(f *SessionHandler) writeError(w http.ResponseWriter, code int, msg string, err error) {
w.Header().Set("X-Vise", msg + ": " + err.Error())
w.Header().Set("Content-Length", "0")
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)
w.Header().Set("X-Vise", err.Error())
}
return
}
func(f* SessionHandler) Shutdown() {
err := f.provider.Close()
if err != nil {
logg.Errorf("handler shutdown error", "err", err)
}
}
func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var r bool
sessionId, err := f.rp.GetSessionId(req)
if err != nil {
f.writeError(w, 400, "Session missing", err)
return
}
input, err := f.rp.GetInput(req)
if err != nil {
f.writeError(w, 400, "Input read fail", err)
return
}
ctx := req.Context()
cfg := f.cfgTemplate
cfg.SessionId = sessionId
var code int
var err error
var perr error
logg.InfoCtxf(ctx, "new request", "session", cfg.SessionId, "input", input)
storage, err := f.provider.Get(cfg.SessionId)
if err != nil {
f.writeError(w, 500, "Storage retrieval fail", err)
return
}
f.hn = f.hn.WithPersister(storage.Persister)
defer f.provider.Put(cfg.SessionId, storage)
en := getEngine(cfg, f.rs, storage.Persister)
en = en.WithFirst(f.hn.Init)
if cfg.EngineDebug {
en = en.WithDebug(nil)
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
r, err = en.Init(ctx)
rp := f.GetRequestParser()
cfg := f.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req)
if err != nil {
f.writeError(w, 500, "Engine init fail", err)
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
}
if r && len(input) > 0 {
r, err = en.Exec(ctx, input)
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 err != nil {
f.writeError(w, 500, "Engine exec fail", err)
if code != 200 {
f.writeError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
_, err = en.WriteResult(ctx, w)
rqs, err = f.Output(rqs)
rqs, perr = f.Reset(rqs)
if err != nil {
f.writeError(w, 500, "Write result fail", err)
f.writeError(w, 500, err)
return
}
err = en.Finish()
if err != nil {
f.writeError(w, 500, "Engine finish fail", err)
if perr != nil {
f.writeError(w, 500, perr)
return
}
_ = r
}
func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr)
return en
}

View File

@@ -1,4 +1,4 @@
package http
package storage
import (
"git.defalsify.org/vise.git/db"
@@ -11,31 +11,31 @@ type Storage struct {
}
type StorageProvider interface {
Get(sessionId string) (Storage, error)
Put(sessionId string, storage Storage) error
Get(sessionId string) (*Storage, error)
Put(sessionId string, storage *Storage) error
Close() error
}
type SimpleStorageProvider struct {
Storage
*Storage
}
func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider {
pe := persist.NewPersister(stateStore)
pe = pe.WithFlush()
return &SimpleStorageProvider{
Storage: Storage{
Storage: &Storage{
Persister: pe,
UserdataDb: userdataStore,
},
}
}
func (p *SimpleStorageProvider) Get(sessionId string) (Storage, error) {
func (p *SimpleStorageProvider) Get(sessionId string) (*Storage, error) {
return p.Storage, nil
}
func (p *SimpleStorageProvider) Put(sessionId string, storage Storage) error {
func (p *SimpleStorageProvider) Put(sessionId string, storage *Storage) error {
return nil
}

View File

@@ -22,6 +22,7 @@ const (
DATA_OFFERINGS
DATA_RECIPIENT
DATA_AMOUNT
DATA_TEMPORARY_PIN
)
func typToBytes(typ DataTyp) []byte {

View File

@@ -1,10 +1,11 @@
# 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 ../../go-vise/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
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

View File

@@ -1 +1 @@
Rudi
Rudi

View File

@@ -1 +1 @@
Balances:
Balances:

View File

@@ -1 +1 @@
Salio
Salio:

View File

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

View File

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

View File

@@ -1 +1 @@
Badili lugha
Badili lugha

View File

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

View File

@@ -1 +1 @@
Change PIN
Change PIN

View File

@@ -1 +1 @@
Badili PIN
Badili PIN

View File

@@ -1 +1 @@
Check statement
Check statement

View File

@@ -1,2 +1 @@
Your community balance is: 0.00SRF
Your community balance is: 0.00SRF

View File

@@ -1 +1 @@
Community balance
Community balance

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
Edit name
Edit name

View File

@@ -1 +1 @@
Weka jina
Weka jina

View File

@@ -1 +1 @@
Edit offerings
Edit offerings

View File

@@ -1 +1 @@
Enter family name:
Enter family name:

View File

@@ -1 +1 @@
Enter your location:
Enter your location:

View File

@@ -1 +1 @@
Weka majina yako ya kwanza:
Weka majina yako ya kwanza:

View File

@@ -1 +1 @@
Female
Female

View File

@@ -1 +1 @@
Guard my PIN
Guard my PIN

View File

@@ -1 +1 @@
Linda PIN yangu
Linda PIN yangu

View File

@@ -1,2 +1,2 @@
The year of birth you entered is invalid.
Please try again.
Please try again.

View File

@@ -1 +1 @@
Incorrect pin
Incorrect pin

View File

@@ -0,0 +1 @@
The PIN you entered is invalid.The PIN must be different from your current PIN.For help call +254757628885

View File

@@ -0,0 +1,3 @@
MOUT back 0
HALT
INCMP _ 0

View File

@@ -0,0 +1 @@
PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.

View File

@@ -0,0 +1 @@
Your language change request was successful.

View File

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

View File

@@ -0,0 +1 @@
Ombi lako la kubadilisha lugha limefanikiwa.

View File

@@ -6,3 +6,7 @@ msgstr "Ombi lako limetumwa. %s atapokea %s kutoka kwa %s."
msgid "Thank you for using Sarafu. Goodbye!"
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
msgid "For more help,please call: 0757628885"
msgstr "Kwa usaidizi zaidi,piga: 0757628885"

View File

@@ -9,6 +9,7 @@ MOUT back 0
HALT
INCMP _ 0
INCMP edit_profile 1
INCMP change_language 2
INCMP balances 3
INCMP pin_management 5
INCMP address 6

View File

@@ -1 +1 @@
Anwani yangu
Anwani yangu

View File

@@ -1 +1 @@
Salio lako ni: 0.00 SRF
Salio lako ni: 0.00 SRF

View File

@@ -1,3 +1,13 @@
LOAD authorize_account 12
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
CATCH old_pin flag_allow_update 0
MOUT back 0
HALT
INCMP _ 0
LOAD save_temporary_pin 6
LOAD verify_new_pin 0
RELOAD save_temporary_pin
RELOAD verify_new_pin
INCMP * confirm_pin_change

View File

@@ -0,0 +1,2 @@
Weka PIN mpya ya nne nambari:

View File

@@ -1 +1 @@
no
no

View File

@@ -1 +1 @@
la
la

View File

@@ -1,9 +1,7 @@
LOAD authorize_account 6
LOAD reset_allow_update 0
MOUT back 0
HALT
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
MOVE new_pin
RELOAD reset_allow_update
INCMP _ 0
INCMP new_pin *

View File

@@ -0,0 +1 @@
Weka PIN yako ya zamani:

View File

@@ -1 +1 @@
Tafadhali weka PIN yako
Tafadhali weka PIN yako

View File

@@ -1 +1 @@
PIN Management
PIN Management

View File

@@ -5,3 +5,4 @@ MOUT back 0
HALT
INCMP _ 0
INCMP old_pin 1

View File

@@ -0,0 +1 @@
The PIN is not a match. Try again

View File

@@ -0,0 +1,6 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP confirm_pin_change 1
INCMP quit 9

View File

@@ -0,0 +1 @@
Your PIN change request has been successful

View File

@@ -0,0 +1,10 @@
LOAD confirm_pin_change 0
RELOAD confirm_pin_change
CATCH pin_reset_mismatch flag_pin_mismatch 1
MOUT back 0
MOUT quit 9
HALT
INCMP main 0
INCMP quit 9

View File

@@ -0,0 +1 @@
Ombi lako la kubadili PIN limefanikiwa

View File

@@ -1 +1 @@
Profile
Profile

View File

@@ -1 +1 @@
Wasifu wangu
Wasifu wangu

View File

@@ -0,0 +1 @@
Quit

View File

@@ -0,0 +1 @@
Ondoka

View File

@@ -1 +1 @@
Badili PIN ya mwenzio
Badili PIN ya mwenzio

View File

@@ -1,6 +1,6 @@
MOUT english 0
MOUT kiswahili 1
HALT
INCMP terms 0
INCMP terms 1
INCMP set_default 0
INCMP set_swa 1
INCMP . *

View File

@@ -1 +1 @@
Enter recipient's phone number:
Enter recipient's phone number:

View File

@@ -0,0 +1,3 @@
LOAD set_language 6
CATCH terms flag_account_created 0
MOVE language_changed

View File

@@ -0,0 +1,3 @@
LOAD set_language 6
CATCH terms flag_account_created 0
MOVE language_changed

View File

@@ -1,5 +1,3 @@
LOAD select_language 0
RELOAD select_language
MOUT yes 0
MOUT no 1
HALT

View File

@@ -1,2 +1,2 @@
{{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}}
Please enter your PIN to confirm:
Please enter your PIN to confirm:

View File

@@ -1,4 +1,3 @@
LOAD authorize_account 6
MAP validate_amount
RELOAD get_recipient
MAP get_recipient
@@ -7,9 +6,9 @@ MAP get_sender
MOUT back 0
MOUT quit 9
HALT
LOAD authorize_account 6
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
CATCH transaction_initiated flag_account_authorized 1
INCMP _ 0
INCMP quit 9
INCMP transaction_initiated *

View File

@@ -1 +1 @@
Unspecified
Unspecified

View File

@@ -1 +1 @@
Profile updated successfully
Profile updated successfully

View File

@@ -1 +1 @@
Akaunti imeupdatiwa
Akaunti imeupdatiwa

View File

@@ -1,2 +1,2 @@
My profile:
{{.get_profile_info}}
My profile:
{{.get_profile_info}}

View File

@@ -1 +1,2 @@
Wasifu wangu
Wasifu wangu:
{{.get_profile_info}}

View File

@@ -1 +1 @@
Angalia Wasifu
Angalia Wasifu

View File

@@ -1 +1 @@
yes
yes

View File

@@ -1 +1 @@
ndio
ndio