Compare commits

..
Author SHA1 Message Date
alfred-mk 75ab9c66a3 run TestSetLanguage with execPath instead of input 2024-09-17 18:54:07 +03:00
alfred-mk 2e7c07e6f4 added missing functions to the other getHandlers 2024-09-17 15:31:44 +03:00
alfred-mk 01f7571185 use dedicated nodes to set the language 2024-09-17 15:29:21 +03:00
alfred-mk 065c8e5c1d use a single set_language function for setting and changing language 2024-09-17 15:26:50 +03:00
alfred-mk dc14480519 remove extra space 2024-09-16 17:29:27 +03:00
alfred-mk aaf4923f64 ensure a PIN is set before changing the language 2024-09-16 17:29:27 +03:00
alfred-mk e9c645bd87 added the profile info on the swa menu template 2024-09-16 17:29:27 +03:00
alfred-mk 1be6da9139 remove new lines on templates and menus to clean up the empty spaces on the menu 2024-09-16 17:29:27 +03:00
alfred-mk fa2930d93a added quit menu to handle eng and swa 2024-09-16 17:29:27 +03:00
alfred-mk a409e292ab add the change_language functionality to the account menu 2024-09-16 17:29:27 +03:00
alfred-mk 00c86a2850 change language 2024-09-16 17:29:27 +03:00
alfred-mk 4daac7e90b added change_language node and templates 2024-09-16 17:29:27 +03:00
alfred-mk 06e23565df added the SetNewLanguage function 2024-09-16 17:29:27 +03:00
lash b19188165b Merge pull request 'wip-menu-help' (#56) from wip-menu-help into master
Reviewed-on: urdt/ussd#56
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-16 16:21:49 +02:00
Carlosokumu 2ed9f083bb add quit with helpline info
add quit with helpline info
2024-09-16 15:17:56 +01:00
lash e323ffa078 Merge pull request 'Pin reset' (#59) from wip-pin-reset into master
Reviewed-on: urdt/ussd#59
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-16 16:14:52 +02:00
lash 68de83af97 Merge pull request 'Allow setting go-vise path for asm executable in makefile' (#57) from lash/vise-make-var into master
Reviewed-on: urdt/ussd#57
2024-09-16 15:49:58 +02:00
Carlosokumu a3f2b23128 add pin reset templates for swahili 2024-09-16 15:17:15 +03:00
Carlosokumu 09f970db9b navigate to main menu after successful pin reset 2024-09-16 14:59:26 +03:00
Carlosokumu 04edd90c21 fix sink issue onback option 2024-09-16 14:58:56 +03:00
Carlosokumu a3f410875a define temporary key value 2024-09-16 14:39:42 +03:00
Carlosokumu 3f3e98e637 add pin reset handlers 2024-09-16 14:39:01 +03:00
Carlosokumu 660f8b7aa2 add pin reset nodes 2024-09-16 14:30:47 +03:00
lash 8ae3372c36 Allow setting go-vise path for asm executable in makefile 2024-09-14 21:34:11 +01:00
lash 3a7c3ffc67 Merge pull request 'wip-unit-tests' (#55) from wip-unit-tests into master
Reviewed-on: urdt/ussd#55
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-14 22:29:52 +02:00
Carlosokumu 78c033e61c remove deprecated code 2024-09-14 20:59:08 +03:00
Carlosokumu b5ef483f34 clean up tests 2024-09-14 20:07:17 +03:00
Carlosokumu 006eef0a28 add testdata loader 2024-09-14 20:02:21 +03:00
Carlosokumu e99a788c60 add tests 2024-09-14 20:01:58 +03:00
lash b5d33a98f0 Merge pull request 'Enable CLI driver of async session' (#49) from lash/async-driver into master
Reviewed-on: urdt/ussd#49
2024-09-14 16:52:06 +02:00
lash f4f475bd45 Merge pull request 'africas-talking' (#50) from africas-talking into lash/async-driver
Reviewed-on: urdt/ussd#50
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-09-14 16:28:05 +02:00
Alfred-mk 6fe2e7287f Update the func name to Output
Signed-off-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-09-14 16:01:18 +02:00
alfred-mk 1e9c9cf6ad removed AT specific code 2024-09-14 15:57:16 +03:00
alfred-mk 3cb0b099e4 use the new AtSessionHandler 2024-09-14 15:55:45 +03:00
alfred-mk b53658e038 have AtOutput as an option 2024-09-13 16:03:31 +03:00
alfred-mk 512460fdeb Added Custom AtOutput to append CON or END to output 2024-09-13 16:02:04 +03:00
alfred-mk 762f90adf6 Added Continue bool to track whether the execution should be terminated 2024-09-12 23:52:35 +03:00
lash 9b4a4eeaf4 Temporary solution for make sure storage object gets put back in all cases of execution 2024-09-12 16:46:11 +01:00
alfred-mk 5e4e6a21a0 Added africastalking binary 2024-09-12 15:56:57 +03:00
alfred-mk ffeb28e851 Use latest go-vise package 2024-09-12 15:56:34 +03:00
lash 9c751aff30 Update go-vise 2024-09-12 04:19:13 +01:00
lash b31d3b907a Add shutdown to async, rehabilitate http cmd 2024-09-12 04:13:57 +01:00
lash d49f866ca4 Factor out methods common to http and async cli 2024-09-12 04:07:55 +01:00
lash dde9f552a6 Isolate http specific parts to minimal 2024-09-12 03:30:23 +01:00
lash 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
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
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
lash 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
91 changed files with 2903 additions and 454 deletions
+261
View File
@@ -0,0 +1,261 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"strings"
"syscall"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
type atRequestParser struct {}
func(arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return "", handlers.ErrInvalidRequest
}
if err := rqv.ParseForm(); err != nil {
return "", fmt.Errorf("failed to parse form data: %v", err)
}
phoneNumber := rqv.FormValue("phoneNumber")
if phoneNumber == "" {
return "", fmt.Errorf("no phone number found")
}
return phoneNumber, nil
}
func(arp *atRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, handlers.ErrInvalidRequest
}
if err := rqv.ParseForm(); err != nil {
return nil, fmt.Errorf("failed to parse form data: %v", err)
}
text := rqv.FormValue("text")
parts := strings.Split(text, "*")
if len(parts) == 0 {
return nil, fmt.Errorf("no input found")
}
return []byte(parts[len(parts)-1]), nil
}
func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
}
rs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
return store, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func main() {
var dbDir string
var resourceDir string
var size uint
var engineDebug bool
var stateDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.UintVar(&port, "p", 7123, "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
rs, err := getResource(resourceDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = ensureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &atRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.NewATSessionHandler(bsh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}
+255
View File
@@ -0,0 +1,255 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"path"
"syscall"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
type asyncRequestParser struct {
sessionId string
input []byte
}
func(p *asyncRequestParser) GetSessionId(r any) (string, error) {
return p.sessionId, nil
}
func(p *asyncRequestParser) GetInput(r any) ([]byte, error) {
return p.input, nil
}
func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
}
rs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
return store, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func main() {
var sessionId string
var dbDir string
var resourceDir string
var size uint
var engineDebug bool
var stateDebug bool
var host string
var port uint
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.UintVar(&port, "p", 7123, "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "sessionId", sessionId)
ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
rs, err := getResource(resourceDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = ensureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &asyncRequestParser{
sessionId: sessionId,
}
sh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
cfg.SessionId = sessionId
rqs := handlers.RequestSession{
Ctx: ctx,
Writer: os.Stdout,
Config: cfg,
}
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
sh.Shutdown()
}()
for true {
rqs, err = sh.Process(rqs)
if err != nil {
fmt.Errorf("error in process: %v", err)
os.Exit(1)
}
rqs, err = sh.Output(rqs)
if err != nil {
fmt.Errorf("error in output: %v", err)
os.Exit(1)
}
rqs, err = sh.Reset(rqs)
if err != nil {
fmt.Errorf("error in reset: %v", err)
os.Exit(1)
}
fmt.Println("")
_, err = fmt.Scanln(&rqs.Input)
if err != nil {
fmt.Errorf("error in input: %v", err)
os.Exit(1)
}
}
}
+221
View File
@@ -0,0 +1,221 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strconv"
"syscall"
"git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/handlers"
httpserver "git.grassecon.net/urdt/ussd/internal/http"
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func getFlags(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp)
if err != nil {
return nil, err
}
return flagParser, nil
}
func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, userdataStore db.Db) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(appFlags, userdataStore)
if err != nil {
return nil, err
}
rs.AddLocalFunc("set_language", ussdHandlers.SetLanguage)
rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize)
rs.AddLocalFunc("quit", ussdHandlers.Quit)
rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount)
rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized)
rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate)
rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
rs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil
}
func ensureDbDir(dbDir string) error {
err := os.MkdirAll(dbDir, 0700)
if err != nil {
return fmt.Errorf("state dir create exited with error: %v\n", err)
}
return nil
}
func getStateStore(dbDir string, ctx context.Context) (db.Db, error) {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile)
return store, nil
}
func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb()
storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile)
return store
}
func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb()
err := store.Connect(ctx, resourceDir)
if err != nil {
return nil, err
}
rfs := resource.NewDbResource(store)
return rfs, nil
}
func main() {
var dbDir string
var resourceDir string
var size uint
var engineDebug bool
var stateDebug bool
var host string
var port uint
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output")
flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.UintVar(&port, "p", 7123, "http port")
flag.Parse()
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
ctx := context.Background()
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getFlags(pfp, true)
if err != nil {
os.Exit(1)
}
cfg := engine.Config{
Root: "root",
OutputSize: uint32(size),
FlagCount: uint32(16),
}
if stateDebug {
cfg.StateDebug = true
}
if engineDebug {
cfg.EngineDebug = true
}
rs, err := getResource(resourceDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
err = ensureDbDir(dbDir)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
userdataStore := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer userdataStore.Close()
dbResource, ok := rs.(*resource.DbResource)
if !ok {
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, userdataStore)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
stateStore, err := getStateStore(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
defer stateStore.Close()
rp := &httpserver.DefaultRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.ToSessionHandler(bsh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
}
s.RegisterOnShutdown(sh.Shutdown)
cint := make(chan os.Signal)
cterm := make(chan os.Signal)
signal.Notify(cint, os.Interrupt, syscall.SIGINT)
signal.Notify(cterm, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case _ = <-cint:
case _ = <-cterm:
}
s.Shutdown(ctx)
}()
err = s.ListenAndServe()
if err != nil {
logg.Infof("Server closed with error", "err", err)
}
}
+83 -189
View File
@@ -4,8 +4,6 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"io"
"net/http"
"os" "os"
"path" "path"
@@ -25,65 +23,6 @@ var (
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
) )
type LocalHandler struct {
sessionId string
}
func NewLocalHandler() *LocalHandler {
return &LocalHandler{
sessionId: "",
}
}
type RequestParser interface {
GetSessionId(*http.Request) (string, error)
GetInput(*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 := io.ReadAll(rq.Body)
if err != nil {
return nil, err
}
return v, nil
}
type DefaultSessionHandler struct {
cfgTemplate engine.Config
rp RequestParser
rh *LocalHandler
dbDir string
resourceDir string
}
func NewDefaultSessionHandler(dbDir string, resourceDir string, rp RequestParser, outputSize uint32, flagCount uint32) *DefaultSessionHandler {
rh := NewLocalHandler()
return &DefaultSessionHandler{
cfgTemplate: engine.Config{
OutputSize: outputSize,
Root: "root",
FlagCount: flagCount,
},
rh: rh,
rp: rp,
dbDir: dbDir,
resourceDir: resourceDir,
}
}
func getParser(fp string, debug bool) (*asm.FlagParser, error) { func getParser(fp string, debug bool) (*asm.FlagParser, error) {
flagParser := asm.NewFlagParser().WithDebug() flagParser := asm.NewFlagParser().WithDebug()
_, err := flagParser.Load(fp) _, err := flagParser.Load(fp)
@@ -95,11 +34,12 @@ 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
} }
rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) ussdHandlers = ussdHandlers.WithPersister(pe)
rs.AddLocalFunc("set_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)
rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
@@ -131,33 +71,45 @@ 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("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
rs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
rs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
rs.AddLocalFunc("quit_with_help",ussdHandlers.QuitWithHelp)
return ussdHandlers, nil return ussdHandlers, nil
} }
func(f *DefaultSessionHandler) getPersister(ctx context.Context) (*persist.Persister, error) { func ensureDbDir(dbDir string) error {
err := os.MkdirAll(f.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(f.dbDir, "state.gdbm") storeFile := path.Join(dbDir, "state.gdbm")
store.Connect(ctx, storeFile) store.Connect(ctx, storeFile)
pr := persist.NewPersister(store) pr := persist.NewPersister(store)
return pr, nil return pr, nil
} }
func(f *DefaultSessionHandler) getUserdataDb(ctx context.Context) db.Db { func getUserdataDb(dbDir string, ctx context.Context) db.Db {
store := gdbmdb.NewGdbmDb() store := gdbmdb.NewGdbmDb()
storeFile := path.Join(f.dbDir, "userdata.gdbm") storeFile := path.Join(dbDir, "userdata.gdbm")
store.Connect(ctx, storeFile) store.Connect(ctx, storeFile)
return store return store
} }
func(f *DefaultSessionHandler) getResource(ctx context.Context) (resource.Resource, error) { func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) {
store := fsdb.NewFsDb() store := fsdb.NewFsDb()
err := store.Connect(ctx, f.resourceDir) err := store.Connect(ctx, resourceDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -165,144 +117,86 @@ func(f *DefaultSessionHandler) getResource(ctx context.Context) (resource.Resour
return rfs, nil return rfs, nil
} }
func(f *DefaultSessionHandler) getEngine(rs resource.Resource, pr *persist.Persister, sessionId string) *engine.DefaultEngine { func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine {
cfg := f.cfgTemplate
cfg.SessionId = sessionId
en := engine.NewEngine(cfg, rs) en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr) en = en.WithPersister(pr)
return en return en
} }
func(f *DefaultSessionHandler) 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 *DefaultSessionHandler) 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()
ctx = context.WithValue(ctx, "SessionId", sessionId)
pfp := path.Join(scriptDir, "pp.csv")
flagParser, err := getParser(pfp, true)
if err != nil {
f.writeError(w, 500, "flagParser failed with error:", err)
return
}
rs, err := f.getResource(ctx)
if err != nil {
f.writeError(w, 500, "getResource failed with error:", err)
return
}
pr, err := f.getPersister(ctx)
if err != nil {
f.writeError(w, 500, "getPersister failed with error:", err)
return
}
store := f.getUserdataDb(ctx)
dbResource, ok := rs.(*resource.DbResource)
if !ok {
f.writeError(w, 500, "getHandler exited with error:", err)
return
}
hl, err := getHandler(flagParser, dbResource, pr, store)
if err != nil {
f.writeError(w, 500, "getHandler exited with error:", err)
return
}
en := f.getEngine(rs, pr, sessionId)
en = en.WithFirst(hl.Init)
if len(input) == 0 {
r, err = en.Init(ctx)
} else {
r, err = en.Exec(ctx, input)
}
if err != nil {
f.writeError(w, 500, "Engine exec fail", err)
return
}
// _, err = en.Init(ctx)
// if err != nil {
// f.writeError(w, 500, "Engine exec fail", err)
// return
// }
// err = engine.Loop(ctx, en, os.Stdin, os.Stdout)
// if err != nil {
// f.writeError(w, 500, "Loop 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
}
_ = r
}
func main() { func main() {
var host string
var port string
var dbDir string var dbDir string
var resourceDir string
var size uint var size uint
var flagCount uint
var sessionId string var sessionId string
var debug bool var debug bool
flag.StringVar(&host, "h", "127.0.0.1", "http host")
flag.StringVar(&port, "p", "7123", "http port")
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.UintVar(&flagCount, "f", 16, "flag count")
flag.Parse() flag.Parse()
logg.Infof("starting server", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "flagCount", flagCount) logg.Infof("start command", "dbdir", dbDir, "outputsize", size)
rp := &DefaultRequestParser{} ctx := context.Background()
h := NewDefaultSessionHandler(dbDir, resourceDir, rp, uint32(size), uint32(flagCount)) ctx = context.WithValue(ctx, "SessionId", sessionId)
s := &http.Server{ pfp := path.Join(scriptDir, "pp.csv")
Addr: fmt.Sprintf("%s:%s", host, port), flagParser, err := getParser(pfp, true)
Handler: h,
if err != nil {
os.Exit(1)
} }
err := s.ListenAndServe() cfg := engine.Config{
Root: "root",
SessionId: sessionId,
OutputSize: uint32(size),
FlagCount: uint32(16),
}
rs, err := getResource(scriptDir, ctx)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Server error: %s", err) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
pe, err := getPersister(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
store := getUserdataDb(dbDir, ctx)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
hl, err := getHandler(flagParser, dbResource, pe, store)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
en := getEngine(cfg, rs, pe)
en = en.WithFirst(hl.Init)
if debug {
en = en.WithDebug(nil)
}
_, err = en.Init(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err)
os.Exit(1)
}
err = engine.Loop(ctx, en, os.Stdin, os.Stdout)
if err != nil {
fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
+7 -6
View File
@@ -2,6 +2,13 @@ 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.20240911231817-0d23e0dbb57f
github.com/alecthomas/assert/v2 v2.2.2
github.com/peteole/testdata-loader v0.3.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
)
require ( 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 +24,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
)
+2 -2
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.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM=
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.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2 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=
+117
View File
@@ -0,0 +1,117 @@
package handlers
import (
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/storage"
)
type BaseSessionHandler struct {
cfgTemplate engine.Config
rp RequestParser
rs resource.Resource
hn *ussd.Handlers
provider storage.StorageProvider
}
func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn *ussd.Handlers) *BaseSessionHandler {
return &BaseSessionHandler{
cfgTemplate: cfg,
rs: rs,
hn: hn,
rp: rp,
provider: storage.NewSimpleStorageProvider(stateDb, userdataDb),
}
}
func(f* BaseSessionHandler) Shutdown() {
err := f.provider.Close()
if err != nil {
logg.Errorf("handler shutdown error", "err", err)
}
}
func(f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
en := engine.NewEngine(cfg, rs)
en = en.WithPersister(pr)
return en
}
func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error) {
var r bool
var err error
var ok bool
logg.InfoCtxf(rqs.Ctx, "new request", rqs)
rqs.Storage, err = f.provider.Get(rqs.Config.SessionId)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err)
return rqs, ErrStorage
}
f.hn = f.hn.WithPersister(rqs.Storage.Persister)
eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
en, ok := eni.(*engine.DefaultEngine)
if !ok {
perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
rqs.Storage = nil
if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
}
return rqs, ErrEngineType
}
en = en.WithFirst(f.hn.Init)
if rqs.Config.EngineDebug {
en = en.WithDebug(nil)
}
rqs.Engine = en
r, err = rqs.Engine.Init(rqs.Ctx)
if err != nil {
perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
rqs.Storage = nil
if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
}
return rqs, err
}
if r && len(rqs.Input) > 0 {
r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input)
}
if err != nil {
perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage)
rqs.Storage = nil
if perr != nil {
logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr)
}
return rqs, err
}
rqs.Continue = r
return rqs, nil
}
func(f *BaseSessionHandler) Output(rqs RequestSession) (RequestSession, error) {
var err error
_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer)
return rqs, err
}
func(f *BaseSessionHandler) Reset(rqs RequestSession) (RequestSession, error) {
defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
return rqs, rqs.Engine.Finish()
}
func(f *BaseSessionHandler) GetConfig() engine.Config {
return f.cfgTemplate
}
func(f *BaseSessionHandler) GetRequestParser() RequestParser {
return f.rp
}
+56
View File
@@ -0,0 +1,56 @@
package handlers
import (
"context"
"errors"
"io"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/storage"
)
var (
logg = logging.NewVanilla().WithDomain("handlers")
)
var (
ErrInvalidRequest = errors.New("invalid request for context")
ErrSessionMissing = errors.New("missing session")
ErrInvalidInput = errors.New("invalid input")
ErrStorage = errors.New("storage retrieval fail")
ErrEngineType = errors.New("incompatible engine")
ErrEngineInit = errors.New("engine init fail")
ErrEngineExec = errors.New("engine exec fail")
)
type RequestSession struct {
Ctx context.Context
Config engine.Config
Engine engine.Engine
Input []byte
Storage *storage.Storage
Writer io.Writer
Continue bool
}
type engineMaker func(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine
// TODO: seems like can remove this.
type RequestParser interface {
GetSessionId(rq any) (string, error)
GetInput(rq any) ([]byte, error)
}
type RequestHandler interface {
GetConfig() engine.Config
GetRequestParser() RequestParser
GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
Process(rs RequestSession) (RequestSession, error)
Output(rs RequestSession) (RequestSession, error)
Reset(rs RequestSession) (RequestSession, error)
Shutdown()
}
+120 -39
View File
@@ -66,20 +66,16 @@ 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,
}
h := &Handlers{ h := &Handlers{
pe: pe,
userdataStore: userDb, userdataStore: userDb,
flagManager: parser, flagManager: appFlags,
accountService: &server.AccountService{}, accountService: &server.AccountService{},
} }
return h, nil return h, nil
@@ -94,6 +90,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
@@ -117,15 +121,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
inputStr := string(input) sym, _ = h.st.Where()
switch inputStr {
case "0": switch sym {
res.FlagSet = []uint32{state.FLAG_LANG} case "set_default":
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = "eng" res.Content = "eng"
case "1": case "set_swa":
res.FlagSet = []uint32{state.FLAG_LANG} res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = "swa" res.Content = "swa"
default: default:
} }
@@ -212,6 +216,74 @@ 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
@@ -254,7 +326,7 @@ func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (res
} }
//AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN) //AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN)
store := h.userdataStore.(utils.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
@@ -318,9 +390,6 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
if err != nil { if err != nil {
return res, err return res, err
} }
if err != nil {
return res, nil
}
} else { } else {
return res, fmt.Errorf("a family name cannot be less than one character") return res, fmt.Errorf("a family name cannot be less than one character")
} }
@@ -477,27 +546,23 @@ 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
} }
return res, nil return res, nil
} }
@@ -523,7 +588,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
@@ -565,6 +630,22 @@ 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 +691,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 +825,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)
@@ -788,7 +870,6 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
} }
res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places 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
@@ -862,7 +943,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
+2 -47
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)
}
+93
View File
@@ -0,0 +1,93 @@
package http
import (
"io"
"net/http"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
type ATSessionHandler struct {
*SessionHandler
}
func NewATSessionHandler(h handlers.RequestHandler) *ATSessionHandler {
return &ATSessionHandler{
SessionHandler: ToSessionHandler(h),
}
}
func (ash *ATSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := ash.GetRequestParser()
cfg := ash.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.writeError(w, 400, err)
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
ash.writeError(w, 400, err)
return
}
rqs, err = ash.Process(rqs)
switch err {
case handlers.ErrStorage:
code = 500
case handlers.ErrEngineInit:
code = 500
case handlers.ErrEngineExec:
code = 500
default:
code = 200
}
if code != 200 {
ash.writeError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = ash.Output(rqs)
if err != nil {
ash.writeError(w, 500, err)
return
}
rqs, err = ash.Reset(rqs)
if err != nil {
ash.writeError(w, 500, err)
return
}
}
func (ash *ATSessionHandler) Output(rqs handlers.RequestSession) (handlers.RequestSession, error) {
var err error
var prefix string
if rqs.Continue {
prefix = "CON "
} else {
prefix = "END "
}
_, err = io.WriteString(rqs.Writer, prefix)
if err != nil {
return rqs, err
}
_, err = rqs.Engine.WriteResult(rqs.Ctx, rqs.Writer)
return rqs, err
}
+122
View File
@@ -0,0 +1,122 @@
package http
import (
"io/ioutil"
"net/http"
"strconv"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/handlers"
)
var (
logg = logging.NewVanilla().WithDomain("httpserver")
)
type DefaultRequestParser struct {
}
func(rp *DefaultRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return "", handlers.ErrInvalidRequest
}
v := rqv.Header.Get("X-Vise-Session")
if v == "" {
return "", handlers.ErrSessionMissing
}
return v, nil
}
func(rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
rqv, ok := rq.(*http.Request)
if !ok {
return nil, handlers.ErrInvalidRequest
}
defer rqv.Body.Close()
v, err := ioutil.ReadAll(rqv.Body)
if err != nil {
return nil, err
}
return v, nil
}
type SessionHandler struct {
handlers.RequestHandler
}
func ToSessionHandler(h handlers.RequestHandler) *SessionHandler {
return &SessionHandler{
RequestHandler: h,
}
}
func(f *SessionHandler) writeError(w http.ResponseWriter, code int, err error) {
s := err.Error()
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
w.WriteHeader(code)
_, err = w.Write([]byte{})
if err != nil {
logg.Errorf("error writing error!!", "err", err, "olderr", s)
w.WriteHeader(500)
}
return
}
func(f *SessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var code int
var err error
var perr error
rqs := handlers.RequestSession{
Ctx: req.Context(),
Writer: w,
}
rp := f.GetRequestParser()
cfg := f.GetConfig()
cfg.SessionId, err = rp.GetSessionId(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
f.writeError(w, 400, err)
}
rqs.Config = cfg
rqs.Input, err = rp.GetInput(req)
if err != nil {
logg.ErrorCtxf(rqs.Ctx, "", "header processing error", err)
f.writeError(w, 400, err)
return
}
rqs, err = f.Process(rqs)
switch err {
case handlers.ErrStorage:
code = 500
case handlers.ErrEngineInit:
code = 500
case handlers.ErrEngineExec:
code = 500
default:
code = 200
}
if code != 200 {
f.writeError(w, 500, err)
return
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")
rqs, err = f.Output(rqs)
rqs, perr = f.Reset(rqs)
if err != nil {
f.writeError(w, 500, err)
return
}
if perr != nil {
f.writeError(w, 500, perr)
return
}
}
+44
View File
@@ -0,0 +1,44 @@
package storage
import (
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/persist"
)
type Storage struct {
Persister *persist.Persister
UserdataDb db.Db
}
type StorageProvider interface {
Get(sessionId string) (*Storage, error)
Put(sessionId string, storage *Storage) error
Close() error
}
type SimpleStorageProvider struct {
*Storage
}
func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProvider {
pe := persist.NewPersister(stateStore)
pe = pe.WithFlush()
return &SimpleStorageProvider{
Storage: &Storage{
Persister: pe,
UserdataDb: userdataStore,
},
}
}
func (p *SimpleStorageProvider) Get(sessionId string) (*Storage, error) {
return p.Storage, nil
}
func (p *SimpleStorageProvider) Put(sessionId string, storage *Storage) error {
return nil
}
func (p *SimpleStorageProvider) Close() error {
return p.Storage.UserdataDb.Close()
}
-44
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
}
+1 -22
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)
}
+9 -58
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)
} }
+2 -1
View File
@@ -1,10 +1,11 @@
# 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 ../../go-vise/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin go run $(VISE_PATH)/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin
@echo "Built $(basename $@).bin from $(basename $@).vis" @echo "Built $(basename $@).bin from $(basename $@).vis"
# Rule to copy .orig files to .txt # Rule to copy .orig files to .txt
+1 -1
View File
@@ -1 +1 @@
Rudi Rudi
+1 -1
View File
@@ -1 +1 @@
Balances: Balances:
+1 -1
View File
@@ -1 +1 @@
Salio Salio:
+1
View File
@@ -0,0 +1 @@
Select language:
+10
View File
@@ -0,0 +1,10 @@
LOAD reset_account_authorized 0
LOAD reset_incorrect 0
CATCH incorrect_pin flag_incorrect_pin 1
CATCH pin_entry flag_account_authorized 0
MOUT english 0
MOUT kiswahili 1
HALT
INCMP set_default 0
INCMP set_swa 1
INCMP . *
@@ -1 +1 @@
Badili lugha Badili lugha
@@ -0,0 +1 @@
Chagua lugha:
+1 -1
View File
@@ -1 +1 @@
Change PIN Change PIN
+1 -1
View File
@@ -1 +1 @@
Badili PIN Badili PIN
+1 -1
View File
@@ -1 +1 @@
Check statement Check statement
+1 -2
View File
@@ -1,2 +1 @@
Your community balance is: 0.00SRF Your community balance is: 0.00SRF
+1 -1
View File
@@ -1 +1 @@
Community balance Community balance
+1
View File
@@ -0,0 +1 @@
Confirm your new PIN:
@@ -0,0 +1,7 @@
CATCH invalid_pin flag_valid_pin 0
MOUT back 0
HALT
INCMP _ 0
INCMP * pin_reset_success
@@ -0,0 +1 @@
Thibitisha PIN yako mpya:
+1 -1
View File
@@ -1 +1 @@
Edit name Edit name
+1 -1
View File
@@ -1 +1 @@
Weka jina Weka jina
+1 -1
View File
@@ -1 +1 @@
Edit offerings Edit offerings
+1 -1
View File
@@ -1 +1 @@
Enter family name: Enter family name:
+1 -1
View File
@@ -1 +1 @@
Enter your location: Enter your location:
+1 -1
View File
@@ -1 +1 @@
Weka majina yako ya kwanza: Weka majina yako ya kwanza:
+1 -1
View File
@@ -1 +1 @@
Female Female
+1 -1
View File
@@ -1 +1 @@
Guard my PIN Guard my PIN
+1 -1
View File
@@ -1 +1 @@
Linda PIN yangu Linda PIN yangu
+2
View File
@@ -0,0 +1,2 @@
LOAD quit_with_help 0
HALT
+1 -1
View File
@@ -1,2 +1,2 @@
The year of birth you entered is invalid. The year of birth you entered is invalid.
Please try again. Please try again.
+1 -1
View File
@@ -1 +1 @@
Incorrect pin Incorrect pin
+1
View File
@@ -0,0 +1 @@
The PIN you entered is invalid.The PIN must be different from your current PIN.For help call +254757628885
+3
View File
@@ -0,0 +1,3 @@
MOUT back 0
HALT
INCMP _ 0
+1
View File
@@ -0,0 +1 @@
PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.
+1
View File
@@ -0,0 +1 @@
Your language change request was successful.
@@ -0,0 +1,5 @@
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9
@@ -0,0 +1 @@
Ombi lako la kubadilisha lugha limefanikiwa.
@@ -6,3 +6,7 @@ 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"
+1 -1
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 . *
+1
View File
@@ -9,6 +9,7 @@ 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
+1 -1
View File
@@ -1 +1 @@
Anwani yangu Anwani yangu
+1 -1
View File
@@ -1 +1 @@
Salio lako ni: 0.00 SRF Salio lako ni: 0.00 SRF
+1
View File
@@ -0,0 +1 @@
Enter a new four number pin
+13
View File
@@ -0,0 +1,13 @@
LOAD authorize_account 12
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
CATCH old_pin flag_allow_update 0
MOUT back 0
HALT
INCMP _ 0
LOAD save_temporary_pin 6
LOAD verify_new_pin 0
RELOAD save_temporary_pin
RELOAD verify_new_pin
INCMP * confirm_pin_change
+2
View File
@@ -0,0 +1,2 @@
Weka PIN mpya ya nne nambari:
+1 -1
View File
@@ -1 +1 @@
no no
+1 -1
View File
@@ -1 +1 @@
la la
+1
View File
@@ -0,0 +1 @@
Enter your old PIN
+7
View File
@@ -0,0 +1,7 @@
LOAD reset_allow_update 0
MOUT back 0
HALT
RELOAD reset_allow_update
INCMP _ 0
INCMP new_pin *
+1
View File
@@ -0,0 +1 @@
Weka PIN yako ya zamani:
+1 -1
View File
@@ -1 +1 @@
Tafadhali weka PIN yako Tafadhali weka PIN yako
+1 -1
View File
@@ -1 +1 @@
PIN Management PIN Management
+2
View File
@@ -4,3 +4,5 @@ MOUT guard_pin 3
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
INCMP old_pin 1
+1
View File
@@ -0,0 +1 @@
The PIN is not a match. Try again
@@ -0,0 +1,6 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP confirm_pin_change 1
INCMP quit 9
+1
View File
@@ -0,0 +1 @@
Your PIN change request has been successful
@@ -0,0 +1,10 @@
LOAD confirm_pin_change 0
RELOAD confirm_pin_change
CATCH pin_reset_mismatch flag_pin_mismatch 1
MOUT back 0
MOUT quit 9
HALT
INCMP main 0
INCMP quit 9
@@ -0,0 +1 @@
Ombi lako la kubadili PIN limefanikiwa
+1 -1
View File
@@ -1 +1 @@
Profile Profile
+1 -1
View File
@@ -1 +1 @@
Wasifu wangu Wasifu wangu
+1
View File
@@ -0,0 +1 @@
Quit
+1
View File
@@ -0,0 +1 @@
Ondoka
+1 -1
View File
@@ -1 +1 @@
Badili PIN ya mwenzio Badili PIN ya mwenzio
+2 -2
View File
@@ -1,6 +1,6 @@
MOUT english 0 MOUT english 0
MOUT kiswahili 1 MOUT kiswahili 1
HALT HALT
INCMP terms 0 INCMP set_default 0
INCMP terms 1 INCMP set_swa 1
INCMP . * INCMP . *
+1 -1
View File
@@ -1 +1 @@
Enter recipient's phone number: Enter recipient's phone number:
+3
View File
@@ -0,0 +1,3 @@
LOAD set_language 6
CATCH terms flag_account_created 0
MOVE language_changed
+3
View File
@@ -0,0 +1,3 @@
LOAD set_language 6
CATCH terms flag_account_created 0
MOVE language_changed
-2
View File
@@ -1,5 +1,3 @@
LOAD select_language 0
RELOAD select_language
MOUT yes 0 MOUT yes 0
MOUT no 1 MOUT no 1
HALT HALT
+1 -1
View File
@@ -1,2 +1,2 @@
{{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}} {{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}}
Please enter your PIN to confirm: Please enter your PIN to confirm:
+1 -1
View File
@@ -1 +1 @@
Unspecified Unspecified
+1 -1
View File
@@ -1 +1 @@
Profile updated successfully Profile updated successfully
+1 -1
View File
@@ -1 +1 @@
Akaunti imeupdatiwa Akaunti imeupdatiwa
+2 -2
View File
@@ -1,2 +1,2 @@
My profile: My profile:
{{.get_profile_info}} {{.get_profile_info}}
+2 -1
View File
@@ -1 +1,2 @@
Wasifu wangu Wasifu wangu:
{{.get_profile_info}}
+1 -1
View File
@@ -1 +1 @@
Angalia Wasifu Angalia Wasifu
+1 -1
View File
@@ -1 +1 @@
yes yes
+1 -1
View File
@@ -1 +1 @@
ndio ndio