Compare commits

...

43 Commits

Author SHA1 Message Date
Carlosokumu
bb28112f47 add doc line 2024-09-13 21:55:40 +03:00
Carlosokumu
5b5352f569 add pin reset handlers 2024-09-13 21:52:15 +03:00
Carlosokumu
e3e8bfe85c add temporary pin key 2024-09-13 21:49:29 +03:00
Carlosokumu
10c917b6da add pin reset nodes 2024-09-13 21:49:07 +03:00
Carlosokumu
7c7150b103 add pin reset functionality 2024-09-13 21:47:30 +03:00
Carlosokumu
99fcb9706e add swahili 2024-09-13 18:13:00 +03:00
Carlosokumu
67062a41ad clean up account service 2024-09-13 18:04:52 +03:00
Carlosokumu
123fdec009 clean up account service 2024-09-13 18:04:03 +03:00
Carlosokumu
20694d956b remove failing test 2024-09-13 18:01:07 +03:00
Carlosokumu
10abad9e59 add test for quit with help 2024-09-13 18:00:50 +03:00
Carlosokumu
ca366ee2bc add quit with help 2024-09-13 18:00:20 +03:00
Carlosokumu
c7f0ddec9b add help page 2024-09-13 16:10:03 +03:00
Carlosokumu
2fe4ada5d3 add help page 2024-09-13 16:09:41 +03:00
Carlosokumu
b0342936e1 add pin reset 2024-09-13 16:08:59 +03:00
Carlosokumu
63d060afe2 comment out test case 2024-09-12 22:01:50 +03:00
Carlosokumu
92d212f891 add build tag toggle for online/offline 2024-09-12 21:09:57 +03:00
Carlosokumu
b25288db2c add go-vcr 2024-09-12 15:53:19 +03:00
Carlosokumu
5aed7c647f match change to account service 2024-09-12 15:52:22 +03:00
Carlosokumu
8765077177 pass http client to AccountService during creation 2024-09-12 15:51:38 +03:00
Carlosokumu
4a6e4ebe55 add api calls tests 2024-09-12 15:49:48 +03:00
Carlosokumu
525eee93d4 fix double pin entry to initiate a transaction 2024-09-12 15:49:11 +03:00
Carlosokumu
0c3ef357df merge remote changes 2024-09-12 10:36:57 +03:00
Carlosokumu
c2d2bd250a Merge remote-tracking branch 'remotes/origin/master' into wip-code-check 2024-09-12 10:35:57 +03:00
0f9b5551ec Merge pull request 'Implement http server for the URDT vise engine' (#41) from lash/http-server into master
Reviewed-on: urdt/ussd#41
2024-09-12 00:13:14 +02:00
Carlosokumu
cb2254664d add tests 2024-09-11 21:29:16 +03:00
Carlosokumu
f010d097ed clean up code 2024-09-11 21:28:47 +03:00
lash
514e043e38 Fix symptom of handler missing persister 2024-09-11 17:53:12 +01:00
lash
836e5fe8ee Revert africas talking changes in http 2024-09-11 17:32:55 +01:00
lash
660fcaa0b6 Import go-vise fixing missing exit on state reset 2024-09-11 17:25:56 +01:00
alfred-mk
44015b1c76 Parse the request body to get the PhoneNumber and Input text 2024-09-11 15:40:49 +03:00
lash
7438531900 Update govise to include removed dead asm code 2024-09-11 04:10:05 +01:00
lash
681f293d3c Externalize requestparser, flush persister on http request end 2024-09-10 23:09:10 +01:00
lash
8e3ff27bb8 Ensure db close on http signal shutdown, correct stores to provider 2024-09-10 20:44:10 +01:00
Carlosokumu
c5fcb79e9e add testdata loader 2024-09-10 22:29:11 +03:00
Carlosokumu
cb77e44cbd reference userdatastore instead of utils datastore 2024-09-10 22:28:47 +03:00
Carlosokumu
4d7c584394 remove redundant code 2024-09-10 22:26:19 +03:00
Carlosokumu
5ff06e8626 add tests 2024-09-10 22:25:34 +03:00
lash
dd2468a4d7 Http server harness
Add storage retrieval solution for http handler

Successfully executed account regisration using http

Set upstream go-vise dependency version in go.mod

Adapt menuhandler to upstream
2024-09-10 13:59:36 +01:00
63cee42261 Merge pull request 'wip-code-check' (#44) from wip-code-check into master
Reviewed-on: urdt/ussd#44
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-10 14:25:50 +02:00
Carlosokumu
9f034967b5 remove resource directory 2024-09-10 12:32:19 +03:00
Carlosokumu
ad48890a9f remove deprecated code 2024-09-10 11:24:09 +03:00
Carlosokumu
c0a3ad7e2b delete deprecated code 2024-09-10 11:23:41 +03:00
Carlosokumu
a3dffdf4e9 match code refactor 2024-09-10 11:23:25 +03:00
28 changed files with 2064 additions and 238 deletions

215
cmd/http/main.go Normal file
View File

@@ -0,0 +1,215 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
}
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
return store, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func main() {
var dbDir string
var resourceDir string
var size uint
var engineDebug bool
var stateDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.UintVar(&port, "p", 7123, "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
rs, err := getResource(resourceDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = ensureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{}
//sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl.Init)
sh := httpserver.NewSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
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

@@ -34,10 +34,11 @@ func getParser(fp string, debug bool) (*asm.FlagParser, error) {
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) { func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, pe, userdataStore) ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ussdHandlers = ussdHandlers.WithPersister(pe)
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin) rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
@@ -70,14 +71,25 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit) rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) 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 return ussdHandlers, nil
} }
func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) { func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700) err := os.MkdirAll(dbDir, 0700)
if err != nil { if err != nil {
return nil, fmt.Errorf("state dir create exited with error: %v\n", err) 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() store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm") storeFile := path.Join(dbDir, "state.gdbm")
@@ -112,18 +124,16 @@ func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *
func main() { func main() {
var dbDir string var dbDir string
var resourceDir string
var size uint var size uint
var sessionId string var sessionId string
var debug bool var debug bool
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&debug, "d", false, "use engine debug output") flag.BoolVar(&debug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output") flag.UintVar(&size, "s", 160, "max size of output")
flag.Parse() flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) logg.Infof("start command", "dbdir", dbDir, "outputsize", size)
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
@@ -141,13 +151,13 @@ func main() {
FlagCount: uint32(16), FlagCount: uint32(16),
} }
rs, err := getResource(resourceDir, ctx) rs, err := 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)
} }
pr, err := getPersister(dbDir, 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)
@@ -161,16 +171,17 @@ func main() {
dbResource, ok := rs.(*resource.DbResource) dbResource, ok := rs.(*resource.DbResource)
if !ok { if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
hl, err := getHandler(flagParser, dbResource, pr, store) hl, err := getHandler(flagParser, dbResource, pe, store)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
en := getEngine(cfg, rs, pr) en := getEngine(cfg, rs, pe)
en = en.WithFirst(hl.Init) en = en.WithFirst(hl.Init)
if debug { if debug {
en = en.WithDebug(nil) en = en.WithDebug(nil)

15
go.mod
View File

@@ -2,6 +2,15 @@ module git.grassecon.net/urdt/ussd
go 1.22.6 go 1.22.6
require (
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
)
require gopkg.in/dnaeon/go-vcr.v4 v4.0.1 // indirect
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/alecthomas/repr v0.2.0 // indirect
@@ -17,9 +26,3 @@ require (
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
require (
git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89
github.com/alecthomas/assert/v2 v2.2.2
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)

6
go.sum
View File

@@ -1,5 +1,5 @@
git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89 h1:YyQODhMwSM5YD9yKHM5jCF0HC0RQtE3MkVXcTnOhXJo= 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.1.0.20240906020635-400f69d01a89/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=
@@ -30,6 +30,8 @@ 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

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,15 +77,14 @@ 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.
// If there is an error during the request or processing, this will be nil. // If there is an error during the request or processing, this will be nil.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data. // - 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

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"net/http"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
@@ -66,21 +67,20 @@ type Handlers struct {
accountService server.AccountServiceInterface accountService server.AccountServiceInterface
} }
func NewHandlers(parser *asm.FlagParser, pe *persist.Persister, userdataStore db.Db) (*Handlers, error) { func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db) (*Handlers, error) {
userDb := utils.UserDataStore{
Store: userdataStore,
}
if pe == nil {
return nil, fmt.Errorf("cannot create handler with nil persister")
}
if userdataStore == nil { if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store") return nil, fmt.Errorf("cannot create handler with nil userdata store")
} }
userDb := &utils.UserDataStore{
Db: userdataStore,
}
client := &http.Client{}
h := &Handlers{ h := &Handlers{
pe: pe, userdataStore: userDb,
userdataStore: userDb, flagManager: appFlags,
flagManager: parser, accountService: &server.AccountService{
accountService: &server.AccountService{}, Client: client,
},
} }
return h, nil return h, nil
} }
@@ -94,6 +94,14 @@ func isValidPIN(pin string) bool {
return match return match
} }
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
if h.pe != nil {
panic("persister already set")
}
h.pe = pe
return h
}
func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var r resource.Result var r resource.Result
@@ -184,6 +192,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
@@ -252,15 +302,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")
} }
store := h.userdataStore
//AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN)
store := h.userdataStore.(utils.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)
@@ -477,22 +531,19 @@ 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 err == nil { if bytes.Equal(input, AccountPin) {
if len(input) == 4 { if h.st.MatchFlag(flag_account_authorized, false) {
if bytes.Equal(input, AccountPin) { res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
if h.st.MatchFlag(flag_account_authorized, false) { res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized)
res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized)
} else {
res.FlagSet = append(res.FlagSet, flag_allow_update)
res.FlagReset = append(res.FlagReset, flag_account_authorized)
}
} else { } else {
res.FlagSet = append(res.FlagSet, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_allow_update)
res.FlagReset = append(res.FlagReset, flag_account_authorized) res.FlagReset = append(res.FlagReset, flag_account_authorized)
return res, nil
} }
} else {
res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
res.FlagReset = append(res.FlagReset, flag_account_authorized)
return res, nil
} }
} else { } else {
return res, nil return res, nil
@@ -523,7 +574,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore.(utils.UserDataStore) store := h.userdataStore
trackingId, err := store.ReadEntry(ctx, sessionId, utils.DATA_TRACKING_ID) trackingId, err := store.ReadEntry(ctx, sessionId, utils.DATA_TRACKING_ID)
if err != nil { if err != nil {
return res, err return res, err
@@ -539,7 +590,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)
@@ -565,6 +615,21 @@ func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource
return res, nil 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 // 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
@@ -610,7 +675,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore.(utils.UserDataStore) store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
@@ -744,7 +809,8 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) store := h.userdataStore
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
amountStr := string(input) amountStr := string(input)
@@ -753,6 +819,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
@@ -760,6 +827,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)
@@ -769,6 +837,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
@@ -776,6 +845,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
@@ -788,7 +858,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 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
@@ -862,7 +932,7 @@ func (h *Handlers) QuitWithBalance(ctx context.Context, sym string, input []byte
l := gotext.NewLocale(translationDir, code) l := gotext.NewLocale(translationDir, code)
l.AddDomain("default") l.AddDomain("default")
store := h.userdataStore.(utils.UserDataStore) store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err

File diff suppressed because it is too large Load Diff

View File

@@ -3,28 +3,16 @@ package mocks
import ( import (
"context" "context"
"git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/db"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
type MockUserDataStore struct { type MockUserDataStore struct {
db.Db
mock.Mock mock.Mock
} }
func (m *MockUserDataStore) SetPrefix(prefix uint8) {
m.Called(prefix)
}
func (m *MockUserDataStore) SetSession(sessionId string) {
m.Called(sessionId)
}
func (m *MockUserDataStore) Get(ctx context.Context, key []byte) ([]byte, error) {
args := m.Called(ctx, key)
return args.Get(0).([]byte), args.Error(1)
}
func (m *MockUserDataStore) ReadEntry(ctx context.Context, sessionId string, typ utils.DataTyp) ([]byte, error) { func (m *MockUserDataStore) ReadEntry(ctx context.Context, sessionId string, typ utils.DataTyp) ([]byte, error) {
args := m.Called(ctx, sessionId, typ) args := m.Called(ctx, sessionId, typ)
return args.Get(0).([]byte), args.Error(1) return args.Get(0).([]byte), args.Error(1)
@@ -34,36 +22,3 @@ func (m *MockUserDataStore) WriteEntry(ctx context.Context, sessionId string, ty
args := m.Called(ctx, sessionId, typ, value) args := m.Called(ctx, sessionId, typ, value)
return args.Error(0) return args.Error(0)
} }
func (m *MockUserDataStore) Prefix() uint8 {
args := m.Called()
return args.Get(0).(uint8)
}
func (m *MockUserDataStore) Safe() bool {
args := m.Called()
return args.Get(0).(bool)
}
func (m *MockUserDataStore) SetLanguage(language *lang.Language) {
m.Called(language)
}
func (m *MockUserDataStore) SetLock(uint8, bool) error {
args := m.Called()
return args.Error(0)
}
func (m *MockUserDataStore) Connect(ctx context.Context, connectionStr string) error {
args := m.Called(ctx, connectionStr)
return args.Error(0)
}
func (m *MockUserDataStore) Put(ctx context.Context, key, value []byte) error {
args := m.Called(ctx, key, value)
return args.Error(0)
}
func (m *MockUserDataStore) Close() error {
args := m.Called(nil)
return args.Error(0)
}

150
internal/http/server.go Normal file
View File

@@ -0,0 +1,150 @@
package http
import (
"fmt"
"io/ioutil"
"net/http"
"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"
)
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")
if v == "" {
return "", fmt.Errorf("no session found")
}
return v, nil
}
func(rp *DefaultRequestParser) GetInput(rq *http.Request) ([]byte, error) {
defer rq.Body.Close()
v, err := ioutil.ReadAll(rq.Body)
if err != nil {
return nil, err
}
return v, nil
}
type SessionHandler struct {
cfgTemplate engine.Config
rp RequestParser
rs resource.Resource
//first resource.EntryFunc
hn *ussd.Handlers
provider StorageProvider
}
//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{
cfgTemplate: cfg,
rs: rs,
//first: first,
hn: hn,
rp: rp,
provider: NewSimpleStorageProvider(stateDb, userdataDb),
}
}
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")
w.WriteHeader(code)
_, err = w.Write([]byte{})
if err != nil {
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
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)
}
r, err = en.Init(ctx)
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
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
_, err = en.WriteResult(ctx, w)
if err != nil {
f.writeError(w, 500, "Write result fail", err)
return
}
err = en.Finish()
if err != nil {
f.writeError(w, 500, "Engine finish fail", err)
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
}

44
internal/http/storage.go Normal file
View File

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

View File

@@ -1,44 +0,0 @@
package utils
import (
"context"
"encoding/json"
"git.defalsify.org/vise.git/db"
)
type AccountFileHandler struct {
store db.Db
}
func NewAccountFileHandler(store db.Db) *AccountFileHandler {
return &AccountFileHandler{
store: store,
}
}
func (afh *AccountFileHandler) ReadAccountData(ctx context.Context, sessionId string) (map[string]string, error) {
var accountData map[string]string
jsonData, err := ReadEntry(ctx, afh.store, sessionId, DATA_ACCOUNT)
if err != nil {
return nil,err
}
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return nil, err
}
return accountData, nil
}
func (afh *AccountFileHandler) WriteAccountData(ctx context.Context, sessionId string, accountData map[string]string) error {
_, err := json.Marshal(accountData)
if err != nil {
return err
}
return nil
}
func (afh *AccountFileHandler) EnsureFileExists() error {
return nil
}

View File

@@ -1,10 +1,7 @@
package utils package utils
import ( import (
"context"
"encoding/binary" "encoding/binary"
"git.defalsify.org/vise.git/db"
) )
type DataTyp uint16 type DataTyp uint16
@@ -25,6 +22,7 @@ const (
DATA_OFFERINGS DATA_OFFERINGS
DATA_RECIPIENT DATA_RECIPIENT
DATA_AMOUNT DATA_AMOUNT
DATA_TEMPORARY_PIN
) )
func typToBytes(typ DataTyp) []byte { func typToBytes(typ DataTyp) []byte {
@@ -37,22 +35,3 @@ func PackKey(typ DataTyp, data []byte) []byte {
v := typToBytes(typ) v := typToBytes(typ)
return append(v, data...) return append(v, data...)
} }
func ReadEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
b, err := store.Get(ctx, k)
if err != nil {
return nil, err
}
return b, nil
}
func WriteEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId))
return store.Put(ctx, k, value)
}

View File

@@ -4,78 +4,29 @@ import (
"context" "context"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/lang"
) )
type DataStore interface { type DataStore interface {
SetPrefix(prefix uint8) db.Db
SetSession(sessionId string)
Get(ctx context.Context, key []byte) ([]byte, error)
ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error)
WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error
Connect(ctx context.Context, connStr string) error
SetLanguage(*lang.Language)
Close() error
Prefix() uint8
Put(ctx context.Context, key []byte, val []byte) error
Safe() bool
SetLock(typ uint8, locked bool) error
} }
type UserDataStore struct { type UserDataStore struct {
Store db.Db db.Db
}
func (store UserDataStore) SetPrefix(prefix uint8) {
store.Store.SetPrefix(prefix)
}
func (store UserDataStore) SetLanguage(lang *lang.Language) {
store.Store.SetLanguage(lang)
}
func (store UserDataStore) SetLock(typ uint8, locked bool) error {
return store.Store.SetLock(typ, locked)
}
func (store UserDataStore) Safe() bool {
return store.Store.Safe()
}
func (store UserDataStore) Put(ctx context.Context, key []byte, val []byte) error {
return store.Store.Put(ctx, key, val)
}
func (store UserDataStore) Connect(ctx context.Context, connectionStr string) error {
return store.Store.Connect(ctx, connectionStr)
}
func (store UserDataStore) Close() error {
return store.Store.Close()
}
func (store UserDataStore) Prefix() uint8 {
return store.Store.Prefix()
}
func (store UserDataStore) SetSession(sessionId string) {
store.Store.SetSession(sessionId)
}
func (store UserDataStore) Get(ctx context.Context, key []byte) ([]byte, error) {
return store.Store.Get(ctx, key)
} }
// ReadEntry retrieves an entry from the store based on the provided parameters. // ReadEntry retrieves an entry from the store based on the provided parameters.
func (store UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) { func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.Store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.Store.SetSession(sessionId) store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId)) k := PackKey(typ, []byte(sessionId))
return store.Get(ctx, k) return store.Get(ctx, k)
} }
func (store UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error { func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.Store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.Store.SetSession(sessionId) store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId)) k := PackKey(typ, []byte(sessionId))
return store.Store.Put(ctx, k, value) return store.Put(ctx, k, value)
} }

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
LOAD quit_with_help 0
HALT

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,4 @@
MOUT back 0
HALT
INCMP _ 0

View File

@@ -6,3 +6,6 @@ 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"
msgstr "Kwa usaidizi zaidi,piga: 0757628885"

View File

@@ -10,6 +10,6 @@ HALT
INCMP send 1 INCMP send 1
INCMP quit 2 INCMP quit 2
INCMP my_account 3 INCMP my_account 3
INCMP quit 4 INCMP help 4
INCMP quit 9 INCMP quit 9
INCMP . * INCMP . *

View File

@@ -0,0 +1 @@
Enter a new four number pin

View File

@@ -0,0 +1,7 @@
LOAD save_temporary_pin 0
MOUT back 0
HALT
RELOAD save_temporary_pin
CATCH invalid_pin flag_incorrect_pin 1
INCMP _ 0
MOVE confirm_pin_change

View File

@@ -0,0 +1 @@
Enter your old PIN

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
LOAD confirm_pin_change 0
MOUT back 0
MOUT quit 9
HALT
INCMP _ 0
INCMP quit 9

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 *