Compare commits

..

8 Commits

12 changed files with 292 additions and 41 deletions

View File

@ -52,7 +52,6 @@ func SearchDomains() []string {
return ParsedDomains return ParsedDomains
} }
func Language() string { func Language() string {
return viseconfig.DefaultLanguage return viseconfig.DefaultLanguage
} }

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path" "path"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/sarafu-vise/config" "git.grassecon.net/grassrootseconomics/sarafu-vise/config"
@ -18,6 +19,7 @@ import (
var ( var (
logg = logging.NewVanilla().WithContextKey("SessionId") logg = logging.NewVanilla().WithContextKey("SessionId")
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
menuSeparator = ": "
) )
func main() { func main() {
@ -25,6 +27,8 @@ func main() {
override := config.NewOverride() override := config.NewOverride()
var sessionId string var sessionId string
var size uint
var engineDebug bool
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)")
@ -32,6 +36,8 @@ func main() {
flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string") flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string")
flag.StringVar(&override.StateConn, "state", "?", "state store connection string") flag.StringVar(&override.StateConn, "state", "?", "state store connection string")
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
flag.UintVar(&size, "s", 160, "max size of output")
flag.Parse() flag.Parse()
config.Apply(override) config.Apply(override)
@ -50,13 +56,23 @@ func main() {
os.Exit(1) os.Exit(1)
} }
cfg := engine.Config{
Root: "root",
SessionId: sessionId,
OutputSize: uint32(size),
FlagCount: uint32(128),
MenuSeparator: menuSeparator,
EngineDebug: engineDebug,
ResetOnEmptyInput: true,
}
x := cmd.NewCmd(sessionId, flagParser) x := cmd.NewCmd(sessionId, flagParser)
x = x.WithEngine(cfg)
err = x.Parse(flag.Args()) err = x.Parse(flag.Args())
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "cmd parse fail: %v\n", err) fmt.Fprintf(os.Stderr, "cmd parse fail: %v\n", err)
os.Exit(1) os.Exit(1)
} }
logg.Infof("start command", "conn", conns, "subcmd", x) logg.Infof("start command", "conn", conns, "subcmd", x)
menuStorageService := storage.NewMenuStorageService(conns) menuStorageService := storage.NewMenuStorageService(conns)
@ -70,5 +86,4 @@ func main() {
fmt.Fprintf(os.Stderr, "cmd exec error: %v\n", err) fmt.Fprintf(os.Stderr, "cmd exec error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -24,7 +24,7 @@ func formatItem(k []byte, v []byte, sessionId string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
s := fmt.Sprintf("%v\t%v\n", o.Label, string(v)) s := fmt.Sprintf("%v\n\t%v\n", o.Label, string(v))
return s, nil return s, nil
} }

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.23.4
require ( require (
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250411080608-34957e5b6ff8 git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250411080608-34957e5b6ff8
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2 git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694 git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694

2
go.sum
View File

@ -4,8 +4,6 @@ git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 h1:FnT39aqXcP5YW
git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ=
git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc=
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b h1:xiTpaqWWoF5qcnarY/9ZkT6aVdnKwqztb2gzIahJn4w= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b h1:xiTpaqWWoF5qcnarY/9ZkT6aVdnKwqztb2gzIahJn4w=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b/go.mod h1:gOn89ipaDcDvmQXRMQYKUqcw/sJcwVOPVt2eC6Geip8=
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401111804-2eed990921c5 h1:DwBZHP4sebfHxK8EU2nlA2CXU81+a7Kj/pnC5vDPcf4= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250401111804-2eed990921c5 h1:DwBZHP4sebfHxK8EU2nlA2CXU81+a7Kj/pnC5vDPcf4=

View File

@ -3,14 +3,31 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"regexp"
"strings"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state"
"git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application" "git.grassecon.net/grassrootseconomics/sarafu-vise/handlers/application"
"git.grassecon.net/grassrootseconomics/visedriver/storage" "git.grassecon.net/grassrootseconomics/visedriver/storage"
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
) )
var argc map[string]int = map[string]int{
"reset": 0,
"admin": 1,
"clone": 1,
"overwrite": 2,
}
var ( var (
logg = logging.NewVanilla().WithDomain("cmd").WithContextKey("SessionId") logg = logging.NewVanilla().WithDomain("cmd").WithContextKey("SessionId")
cloneTargetRegex = `^\+000`
) )
type Cmd struct { type Cmd struct {
@ -19,7 +36,12 @@ type Cmd struct {
flagParser *application.FlagManager flagParser *application.FlagManager
cmd int cmd int
enable bool enable bool
param string
exec func(ctx context.Context, ss storage.StorageService) error exec func(ctx context.Context, ss storage.StorageService) error
engineConfig *engine.Config
st *state.State
key string
value string
} }
func NewCmd(sessionId string, flagParser *application.FlagManager) *Cmd { func NewCmd(sessionId string, flagParser *application.FlagManager) *Cmd {
@ -29,10 +51,115 @@ func NewCmd(sessionId string, flagParser *application.FlagManager) *Cmd {
} }
} }
func (c *Cmd) WithEngine(engineConfig engine.Config) *Cmd {
c.engineConfig = &engineConfig
return c
}
func (c *Cmd) Exec(ctx context.Context, ss storage.StorageService) error { func (c *Cmd) Exec(ctx context.Context, ss storage.StorageService) error {
return c.exec(ctx, ss) return c.exec(ctx, ss)
} }
func (c *Cmd) engine(ctx context.Context, rs resource.Resource, pe *persist.Persister) (engine.Engine, error) {
if c.engineConfig == nil {
return nil, fmt.Errorf("engine config missing")
}
en := engine.NewEngine(*c.engineConfig, rs)
st := pe.GetState()
if st == nil {
return nil, fmt.Errorf("persister state fail")
}
en = en.WithState(st)
st.UseDebug()
ca := pe.GetMemory()
if ca == nil {
return nil, fmt.Errorf("persister cache fail")
}
en = en.WithMemory(ca)
logg.DebugCtxf(ctx, "state loaded", "state", st)
return en, nil
}
func (c *Cmd) execClone(ctx context.Context, ss storage.StorageService) error {
re := regexp.MustCompile(cloneTargetRegex)
if !re.MatchString(c.param) {
return fmt.Errorf("Clone sessionId must match target: %s", c.param)
}
pe, err := ss.GetPersister(ctx)
if err != nil {
return fmt.Errorf("get persister error: %v", err)
}
err = pe.Load(c.engineConfig.SessionId)
if err != nil {
return fmt.Errorf("persister load error: %v", err)
}
/// TODO consider DRY with devtools/store/dump
store, err := ss.GetUserdataDb(ctx)
if err != nil {
return fmt.Errorf("store retrieve error: %v", err)
}
store.SetSession(c.engineConfig.SessionId)
store.SetPrefix(db.DATATYPE_USERDATA)
dmp, err := store.Dump(ctx, []byte(""))
if err != nil {
return fmt.Errorf("store dump fail: %v\n", err.Error())
}
for true {
store.SetSession(c.engineConfig.SessionId)
k, v := dmp.Next(ctx)
if k == nil {
break
}
store.SetSession(c.param)
err = store.Put(ctx, k, v)
if err != nil {
return fmt.Errorf("user data store clone failed on key: %x", k)
}
}
return pe.Save(c.param)
}
func (c *Cmd) execReset(ctx context.Context, ss storage.StorageService) error {
pe, err := ss.GetPersister(ctx)
if err != nil {
return fmt.Errorf("get persister error: %v", err)
}
rs, err := ss.GetResource(ctx)
if err != nil {
return fmt.Errorf("get resource error: %v", err)
}
dbResource, ok := rs.(*resource.DbResource)
if !ok {
return fmt.Errorf("get dbresource error: %v", err)
}
err = pe.Load(c.engineConfig.SessionId)
if err != nil {
return fmt.Errorf("persister load error: %v", err)
}
en, err := c.engine(ctx, dbResource, pe)
if err != nil {
return err
}
_, err = en.(*engine.DefaultEngine).Reset(ctx, false)
if err != nil {
return err
}
st := pe.GetState()
logg.DebugCtxf(ctx, "state after reset", "state", st)
err = pe.Save(c.engineConfig.SessionId)
if err != nil {
return err
}
return nil
}
func (c *Cmd) execAdmin(ctx context.Context, ss storage.StorageService) error { func (c *Cmd) execAdmin(ctx context.Context, ss storage.StorageService) error {
pe, err := ss.GetPersister(ctx) pe, err := ss.GetPersister(ctx)
if err != nil { if err != nil {
@ -63,6 +190,51 @@ func (c *Cmd) execAdmin(ctx context.Context, ss storage.StorageService) error {
return nil return nil
} }
func (c *Cmd) execOverwrite(ctx context.Context, ss storage.StorageService) error {
store, err := ss.GetUserdataDb(ctx)
if err != nil {
return fmt.Errorf("failed to get userdata store: %v", err)
}
// Map of symbolic keys to their DataTyp constants
symbolicKeys := map[string]storedb.DataTyp{
"first_name": storedb.DATA_FIRST_NAME,
"family_name": storedb.DATA_FAMILY_NAME,
"yob": storedb.DATA_YOB,
"location": storedb.DATA_LOCATION,
"gender": storedb.DATA_GENDER,
"offerings": storedb.DATA_OFFERINGS,
}
// Lookup symbolic key
dtype, ok := symbolicKeys[strings.ToLower(c.key)]
if !ok {
return fmt.Errorf("unknown key '%s'. Available keys: %v", c.key, keysOf(symbolicKeys))
}
k := storedb.ToBytes(dtype)
store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(c.sessionId)
err = store.Put(ctx, k, []byte(c.value))
if err != nil {
return fmt.Errorf("failed to overwrite entry for key %s: %v", c.key, err)
}
logg.InfoCtxf(ctx, "overwrote data", "sessionId", c.sessionId, "key", c.key, "value", c.value)
return nil
}
// keysOf returns a list of keys from the symbolic map for error messages
func keysOf(m map[string]storedb.DataTyp) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func (c *Cmd) parseCmdAdmin(cmd string, param string, more []string) (bool, error) { func (c *Cmd) parseCmdAdmin(cmd string, param string, more []string) (bool, error) {
if cmd == "admin" { if cmd == "admin" {
if param == "1" { if param == "1" {
@ -76,13 +248,56 @@ func (c *Cmd) parseCmdAdmin(cmd string, param string, more []string) (bool, erro
return false, nil return false, nil
} }
func (c *Cmd) parseCmdReset(cmd string, param string, more []string) (bool, error) {
if cmd == "reset" {
c.enable = false
c.exec = c.execReset
return true, nil
}
return false, nil
}
func (c *Cmd) parseCmdClone(cmd string, param string, more []string) (bool, error) {
if cmd == "clone" {
c.enable = false
c.param = param
c.exec = c.execClone
return true, nil
}
return false, nil
}
func (c *Cmd) parseCmdOverwrite(cmd string, param string, more []string) (bool, error) {
if cmd == "overwrite" {
if len(more) < 1 {
return false, fmt.Errorf("overwrite requires key and value")
}
c.key = param
c.value = more[0]
c.exec = c.execOverwrite
return true, nil
}
return false, nil
}
func (c *Cmd) Parse(args []string) error { func (c *Cmd) Parse(args []string) error {
if len(args) < 2 { var param string
if len(args) < 1 {
return fmt.Errorf("Wrong number of arguments: %v", args) return fmt.Errorf("Wrong number of arguments: %v", args)
} }
cmd := args[0] cmd := args[0]
param := args[1]
n, ok := argc[cmd]
if !ok {
return fmt.Errorf("invalid command: %v", cmd)
}
if n > 0 {
if len(args) < n+1 {
return fmt.Errorf("Wrong number of arguments, need: %d", n)
}
param = args[1]
args = args[2:] args = args[2:]
}
r, err := c.parseCmdAdmin(cmd, param, args) r, err := c.parseCmdAdmin(cmd, param, args)
if err != nil { if err != nil {
@ -92,5 +307,29 @@ func (c *Cmd) Parse(args []string) error {
return nil return nil
} }
r, err = c.parseCmdReset(cmd, param, args)
if err != nil {
return err
}
if r {
return nil
}
r, err = c.parseCmdClone(cmd, param, args)
if err != nil {
return err
}
if r {
return nil
}
r, err = c.parseCmdOverwrite(cmd, param, args)
if err != nil {
return err
}
if r {
return nil
}
return fmt.Errorf("unknown subcommand: %s", cmd) return fmt.Errorf("unknown subcommand: %s", cmd)
} }