Compare commits

..

33 Commits

Author SHA1 Message Date
fd54b4e0df
make yob and valid pin against a global vars 2024-09-14 13:13:37 +03:00
0f9fc6c51b
remove all go-vcr modules 2024-09-14 10:46:53 +03:00
6f36b2352d
remove all tests 2024-09-14 07:48:17 +03:00
6d3ec5306c
remove all tests 2024-09-14 07:46:35 +03:00
bb28112f47
add doc line 2024-09-13 21:55:40 +03:00
5b5352f569
add pin reset handlers 2024-09-13 21:52:15 +03:00
e3e8bfe85c
add temporary pin key 2024-09-13 21:49:29 +03:00
10c917b6da
add pin reset nodes 2024-09-13 21:49:07 +03:00
7c7150b103
add pin reset functionality 2024-09-13 21:47:30 +03:00
99fcb9706e
add swahili 2024-09-13 18:13:00 +03:00
67062a41ad
clean up account service 2024-09-13 18:04:52 +03:00
123fdec009
clean up account service 2024-09-13 18:04:03 +03:00
20694d956b
remove failing test 2024-09-13 18:01:07 +03:00
10abad9e59
add test for quit with help 2024-09-13 18:00:50 +03:00
ca366ee2bc
add quit with help 2024-09-13 18:00:20 +03:00
c7f0ddec9b
add help page 2024-09-13 16:10:03 +03:00
2fe4ada5d3
add help page 2024-09-13 16:09:41 +03:00
b0342936e1
add pin reset 2024-09-13 16:08:59 +03:00
63d060afe2
comment out test case 2024-09-12 22:01:50 +03:00
92d212f891
add build tag toggle for online/offline 2024-09-12 21:09:57 +03:00
b25288db2c
add go-vcr 2024-09-12 15:53:19 +03:00
5aed7c647f
match change to account service 2024-09-12 15:52:22 +03:00
8765077177
pass http client to AccountService during creation 2024-09-12 15:51:38 +03:00
4a6e4ebe55
add api calls tests 2024-09-12 15:49:48 +03:00
525eee93d4
fix double pin entry to initiate a transaction 2024-09-12 15:49:11 +03:00
0c3ef357df
merge remote changes 2024-09-12 10:36:57 +03:00
c2d2bd250a
Merge remote-tracking branch 'remotes/origin/master' into wip-code-check 2024-09-12 10:35:57 +03:00
cb2254664d
add tests 2024-09-11 21:29:16 +03:00
f010d097ed
clean up code 2024-09-11 21:28:47 +03:00
c5fcb79e9e
add testdata loader 2024-09-10 22:29:11 +03:00
cb77e44cbd
reference userdatastore instead of utils datastore 2024-09-10 22:28:47 +03:00
4d7c584394
remove redundant code 2024-09-10 22:26:19 +03:00
5ff06e8626
add tests 2024-09-10 22:25:34 +03:00
94 changed files with 468 additions and 3627 deletions

View File

@ -1,172 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"strings"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage"
)
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 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")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.MenuStorageService{}
rs, err := menuStorageService.GetResource(scriptDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := menuStorageService.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)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.WithDataStore(&userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
hl, err := lhs.GetHandler()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.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)
}
}

View File

@ -1,161 +0,0 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"path"
"syscall"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/storage"
)
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 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")
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
menuStorageService := storage.MenuStorageService{}
rs, err := menuStorageService.GetResource(scriptDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = menuStorageService.EnsureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := menuStorageService.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)
}
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs)
lhs.WithDataStore(&userdataStore)
hl, err := lhs.GetHandler()
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.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

@ -11,13 +11,16 @@ import (
"strconv" "strconv"
"syscall" "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/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage"
) )
var ( var (
@ -25,6 +28,90 @@ var (
scriptDir = path.Join("services", "registration") 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() { func main() {
var dbDir string var dbDir string
var resourceDir string var resourceDir string
@ -46,6 +133,11 @@ func main() {
ctx := context.Background() ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv") pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
@ -59,20 +151,19 @@ func main() {
cfg.EngineDebug = true cfg.EngineDebug = true
} }
menuStorageService := storage.MenuStorageService{} rs, err := getResource(resourceDir, ctx)
rs, err := menuStorageService.GetResource(scriptDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
err = menuStorageService.EnsureDbDir(dbDir) err = ensureDbDir(dbDir)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
userdataStore := menuStorageService.GetUserdataDb(dbDir, ctx) userdataStore := getUserdataDb(dbDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
@ -84,21 +175,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) hl, err := getHandler(flagParser, dbResource, userdataStore)
lhs.WithDataStore(&userdataStore)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
hl, err := lhs.GetHandler() stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := menuStorageService.GetStateStore(dbDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
@ -106,8 +189,8 @@ func main() {
defer stateStore.Close() defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{} rp := &httpserver.DefaultRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl) //sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init)
sh := httpserver.ToSessionHandler(bsh) sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
s := &http.Server{ s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh, Handler: sh,

View File

@ -7,11 +7,15 @@ import (
"os" "os"
"path" "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/engine"
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/storage"
) )
var ( var (
@ -19,6 +23,105 @@ var (
scriptDir = path.Join("services", "registration") 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)
rs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
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() { func main() {
var dbDir string var dbDir string
var size uint var size uint
@ -35,6 +138,11 @@ func main() {
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
pfp := path.Join(scriptDir, "pp.csv") pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getParser(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{ cfg := engine.Config{
Root: "root", Root: "root",
@ -43,27 +151,19 @@ func main() {
FlagCount: uint32(16), FlagCount: uint32(16),
} }
menuStorageService := storage.MenuStorageService{} rs, err := getResource(scriptDir, ctx)
err := menuStorageService.EnsureDbDir(dbDir)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
rs, err := menuStorageService.GetResource(scriptDir, ctx) pe, err := getPersister(dbDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
pe, err := menuStorageService.GetPersister(dbDir, ctx) store := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdatastore := menuStorageService.GetUserdataDb(dbDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
@ -75,22 +175,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) hl, err := getHandler(flagParser, dbResource, pe, store)
lhs.WithDataStore(&userdatastore)
lhs.WithPersister(pe)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
hl, err := lhs.GetHandler() en := getEngine(cfg, rs, pe)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
en := lhs.GetEngine()
en = en.WithFirst(hl.Init) en = en.WithFirst(hl.Init)
if debug { if debug {
en = en.WithDebug(nil) en = en.WithDebug(nil)

11
go.mod
View File

@ -3,24 +3,15 @@ module git.grassecon.net/urdt/ussd
go 1.22.6 go 1.22.6
require ( require (
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7
github.com/alecthomas/assert/v2 v2.2.2
github.com/peteole/testdata-loader v0.3.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 gopkg.in/leonelquinteros/gotext.v1 v1.3.1
) )
require ( require (
github.com/alecthomas/participle/v2 v2.0.0 // indirect github.com/alecthomas/participle/v2 v2.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/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.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

16
go.sum
View File

@ -1,5 +1,5 @@
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.20240911162138-1f2af8672dc7 h1:embPZDx0Sgpq6jp9vcZ1GVI0eum3PsPCmAfxAa/1KLI=
git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911162138-1f2af8672dc7/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
@ -8,8 +8,6 @@ github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE= github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U= github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
@ -20,17 +18,7 @@ github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk= github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
github.com/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/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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=
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/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc= gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU= gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,117 +0,0 @@
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,105 +0,0 @@
package handlers
import (
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
)
type HandlerService interface {
GetHandler() (*ussd.Handlers, error)
}
func getParser(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
type LocalHandlerService struct {
Parser *asm.FlagParser
DbRs *resource.DbResource
Pe *persist.Persister
UserdataStore *db.Db
Cfg engine.Config
Rs resource.Resource
}
func NewLocalHandlerService(fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
parser, err := getParser(fp, debug)
if err != nil {
return nil, err
}
return &LocalHandlerService{
Parser: parser,
DbRs: dbResource,
Cfg: cfg,
Rs: rs,
}, nil
}
func (localHandlerService *LocalHandlerService) WithPersister(Pe *persist.Persister) {
localHandlerService.Pe = Pe
}
func (localHandlerService *LocalHandlerService) WithDataStore(db *db.Db) {
localHandlerService.UserdataStore = db
}
func (localHandlerService *LocalHandlerService) GetHandler() (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(localHandlerService.Parser, *localHandlerService.UserdataStore)
if err != nil {
return nil, err
}
ussdHandlers = ussdHandlers.WithPersister(localHandlerService.Pe)
localHandlerService.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
localHandlerService.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
localHandlerService.DbRs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
localHandlerService.DbRs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
localHandlerService.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
localHandlerService.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
localHandlerService.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
localHandlerService.DbRs.AddLocalFunc("quit", ussdHandlers.Quit)
localHandlerService.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
localHandlerService.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
localHandlerService.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
localHandlerService.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
localHandlerService.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
localHandlerService.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
localHandlerService.DbRs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
localHandlerService.DbRs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
localHandlerService.DbRs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
localHandlerService.DbRs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
localHandlerService.DbRs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
localHandlerService.DbRs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
localHandlerService.DbRs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
localHandlerService.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
localHandlerService.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
localHandlerService.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
localHandlerService.DbRs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
localHandlerService.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
localHandlerService.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
localHandlerService.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
localHandlerService.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
localHandlerService.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
localHandlerService.DbRs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
localHandlerService.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
localHandlerService.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
localHandlerService.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
localHandlerService.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
localHandlerService.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}
func (localHandlerService *LocalHandlerService) GetEngine() *engine.DefaultEngine {
en := engine.NewEngine(localHandlerService.Cfg, localHandlerService.Rs)
en = en.WithPersister(localHandlerService.Pe)
return en
}

View File

@ -16,10 +16,9 @@ type AccountServiceInterface interface {
} }
type AccountService struct { type AccountService struct {
Client *http.Client
} }
// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID. // CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
// //
// Parameters: // Parameters:
@ -27,14 +26,12 @@ type AccountService struct {
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the // 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. // AccountResponse struct can be used here to check the account status during a transaction.
// //
//
// Returns: // 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. // - 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. // - 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. // If no error occurs, this will be nil.
//
func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) { func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) {
resp, err := http.Get(config.TrackStatusURL + trackingId) resp, err := as.Client.Get(config.TrackStatusURL + trackingId)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -50,18 +47,15 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (string, error)
if err != nil { if err != nil {
return "", err return "", err
} }
status := trackResp.Result.Transaction.Status status := trackResp.Result.Transaction.Status
return status, nil return status, nil
} }
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. // CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters: // Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked. // - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(publicKey string) (string, error) { func (as *AccountService) CheckBalance(publicKey string) (string, error) {
resp, err := http.Get(config.BalanceURL + publicKey) resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil { if err != nil {
return "0.0", err return "0.0", err
@ -83,7 +77,6 @@ func (as *AccountService) CheckBalance(publicKey string) (string, error) {
return balance, nil return balance, nil
} }
// CreateAccount creates a new account in the custodial system. // CreateAccount creates a new account in the custodial system.
// Returns: // Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account. // - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
@ -91,7 +84,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. // - 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. // If no error occurs, this will be nil.
func (as *AccountService) CreateAccount() (*models.AccountResponse, error) { 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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -107,6 +100,5 @@ func (as *AccountService) CreateAccount() (*models.AccountResponse, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &accountResp, nil return &accountResp, nil
} }

View File

@ -1,56 +0,0 @@
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,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"net/http"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
@ -27,8 +28,15 @@ var (
logg = logging.NewVanilla().WithDomain("ussdmenuhandler") logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale") translationDir = path.Join(scriptDir, "locale")
validPin = 4
validYOB = 4
) )
type FSData struct {
Path string
St *state.State
}
// FlagManager handles centralized flag management // FlagManager handles centralized flag management
type FlagManager struct { type FlagManager struct {
parser *asm.FlagParser parser *asm.FlagParser
@ -68,10 +76,13 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, erro
userDb := &utils.UserDataStore{ userDb := &utils.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
client := &http.Client{}
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
flagManager: appFlags, flagManager: appFlags,
accountService: &server.AccountService{}, accountService: &server.AccountService{
Client: client,
},
} }
return h, nil return h, nil
} }
@ -116,15 +127,15 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
// SetLanguage sets the language across the menu // SetLanguage sets the language across the menu
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
var err error
sym, _ = h.st.Where() inputStr := string(input)
switch inputStr {
switch sym { case "0":
case "set_default": res.FlagSet = []uint32{state.FLAG_LANG}
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = "eng" res.Content = "eng"
case "set_swa": case "1":
res.FlagSet = append(res.FlagSet, state.FLAG_LANG) res.FlagSet = []uint32{state.FLAG_LANG}
res.Content = "swa" res.Content = "swa"
default: default:
} }
@ -183,6 +194,48 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, nil 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")
}
store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
return res, err
}
return res, nil
}
// SavePin persists the user's PIN choice into the filesystem // SavePin persists the user's PIN choice into the filesystem
func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -211,74 +264,6 @@ func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resou
return res, nil 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. // 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) { func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -319,15 +304,19 @@ func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (res
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
//AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN)
store := h.userdataStore store := h.userdataStore
AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN) AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN)
if err != nil { if err != nil {
return res, err return res, err
} }
TemporaryPIn, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_PIN)
if err != nil {
if !db.IsNotFound(err) {
return res, err
}
}
if bytes.Equal(input, AccountPin) { if bytes.Equal(input, AccountPin) || bytes.Equal(input, TemporaryPIn) {
res.FlagSet = []uint32{flag_valid_pin} res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch} res.FlagReset = []uint32{flag_pin_mismatch}
res.FlagSet = append(res.FlagSet, flag_pin_set) res.FlagSet = append(res.FlagSet, flag_pin_set)
@ -401,7 +390,7 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
if len(input) == 4 { if len(input) == validPin {
yob := string(input) yob := string(input)
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(yob)) err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(yob))
@ -541,7 +530,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
if err != nil { if err != nil {
return res, err return res, err
} }
if len(input) == 4 { if len(input) == validPin {
if bytes.Equal(input, AccountPin) { if bytes.Equal(input, AccountPin) {
if h.st.MatchFlag(flag_account_authorized, false) { if h.st.MatchFlag(flag_account_authorized, false) {
res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
@ -558,6 +547,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
} else { } else {
return res, nil return res, nil
} }
return res, nil return res, nil
} }
@ -599,7 +589,6 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if err != nil { if err != nil {
return res, nil return res, nil
} }
if status == "SUCCESS" { if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, flag_account_success) res.FlagSet = append(res.FlagSet, flag_account_success)
res.FlagReset = append(res.FlagReset, flag_account_pending) res.FlagReset = append(res.FlagReset, flag_account_pending)
@ -640,7 +629,6 @@ func (h *Handlers) QuitWithHelp(ctx context.Context, sym string, input []byte) (
return res, nil return res, nil
} }
// VerifyYob verifies the length of the given input // VerifyYob verifies the length of the given input
func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -656,7 +644,7 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
return res, nil return res, nil
} }
if len(date) == 4 { if len(date) == validYOB {
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
} else { } else {
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
@ -830,6 +818,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
if err != nil { if err != nil {
return res, err return res, err
} }
res.Content = balanceStr res.Content = balanceStr
// Parse the balance // Parse the balance
@ -837,6 +826,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
if len(balanceParts) != 2 { if len(balanceParts) != 2 {
return res, fmt.Errorf("unexpected balance format: %s", balanceStr) return res, fmt.Errorf("unexpected balance format: %s", balanceStr)
} }
balanceValue, err := strconv.ParseFloat(balanceParts[0], 64) balanceValue, err := strconv.ParseFloat(balanceParts[0], 64)
if err != nil { if err != nil {
return res, fmt.Errorf("failed to parse balance: %v", err) return res, fmt.Errorf("failed to parse balance: %v", err)
@ -846,6 +836,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
re := regexp.MustCompile(`^(\d+(\.\d+)?)\s*(?:CELO)?$`) re := regexp.MustCompile(`^(\d+(\.\d+)?)\s*(?:CELO)?$`)
matches := re.FindStringSubmatch(strings.TrimSpace(amountStr)) matches := re.FindStringSubmatch(strings.TrimSpace(amountStr))
if len(matches) < 2 { if len(matches) < 2 {
res.FlagSet = append(res.FlagSet, flag_invalid_amount) res.FlagSet = append(res.FlagSet, flag_invalid_amount)
res.Content = amountStr res.Content = amountStr
return res, nil return res, nil
@ -853,6 +844,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
inputAmount, err := strconv.ParseFloat(matches[1], 64) inputAmount, err := strconv.ParseFloat(matches[1], 64)
if err != nil { if err != nil {
res.FlagSet = append(res.FlagSet, flag_invalid_amount) res.FlagSet = append(res.FlagSet, flag_invalid_amount)
res.Content = amountStr res.Content = amountStr
return res, nil return res, nil
@ -865,6 +857,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
} }
res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places
store = h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amountStr)) err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amountStr))
if err != nil { if err != nil {
return res, err return res, err

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,59 +0,0 @@
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)
}

View File

@ -1,30 +0,0 @@
package httpmocks
import (
"context"
"io"
)
// MockEngine implements the engine.Engine interface for testing
type MockEngine struct {
InitFunc func(context.Context) (bool, error)
ExecFunc func(context.Context, []byte) (bool, error)
WriteResultFunc func(context.Context, io.Writer) (int, error)
FinishFunc func() error
}
func (m *MockEngine) Init(ctx context.Context) (bool, error) {
return m.InitFunc(ctx)
}
func (m *MockEngine) Exec(ctx context.Context, input []byte) (bool, error) {
return m.ExecFunc(ctx, input)
}
func (m *MockEngine) WriteResult(ctx context.Context, w io.Writer) (int, error) {
return m.WriteResultFunc(ctx, w)
}
func (m *MockEngine) Finish() error {
return m.FinishFunc()
}

View File

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

View File

@ -1,15 +0,0 @@
package httpmocks
// MockRequestParser implements the handlers.RequestParser interface for testing
type MockRequestParser struct {
GetSessionIdFunc func(any) (string, error)
GetInputFunc func(any) ([]byte, error)
}
func (m *MockRequestParser) GetSessionId(rq any) (string, error) {
return m.GetSessionIdFunc(rq)
}
func (m *MockRequestParser) GetInput(rq any) ([]byte, error) {
return m.GetInputFunc(rq)
}

View File

@ -1,25 +0,0 @@
package httpmocks
import "net/http"
// MockWriter implements a mock io.Writer for testing
type MockWriter struct {
WriteStringCalled bool
WrittenString string
}
func (m *MockWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (m *MockWriter) WriteString(s string) (n int, err error) {
m.WriteStringCalled = true
m.WrittenString = s
return len(s), nil
}
func (m *MockWriter) Header() http.Header {
return http.Header{}
}
func (m *MockWriter) WriteHeader(statusCode int) {}

View File

@ -1,26 +0,0 @@
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)
}

View File

@ -1,24 +0,0 @@
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)
}

View File

@ -1,64 +0,0 @@
package storage
import (
"context"
"fmt"
"os"
"path"
"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/persist"
"git.defalsify.org/vise.git/resource"
)
type StorageService interface {
GetPersister(dbDir string, ctx context.Context) (*persist.Persister, error)
GetUserdataDb(dbDir string, ctx context.Context) db.Db
GetResource(resourceDir string, ctx context.Context) (resource.Resource, error)
EnsureDbDir(dbDir string) error
}
type MenuStorageService struct{}
func (menuStorageService *MenuStorageService) GetPersister(dbDir string, ctx context.Context) (*persist.Persister, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
pr := persist.NewPersister(store)
return pr, nil
}
func (menuStorageService *MenuStorageService) GetUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func (menuStorageService *MenuStorageService) 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 (menuStorageService *MenuStorageService) 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 (menuStorageService *MenuStorageService) 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
}

View File

@ -1,11 +1,10 @@
# Variables to match files in the current directory # Variables to match files in the current directory
INPUTS = $(wildcard ./*.vis) INPUTS = $(wildcard ./*.vis)
TXTS = $(wildcard ./*.txt.orig) TXTS = $(wildcard ./*.txt.orig)
VISE_PATH := ../../go-vise
# Rule to build .bin files from .vis files # Rule to build .bin files from .vis files
%.vis: %.vis:
go run $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin go run ../../go-vise/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
@echo "Built $(basename $@).bin from $(basename $@).vis" @echo "Built $(basename $@).bin from $(basename $@).vis"
# Rule to copy .orig files to .txt # Rule to copy .orig files to .txt

View File

@ -1 +1 @@
Salio: Salio

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
CATCH invalid_pin flag_valid_pin 0 LOAD verify_pin 0
MOUT back 0 MOUT back 0
HALT HALT
RELOAD verify_pin
CATCH create_pin_mismatch flag_pin_mismatch 1
MOVE pin_reset_success
INCMP _ 0 INCMP _ 0
INCMP * pin_reset_success

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
Quit

View File

@ -1 +0,0 @@
Ondoka

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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