Compare commits

..

13 Commits

Author SHA1 Message Date
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
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
11 changed files with 525 additions and 361 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

@@ -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,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)
@@ -135,29 +75,37 @@ func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.P
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 +113,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)
} }
} }

12
go.mod
View File

@@ -2,6 +2,12 @@ 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
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 +23,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
)

4
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=

View File

@@ -66,21 +66,17 @@ 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: appFlags,
flagManager: parser, 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
@@ -254,7 +258,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
@@ -523,7 +527,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
@@ -610,7 +614,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
@@ -862,7 +866,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

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

@@ -39,7 +39,6 @@ func PackKey(typ DataTyp, data []byte) []byte {
} }
func ReadEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp) ([]byte, error) { func ReadEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId) store.SetSession(sessionId)
k := PackKey(typ, []byte(sessionId)) k := PackKey(typ, []byte(sessionId))

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