forked from grassrootseconomics/visedriver
		
	Compare commits
	
		
			34 Commits
		
	
	
		
			lash/remov
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 770fef68b9 | |||
| dd2751839d | |||
| 5a9c822075 | |||
|  | c8c0daac24 | ||
|  | 1bcffe2d23 | ||
| bf10e5357c | |||
|  | d91c96f541 | ||
|  | ec9032a42e | ||
|  | 6619afe62b | ||
|  | 1eb0b15552 | ||
|  | ef803e0ee2 | ||
|  | 03d19283f6 | ||
|  | 15ce29a1a4 | ||
|  | 6749c632b0 | ||
|  | 8530c45074 | ||
|  | d5e636fbd6 | ||
|  | f7d31e4e81 | ||
|  | 90ecec1798 | ||
|  | 874edb3da6 | ||
|  | 60ff1b0ab3 | ||
|  | 9b3dad579b | ||
|  | 348fff8936 | ||
|  | c5bb1c80a5 | ||
|  | b8a377befb | ||
|  | c9b92191f3 | ||
|  | ddd8d7cac0 | ||
|  | 37973a6c9b | ||
|  | 975720919c | ||
|  | c0534ede1b | ||
|  | 24e729d275 | ||
|  | ae6e2a99c5 | ||
|  | 7ca3974371 | ||
|  | adbfab3964 | ||
|  | 5228aef088 | 
							
								
								
									
										127
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										127
									
								
								config/config.go
									
									
									
									
									
								
							| @ -1,27 +1,48 @@ | ||||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/env" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/storage" | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	defaultLanguage		   = "eng" | ||||
| 	languages []string | ||||
| 	logg               = slogging.Get().With("component", "visedriver-config") | ||||
| 	defaultLanguage    = "eng" | ||||
| 	languages          []string | ||||
| 	DefaultLanguage    string | ||||
| 	dbConn             string | ||||
| 	dbConnMissing      bool | ||||
| 	dbConnMode         storage.DbMode | ||||
| 	stateDbConn        string | ||||
| 	stateDbConnMode    storage.DbMode | ||||
| 	resourceDbConn     string | ||||
| 	resourceDbConnMode storage.DbMode | ||||
| 	userDbConn         string | ||||
| 	userDbConnMode     storage.DbMode | ||||
| 	Languages          []string | ||||
| 	configManager      *Config | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	DbConn		string | ||||
| 	DefaultLanguage	    string | ||||
| 	Languages	[]string | ||||
| ) | ||||
| type Override struct { | ||||
| 	DbConn           string | ||||
| 	DbConnMode       storage.DbMode | ||||
| 	StateConn        string | ||||
| 	StateConnMode    storage.DbMode | ||||
| 	ResourceConn     string | ||||
| 	ResourceConnMode storage.DbMode | ||||
| 	UserConn         string | ||||
| 	UserConnMode     storage.DbMode | ||||
| } | ||||
| 
 | ||||
| func setLanguage() error { | ||||
| 	defaultLanguage = env.GetEnv("DEFAULT_LANGUAGE", defaultLanguage) | ||||
| 	languages = strings.Split(env.GetEnv("LANGUAGES", defaultLanguage), ",") | ||||
| 	haveDefaultLanguage := false | ||||
| 	for i, v := range(languages) { | ||||
| 	for i, v := range languages { | ||||
| 		languages[i] = strings.ReplaceAll(v, " ", "") | ||||
| 		if languages[i] == defaultLanguage { | ||||
| 			haveDefaultLanguage = true | ||||
| @ -35,15 +56,97 @@ func setLanguage() error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| func setConn() error { | ||||
| 	DbConn = env.GetEnv("DB_CONN", "") | ||||
| 	dbConn = env.GetEnv("DB_CONN", "?") | ||||
| 	stateDbConn = env.GetEnv("DB_CONN_STATE", dbConn) | ||||
| 	resourceDbConn = env.GetEnv("DB_CONN_RESOURCE", dbConn) | ||||
| 	userDbConn = env.GetEnv("DB_CONN_USER", dbConn) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func ApplyConn(override *Override) { | ||||
| 	if override.DbConn != "?" { | ||||
| 		dbConn = override.DbConn | ||||
| 		stateDbConn = override.StateConn | ||||
| 		resourceDbConn = override.ResourceConn | ||||
| 		userDbConn = override.UserConn | ||||
| 	} | ||||
| 	dbConnMode = override.DbConnMode | ||||
| 	if override.StateConn != "?" { | ||||
| 		stateDbConn = override.StateConn | ||||
| 	} | ||||
| 	if override.ResourceConn != "?" { | ||||
| 		resourceDbConn = override.ResourceConn | ||||
| 	} | ||||
| 	if override.UserConn != "?" { | ||||
| 		userDbConn = override.UserConn | ||||
| 	} | ||||
| 
 | ||||
| 	if dbConn == "?" { | ||||
| 		dbConn = "" | ||||
| 	} | ||||
| 
 | ||||
| 	if stateDbConn == "?" { | ||||
| 		stateDbConn = dbConn | ||||
| 		stateDbConnMode = dbConnMode | ||||
| 	} | ||||
| 	if resourceDbConn == "?" { | ||||
| 		resourceDbConn = dbConn | ||||
| 		resourceDbConnMode = dbConnMode | ||||
| 	} | ||||
| 	if userDbConn == "?" { | ||||
| 		userDbConn = dbConn | ||||
| 		userDbConnMode = dbConnMode | ||||
| 	} | ||||
| 
 | ||||
| 	logg.Debugf("conns", "conn", dbConn, "user", userDbConn) | ||||
| 	if override.DbConnMode != storage.DBMODE_ANY { | ||||
| 		dbConnMode = override.DbConnMode | ||||
| 	} | ||||
| 	if override.StateConnMode != storage.DBMODE_ANY { | ||||
| 		stateDbConnMode = override.StateConnMode | ||||
| 	} | ||||
| 	if override.ResourceConnMode != storage.DBMODE_ANY { | ||||
| 		resourceDbConnMode = override.ResourceConnMode | ||||
| 	} | ||||
| 	if override.UserConnMode != storage.DBMODE_ANY { | ||||
| 		userDbConnMode = override.UserConnMode | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func GetConns() (storage.Conns, error) { | ||||
| 	o := storage.NewConns() | ||||
| 	c, err := storage.ToConnDataMode(stateDbConn, stateDbConnMode) | ||||
| 	if err != nil { | ||||
| 		return o, err | ||||
| 	} | ||||
| 	o.Set(c, storage.STORETYPE_STATE) | ||||
| 	c, err = storage.ToConnDataMode(resourceDbConn, resourceDbConnMode) | ||||
| 	if err != nil { | ||||
| 		return o, err | ||||
| 	} | ||||
| 	o.Set(c, storage.STORETYPE_RESOURCE) | ||||
| 	c, err = storage.ToConnDataMode(userDbConn, userDbConnMode) | ||||
| 	if err != nil { | ||||
| 		return o, err | ||||
| 	} | ||||
| 	o.Set(c, storage.STORETYPE_USER) | ||||
| 	return o, nil | ||||
| } | ||||
| 
 | ||||
| // LoadConfig initializes the configuration values after environment variables are loaded.
 | ||||
| func LoadConfig() error { | ||||
| 	configManager = NewConfig() | ||||
| 
 | ||||
| 	// Add configuration keys with validation
 | ||||
| 	configManager.AddKey("HOST", "127.0.0.1", false, nil) | ||||
| 	configManager.AddKey("PORT", "7123", false, func(v string) error { | ||||
| 		_, err := strconv.Atoi(v) | ||||
| 		return err | ||||
| 	}) | ||||
| 	configManager.AddKey("DB_CONN", "", true, nil) | ||||
| 	// ... add other keys ?  or is enough :/ ...
 | ||||
| 
 | ||||
| 	err := setConn() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @ -55,5 +158,7 @@ func LoadConfig() error { | ||||
| 	DefaultLanguage = defaultLanguage | ||||
| 	Languages = languages | ||||
| 
 | ||||
| 	// Report configuration
 | ||||
| 	configManager.Report("INFO") | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										64
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | ||||
| //go:build configreport
 | ||||
| // +build configreport
 | ||||
| 
 | ||||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| ) | ||||
| 
 | ||||
| // go test -tags configreport ./config/...   ---> run with tag
 | ||||
| func TestConfig(t *testing.T) { | ||||
| 	logger := slogging.Get().With("component", "test") | ||||
| 	cfg := NewConfig(logger) | ||||
| 
 | ||||
| 	t.Run("Default Values", func(t *testing.T) { | ||||
| 		cfg.AddKey("TEST_KEY", "default", false, nil) | ||||
| 		value, err := cfg.GetValue("TEST_KEY") | ||||
| 		t.Logf("Got value: %q, error: %v", value, err) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error: %v", err) | ||||
| 		} | ||||
| 		if value != "default" { | ||||
| 			t.Errorf("expected 'default', got '%s'", value) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("Environment Override", func(t *testing.T) { | ||||
| 		os.Setenv("TEST_ENV_KEY", "override") | ||||
| 		defer os.Unsetenv("TEST_ENV_KEY") | ||||
| 
 | ||||
| 		cfg.AddKey("TEST_ENV_KEY", "default", false, nil) | ||||
| 		value, err := cfg.GetValue("TEST_ENV_KEY") | ||||
| 		t.Logf("Got value: %q, error: %v", value, err) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("unexpected error: %v", err) | ||||
| 		} | ||||
| 		if value != "override" { | ||||
| 			t.Errorf("expected 'override', got '%s'", value) | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("Validation", func(t *testing.T) { | ||||
| 		validator := func(v string) error { | ||||
| 			if v != "valid" { | ||||
| 				return fmt.Errorf("invalid value") | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 
 | ||||
| 		cfg.AddKey("VALIDATED_KEY", "valid", false, validator) | ||||
| 		os.Setenv("VALIDATED_KEY", "invalid") | ||||
| 		defer os.Unsetenv("VALIDATED_KEY") | ||||
| 
 | ||||
| 		value, err := cfg.GetValue("VALIDATED_KEY") | ||||
| 		t.Logf("Got value: %q, error: %v", value, err) | ||||
| 		if err == nil { | ||||
| 			t.Error("expected validation error, got nil") | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										84
									
								
								config/reporter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								config/reporter.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| //go:build configreport
 | ||||
| 
 | ||||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/env" | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| ) | ||||
| 
 | ||||
| // ConfigValue represents a configuration key-value pair
 | ||||
| type ConfigValue struct { | ||||
| 	Key       string | ||||
| 	Default   string | ||||
| 	Validator func(string) error | ||||
| 	Sensitive bool | ||||
| } | ||||
| 
 | ||||
| // Config handles configuration management and reporting
 | ||||
| type Config struct { | ||||
| 	values map[string]ConfigValue | ||||
| 	logger *slogging.Slog | ||||
| } | ||||
| 
 | ||||
| func NewConfig(logging interface{}) *Config { | ||||
| 	return &Config{ | ||||
| 		values: make(map[string]ConfigValue), | ||||
| 		logger: slogging.Get().With("component", "visedriver-config-reporter"), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // AddKey registers a new configuration key with optional validation
 | ||||
| func (c *Config) AddKey(key string, defaultValue string, sensitive bool, validator func(string) error) { | ||||
| 	c.values[key] = ConfigValue{ | ||||
| 		Key:       key, | ||||
| 		Default:   defaultValue, | ||||
| 		Validator: validator, | ||||
| 		Sensitive: sensitive, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetValue returns the value for a given key, applying environment override if present
 | ||||
| func (c *Config) GetValue(key string) (string, error) { | ||||
| 	// Find config value by key
 | ||||
| 	var cv ConfigValue | ||||
| 	for _, v := range c.values { | ||||
| 		if v.Key == key { | ||||
| 			cv = v | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if cv.Key == "" { | ||||
| 		return "", fmt.Errorf("configuration key not found: %s", key) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get value from environment or default
 | ||||
| 	value := env.GetEnv(key, cv.Default) | ||||
| 
 | ||||
| 	// Validate if validator exists
 | ||||
| 	if cv.Validator != nil && cv.Validator(value) != nil { | ||||
| 		return "", fmt.Errorf("invalid value for key %s", key) | ||||
| 	} | ||||
| 
 | ||||
| 	return value, nil | ||||
| } | ||||
| 
 | ||||
| // Report outputs all configuration values at the specified log level
 | ||||
| func (c *Config) Report(level string) { | ||||
| 	for _, cv := range c.values { | ||||
| 		value, err := c.GetValue(cv.Key) | ||||
| 		if err != nil { | ||||
| 			c.logger.Errorf("Error getting value for %s: %v", cv.Key, err) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if cv.Sensitive { | ||||
| 			value = "****" | ||||
| 		} | ||||
| 
 | ||||
| 		c.logger.Debugf("config set", cv.Key, value) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										18
									
								
								config/reporter_noop.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								config/reporter_noop.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| //go:build !configreport
 | ||||
| 
 | ||||
| package config | ||||
| 
 | ||||
| type Config struct{} | ||||
| 
 | ||||
| func NewConfig() *Config { | ||||
| 	return &Config{} | ||||
| } | ||||
| 
 | ||||
| func (c *Config) AddKey(key string, defaultValue string, sensitive bool, validator func(string) error) { | ||||
| } | ||||
| 
 | ||||
| func (c *Config) GetValue(key string) (string, error) { | ||||
| 	return "", nil | ||||
| } | ||||
| 
 | ||||
| func (c *Config) Report(level string) {} | ||||
| @ -10,7 +10,7 @@ This document describes component versions: | ||||
| 
 | ||||
| ## User profile data | ||||
| 
 | ||||
| All user profile items are stored under keys matching the user's session id, prefixed with the 8-bit value `git.defalsify.org/vise.git/db.DATATYPE_USERDATA` (32), and followed with a 16-big big-endian value subprefix. | ||||
| All user profile items are stored under keys matching the user's session id, prefixed with the 8-bit value `github.com/grassrootseconomics/go-vise/db.DATATYPE_USERDATA` (32), and followed with a 16-big big-endian value subprefix. | ||||
| 
 | ||||
| For example, given the sessionId `+254123` and the key `git.grassecon.net/urdt-ussd/common.DATA_PUBLIC_KEY` (2) will be stored under the key: | ||||
| 
 | ||||
|  | ||||
| @ -2,8 +2,9 @@ package entry | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"git.defalsify.org/vise.git/resource" | ||||
| 	"git.defalsify.org/vise.git/persist" | ||||
| 
 | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| ) | ||||
| 
 | ||||
| type EntryHandler interface { | ||||
|  | ||||
							
								
								
									
										34
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								go.mod
									
									
									
									
									
								
							| @ -1,39 +1,27 @@ | ||||
| module git.grassecon.net/grassrootseconomics/visedriver | ||||
| 
 | ||||
| go 1.23.0 | ||||
| go 1.24 | ||||
| 
 | ||||
| toolchain go1.24.6 | ||||
| 
 | ||||
| require ( | ||||
| 	git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d | ||||
| 	github.com/alecthomas/assert/v2 v2.2.2 | ||||
| 	github.com/gofrs/uuid v4.4.0+incompatible | ||||
| 	github.com/grassrootseconomics/eth-custodial v1.3.0-beta | ||||
| 	github.com/grassrootseconomics/ussd-data-service v1.2.0-beta | ||||
| 	github.com/jackc/pgx/v5 v5.7.1 | ||||
| 	github.com/grassrootseconomics/go-vise v0.5.0 | ||||
| 	github.com/jackc/pgx/v5 v5.7.5 | ||||
| 	github.com/joho/godotenv v1.5.1 | ||||
| 	github.com/peteole/testdata-loader v0.3.0 | ||||
| 	github.com/stretchr/testify v1.9.0 | ||||
| 	golang.org/x/crypto v0.27.0 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/alecthomas/repr v0.2.0 // indirect | ||||
| 	github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||||
| 	github.com/fxamacker/cbor/v2 v2.4.0 // indirect | ||||
| 	github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect | ||||
| 	github.com/hexops/gotextdiff v1.0.3 // indirect | ||||
| 	github.com/fxamacker/cbor/v2 v2.9.0 // indirect | ||||
| 	github.com/jackc/pgpassfile v1.0.0 // indirect | ||||
| 	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect | ||||
| 	github.com/jackc/puddle/v2 v2.2.2 // indirect | ||||
| 	github.com/kr/text v0.2.0 // indirect | ||||
| 	github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect | ||||
| 	github.com/leonelquinteros/gotext v1.7.2 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | ||||
| 	github.com/rogpeppe/go-internal v1.13.1 // indirect | ||||
| 	github.com/stretchr/objx v0.5.2 // indirect | ||||
| 	github.com/stretchr/testify v1.9.0 // indirect | ||||
| 	github.com/x448/float16 v0.8.4 // indirect | ||||
| 	golang.org/x/sync v0.8.0 // indirect | ||||
| 	golang.org/x/sys v0.25.0 // indirect | ||||
| 	golang.org/x/text v0.18.0 // indirect | ||||
| 	gopkg.in/leonelquinteros/gotext.v1 v1.3.1 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| 	golang.org/x/crypto v0.40.0 // indirect | ||||
| 	golang.org/x/sync v0.16.0 // indirect | ||||
| 	golang.org/x/text v0.27.0 // indirect | ||||
| ) | ||||
|  | ||||
							
								
								
									
										65
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,76 +1,47 @@ | ||||
| git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d h1:bPAOVZOX4frSGhfOdcj7kc555f8dc9DmMd2YAyC2AMw= | ||||
| git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= | ||||
| 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/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= | ||||
| github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= | ||||
| github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE= | ||||
| github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= | ||||
| github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= | ||||
| github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= | ||||
| github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= | ||||
| github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= | ||||
| github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU= | ||||
| github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo= | ||||
| github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= | ||||
| github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= | ||||
| github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= | ||||
| github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= | ||||
| github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||
| github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= | ||||
| github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= | ||||
| github.com/grassrootseconomics/go-vise v0.5.0 h1:FRg2de55Eb5SisrgTBeFWfWX+sXwp5q9r7YWtKWDwsk= | ||||
| github.com/grassrootseconomics/go-vise v0.5.0/go.mod h1:b2/q4jfTu2i1wyUwYUu7FYq4m2f1AZv8MpiDM3ZcoGo= | ||||
| github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= | ||||
| github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= | ||||
| github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= | ||||
| github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= | ||||
| github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= | ||||
| github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= | ||||
| github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= | ||||
| github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= | ||||
| github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= | ||||
| github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= | ||||
| github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | ||||
| github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= | ||||
| github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= | ||||
| github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||
| github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY= | ||||
| github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk= | ||||
| github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s= | ||||
| github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A= | ||||
| github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc= | ||||
| github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= | ||||
| github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= | ||||
| github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= | ||||
| github.com/pashagolub/pgxmock/v4 v4.7.0 h1:de2ORuFYyjwOQR7NBm57+321RnZxpYiuUjsmqRiqgh8= | ||||
| github.com/pashagolub/pgxmock/v4 v4.7.0/go.mod h1:9L57pC193h2aKRHVyiiE817avasIPZnPwPlw3JczWvM= | ||||
| github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I= | ||||
| github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | ||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | ||||
| github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= | ||||
| github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= | ||||
| github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= | ||||
| golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= | ||||
| golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= | ||||
| golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | ||||
| golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= | ||||
| golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= | ||||
| golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= | ||||
| golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= | ||||
| golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | ||||
| golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= | ||||
| golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= | ||||
| golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= | ||||
| golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||
| golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= | ||||
| golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||
| gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc= | ||||
| gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|  | ||||
| @ -1,84 +1,87 @@ | ||||
| package request | ||||
| 
 | ||||
| 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/grassrootseconomics/visedriver/storage" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/errors" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/grassrootseconomics/go-vise/db" | ||||
| 	"github.com/grassrootseconomics/go-vise/engine" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/entry" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/errors" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/storage" | ||||
| ) | ||||
| 
 | ||||
| type EngineFunc func(engine.Config, resource.Resource, *persist.Persister) engine.Engine | ||||
| 
 | ||||
| type BaseRequestHandler struct { | ||||
| 	cfgTemplate engine.Config | ||||
| 	rp RequestParser | ||||
| 	rs resource.Resource | ||||
| 	hn entry.EntryHandler | ||||
| 	provider storage.StorageProvider | ||||
| 	rp          RequestParser | ||||
| 	rs          resource.Resource | ||||
| 	hn          entry.EntryHandler | ||||
| 	provider    storage.StorageProvider | ||||
| 	engineFunc	EngineFunc | ||||
| } | ||||
| 
 | ||||
| //func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn *handlers.Handlers) *BaseRequestHandler {
 | ||||
| func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn entry.EntryHandler) *BaseRequestHandler { | ||||
| 	return &BaseRequestHandler{ | ||||
| 	h := &BaseRequestHandler{ | ||||
| 		cfgTemplate: cfg, | ||||
| 		rs:          rs, | ||||
| 		hn:          hn, | ||||
| 		rp:          rp, | ||||
| 		provider:    storage.NewSimpleStorageProvider(stateDb, userdataDb), | ||||
| 	} | ||||
| 	h.engineFunc = h.getDefaultEngine | ||||
| 	return h | ||||
| } | ||||
| 
 | ||||
| func (f *BaseRequestHandler) Shutdown() { | ||||
| 	err := f.provider.Close() | ||||
| func (f *BaseRequestHandler) WithEngineFunc(fn EngineFunc) *BaseRequestHandler { | ||||
| 	f.engineFunc = fn | ||||
| 	return f | ||||
| } | ||||
| 
 | ||||
| func (f *BaseRequestHandler) Shutdown(ctx context.Context) { | ||||
| 	err := f.provider.Close(ctx) | ||||
| 	if err != nil { | ||||
| 		logg.Errorf("handler shutdown error", "err", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (f *BaseRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { | ||||
| 	return f.engineFunc(cfg, rs, pr) | ||||
| } | ||||
| 
 | ||||
| func (f *BaseRequestHandler) getDefaultEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine { | ||||
| 	en := engine.NewEngine(cfg, rs) | ||||
| 	en = en.WithPersister(pr) | ||||
| 	en = en.WithFirst(f.hn.Init) | ||||
| 	if f.cfgTemplate.EngineDebug { | ||||
| 		en = en.WithDebug(nil) | ||||
| 	} | ||||
| 	return en | ||||
| } | ||||
| 
 | ||||
| func(f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) { | ||||
| func (f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) { | ||||
| 	var r bool | ||||
| 	var err error | ||||
| 	var ok bool | ||||
| 
 | ||||
| 	logg.InfoCtxf(rqs.Ctx, "new request", "data", rqs) | ||||
| 
 | ||||
| 	rqs.Storage, err = f.provider.Get(rqs.Config.SessionId) | ||||
| 	rqs.Storage, err = f.provider.Get(rqs.Ctx, rqs.Config.SessionId) | ||||
| 	if err != nil { | ||||
| 		logg.ErrorCtxf(rqs.Ctx, "", "storage get error", err) | ||||
| 		return rqs, errors.ErrStorage | ||||
| 	} | ||||
| 
 | ||||
| 	//f.hn = f.hn.WithPersister(rqs.Storage.Persister)
 | ||||
| 	f.hn.SetPersister(rqs.Storage.Persister) | ||||
| 	defer func() { | ||||
| 		f.hn.Exit() | ||||
| 	}() | ||||
| 	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, errors.ErrEngineType | ||||
| 	} | ||||
| 	en = en.WithFirst(f.hn.Init) | ||||
| 	if rqs.Config.EngineDebug { | ||||
| 		en = en.WithDebug(nil) | ||||
| 	} | ||||
| 	rqs.Engine = en | ||||
| 
 | ||||
| 	rqs.Engine = f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) | ||||
| 	r, err = rqs.Engine.Exec(rqs.Ctx, rqs.Input) | ||||
| 	if err != nil { | ||||
| 		perr := f.provider.Put(rqs.Config.SessionId, rqs.Storage) | ||||
| 		perr := f.provider.Put(rqs.Ctx, rqs.Config.SessionId, rqs.Storage) | ||||
| 		rqs.Storage = nil | ||||
| 		if perr != nil { | ||||
| 			logg.ErrorCtxf(rqs.Ctx, "", "storage put error", perr) | ||||
| @ -90,21 +93,21 @@ func(f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) | ||||
| 	return rqs, nil | ||||
| } | ||||
| 
 | ||||
| func(f *BaseRequestHandler) Output(rqs RequestSession) (RequestSession,  error) { | ||||
| func (f *BaseRequestHandler) Output(rqs RequestSession) (RequestSession, error) { | ||||
| 	var err error | ||||
| 	_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer) | ||||
| 	return rqs, err | ||||
| } | ||||
| 
 | ||||
| func(f *BaseRequestHandler) Reset(rqs RequestSession) (RequestSession, error) { | ||||
| 	defer f.provider.Put(rqs.Config.SessionId, rqs.Storage) | ||||
| 	return rqs, rqs.Engine.Finish() | ||||
| func (f *BaseRequestHandler) Reset(ctx context.Context, rqs RequestSession) (RequestSession, error) { | ||||
| 	defer f.provider.Put(ctx, rqs.Config.SessionId, rqs.Storage) | ||||
| 	return rqs, rqs.Engine.Finish(ctx) | ||||
| } | ||||
| 
 | ||||
| func (f *BaseRequestHandler) GetConfig() engine.Config { | ||||
| 	return f.cfgTemplate | ||||
| } | ||||
| 
 | ||||
| func(f *BaseRequestHandler) GetRequestParser() RequestParser { | ||||
| func (f *BaseRequestHandler) GetRequestParser() RequestParser { | ||||
| 	return f.rp | ||||
| } | ||||
|  | ||||
| @ -4,13 +4,13 @@ import ( | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"git.defalsify.org/vise.git/logging" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/request" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/errors" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/request" | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	logg = logging.NewVanilla().WithDomain("visedriver.http.session") | ||||
| 	logg = slogging.Get().With("component", "visedriver.http.session") | ||||
| ) | ||||
| 
 | ||||
| // HTTPRequestHandler implements the session handler for HTTP
 | ||||
| @ -80,7 +80,7 @@ func (hh *HTTPRequestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request | ||||
| 	w.WriteHeader(200) | ||||
| 	w.Header().Set("Content-Type", "text/plain") | ||||
| 	rqs, err = hh.Output(rqs) | ||||
| 	rqs, perr = hh.Reset(rqs) | ||||
| 	rqs, perr = hh.Reset(rqs.Ctx, rqs) | ||||
| 	if err != nil { | ||||
| 		hh.WriteError(w, 500, err) | ||||
| 		return | ||||
|  | ||||
| @ -8,10 +8,10 @@ import ( | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"git.defalsify.org/vise.git/engine" | ||||
| 	"github.com/grassrootseconomics/go-vise/engine" | ||||
| 	viseerrors "git.grassecon.net/grassrootseconomics/visedriver/errors" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/request" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks" | ||||
| ) | ||||
| 
 | ||||
| // invalidRequestType is a custom type to test invalid request scenarios
 | ||||
| @ -88,7 +88,7 @@ func TestRequestHandler_ServeHTTP(t *testing.T) { | ||||
| 				OutputFunc: func(rs request.RequestSession) (request.RequestSession, error) { | ||||
| 					return rs, tt.outputErr | ||||
| 				}, | ||||
| 				ResetFunc: func(rs request.RequestSession) (request.RequestSession, error) { | ||||
| 				ResetFunc: func(ctx context.Context, rs request.RequestSession) (request.RequestSession, error) { | ||||
| 					return rs, tt.resetErr | ||||
| 				}, | ||||
| 				GetRequestParserFunc: func() request.RequestParser { | ||||
| @ -165,7 +165,7 @@ func TestDefaultRequestParser_GetSessionId(t *testing.T) { | ||||
| 
 | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			id, err := parser.GetSessionId(context.Background(),tt.request) | ||||
| 			id, err := parser.GetSessionId(context.Background(), tt.request) | ||||
| 
 | ||||
| 			if id != tt.expectedID { | ||||
| 				t.Errorf("Expected session ID %s, got %s", tt.expectedID, id) | ||||
|  | ||||
| @ -4,39 +4,39 @@ import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 
 | ||||
| 	"git.defalsify.org/vise.git/resource" | ||||
| 	"git.defalsify.org/vise.git/persist" | ||||
| 	"git.defalsify.org/vise.git/engine" | ||||
| 	"git.defalsify.org/vise.git/logging" | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/storage" | ||||
| 	"github.com/grassrootseconomics/go-vise/engine" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	logg = logging.NewVanilla().WithDomain("visedriver.request") | ||||
| 	logg = slogging.Get().With("component", "visedriver.request") | ||||
| ) | ||||
| 
 | ||||
| type RequestSession struct { | ||||
| 	Ctx context.Context | ||||
| 	Config engine.Config | ||||
| 	Engine engine.Engine | ||||
| 	Input []byte | ||||
| 	Storage *storage.Storage | ||||
| 	Writer io.Writer | ||||
| 	Ctx      context.Context | ||||
| 	Config   engine.Config | ||||
| 	Engine   engine.Engine | ||||
| 	Input    []byte | ||||
| 	Storage  *storage.Storage | ||||
| 	Writer   io.Writer | ||||
| 	Continue bool | ||||
| } | ||||
| 
 | ||||
| // TODO: seems like can remove this.
 | ||||
| type RequestParser interface { | ||||
| 	GetSessionId(ctx context.Context, rq any) (string, error) | ||||
| 	GetInput(rq any) ([]byte, error) | ||||
| 	GetSessionId(context.Context, any) (string, error) | ||||
| 	GetInput(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() | ||||
| 	GetEngine(engine.Config, resource.Resource, *persist.Persister) engine.Engine | ||||
| 	Process(RequestSession) (RequestSession, error) | ||||
| 	Output(RequestSession) (RequestSession, error) | ||||
| 	Reset(context.Context, RequestSession) (RequestSession, error) | ||||
| 	Shutdown(ctx context.Context) | ||||
| } | ||||
|  | ||||
							
								
								
									
										98
									
								
								storage/conn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								storage/conn.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | ||||
| package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| ) | ||||
| 
 | ||||
| type DbMode uint8 | ||||
| 
 | ||||
| const ( | ||||
| 	DBTYPE_NONE = iota | ||||
| 	DBTYPE_MEM | ||||
| 	DBTYPE_FS | ||||
| 	DBTYPE_GDBM | ||||
| 	DBTYPE_POSTGRES | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	DBMODE_ANY DbMode = iota | ||||
| 	DBMODE_BINARY | ||||
| 	DBMODE_TEXT | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	STORETYPE_STATE = iota | ||||
| 	STORETYPE_RESOURCE | ||||
| 	STORETYPE_USER | ||||
| 	_STORETYPE_MAX | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	DbModeDebug  = []string{"ANY", "BIN", "TXT"} | ||||
| 	DbTypeDebug  = []string{"NONE", "MEM", "FS", "GDBM", "POSTGRES"} | ||||
| 	DbStoreDebug = []string{"STATE", "RESOURCE", "USER"} | ||||
| ) | ||||
| 
 | ||||
| type Conns map[int8]ConnData | ||||
| 
 | ||||
| func NewConns() Conns { | ||||
| 	c := make(Conns) | ||||
| 	return c | ||||
| } | ||||
| 
 | ||||
| func (c Conns) Set(conn ConnData, typ int8) { | ||||
| 	if typ < 0 || typ >= _STORETYPE_MAX { | ||||
| 		panic(fmt.Errorf("invalid store type: %d", typ)) | ||||
| 	} | ||||
| 	c[typ] = conn | ||||
| } | ||||
| 
 | ||||
| func (c Conns) Have(conn *ConnData) int8 { | ||||
| 	for i := range _STORETYPE_MAX { | ||||
| 		ii := int8(i) | ||||
| 		v, ok := c[ii] | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		if v.Raw() == conn.Raw() { | ||||
| 			if v.Mode() == DBMODE_ANY || v.Mode() == conn.Mode() { | ||||
| 				return ii | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return -1 | ||||
| } | ||||
| 
 | ||||
| type ConnData struct { | ||||
| 	typ    int | ||||
| 	str    string | ||||
| 	domain string | ||||
| 	mode   DbMode | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) DbType() int { | ||||
| 	return cd.typ | ||||
| } | ||||
| 
 | ||||
| func (cd ConnData) String() string { | ||||
| 	return fmt.Sprintf("conn: %s, mod %s, typ %s", cd.str, DbModeDebug[uint8(cd.mode)], DbTypeDebug[uint8(cd.typ)]) | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Domain() string { | ||||
| 	return cd.domain | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Mode() DbMode { | ||||
| 	return cd.mode | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Path() string { | ||||
| 	v, _ := url.Parse(cd.str) | ||||
| 	v.RawQuery = "" | ||||
| 	return v.String() | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Raw() string { | ||||
| 	return cd.str | ||||
| } | ||||
| @ -1,127 +0,0 @@ | ||||
| package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"git.defalsify.org/vise.git/db" | ||||
| 	gdbmdb "git.defalsify.org/vise.git/db/gdbm" | ||||
| 	"git.defalsify.org/vise.git/lang" | ||||
| 	"git.defalsify.org/vise.git/logging" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	logg = logging.NewVanilla().WithDomain("gdbmstorage") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	dbC map[string]chan db.Db | ||||
| ) | ||||
| 
 | ||||
| type ThreadGdbmDb struct { | ||||
| 	db db.Db | ||||
| 	connStr string | ||||
| } | ||||
| 
 | ||||
| func NewThreadGdbmDb() *ThreadGdbmDb { | ||||
| 	if dbC == nil { | ||||
| 		dbC = make(map[string]chan db.Db) | ||||
| 	} | ||||
| 	return &ThreadGdbmDb{} | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Connect(ctx context.Context, connStr string) error { | ||||
| 	var ok bool | ||||
| 	_, ok = dbC[connStr] | ||||
| 	if ok { | ||||
| 		logg.WarnCtxf(ctx, "already registered thread gdbm, skipping", "connStr", connStr) | ||||
| 		return nil | ||||
| 	} | ||||
| 	gdb := gdbmdb.NewGdbmDb() | ||||
| 	err := gdb.Connect(ctx, connStr) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	dbC[connStr] = make(chan db.Db, 1) | ||||
| 	dbC[connStr]<- gdb | ||||
| 	tdb.connStr = connStr | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) reserve() { | ||||
| 	if tdb.db == nil { | ||||
| 		tdb.db = <-dbC[tdb.connStr] | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) release() { | ||||
| 	if tdb.db == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	dbC[tdb.connStr] <- tdb.db | ||||
| 	tdb.db = nil | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) SetPrefix(pfx uint8) { | ||||
| 	tdb.reserve() | ||||
| 	tdb.db.SetPrefix(pfx) | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) SetSession(sessionId string) { | ||||
| 	tdb.reserve() | ||||
| 	tdb.db.SetSession(sessionId) | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) SetLanguage(lng *lang.Language) { | ||||
| 	tdb.reserve() | ||||
| 	tdb.db.SetLanguage(lng) | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Safe() bool { | ||||
| 	tdb.reserve() | ||||
| 	v := tdb.db.Safe() | ||||
| 	tdb.release() | ||||
| 	return v | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Prefix() uint8 { | ||||
| 	tdb.reserve() | ||||
| 	v := tdb.db.Prefix() | ||||
| 	tdb.release() | ||||
| 	return v | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) SetLock(typ uint8, locked bool) error { | ||||
| 	tdb.reserve() | ||||
| 	err := tdb.db.SetLock(typ, locked) | ||||
| 	tdb.release() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Put(ctx context.Context, key []byte, val []byte) error { | ||||
| 	tdb.reserve() | ||||
| 	err := tdb.db.Put(ctx, key, val) | ||||
| 	tdb.release() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Get(ctx context.Context, key []byte) ([]byte, error) { | ||||
| 	tdb.reserve() | ||||
| 	v, err := tdb.db.Get(ctx, key) | ||||
| 	tdb.release() | ||||
| 	return v, err | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Close() error { | ||||
| 	tdb.reserve() | ||||
| 	close(dbC[tdb.connStr]) | ||||
| 	delete(dbC, tdb.connStr) | ||||
| 	err := tdb.db.Close() | ||||
| 	tdb.db = nil | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func(tdb *ThreadGdbmDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) { | ||||
| 	tdb.reserve() | ||||
| 	defer tdb.release() | ||||
| 	return tdb.db.Dump(ctx, key) | ||||
| } | ||||
| @ -4,38 +4,9 @@ import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	DBTYPE_MEM = iota | ||||
| 	DBTYPE_GDBM | ||||
| 	DBTYPE_POSTGRES | ||||
| ) | ||||
| 
 | ||||
| type ConnData struct { | ||||
| 	typ int | ||||
| 	str string | ||||
| 	domain string | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) DbType() int { | ||||
| 	return cd.typ | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) String() string { | ||||
| 	return cd.str | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Domain() string { | ||||
| 	return cd.domain | ||||
| } | ||||
| 
 | ||||
| func (cd *ConnData) Path() string { | ||||
| 	v, _ := url.Parse(cd.str) | ||||
| 	v.RawQuery = "" | ||||
| 	return v.String() | ||||
| } | ||||
| 
 | ||||
| func probePostgres(s string) (string, string, bool) { | ||||
| 	domain := "public" | ||||
| 	v, err := url.Parse(s) | ||||
| @ -53,21 +24,62 @@ func probePostgres(s string) (string, string, bool) { | ||||
| } | ||||
| 
 | ||||
| func probeGdbm(s string) (string, string, bool) { | ||||
| 	if !path.IsAbs(s) { | ||||
| 	domain := "public" | ||||
| 	v, err := url.Parse(s) | ||||
| 	if err != nil { | ||||
| 		return "", "", false | ||||
| 	} | ||||
| 	if v.Scheme != "gdbm" { | ||||
| 		return "", "", false | ||||
| 	} | ||||
| 	s = v.Path | ||||
| 	return s, domain, true | ||||
| } | ||||
| 
 | ||||
| func probeFs(s string) (string, string, bool) { | ||||
| 	var err error | ||||
| 
 | ||||
| 	v, _ := url.Parse(s) | ||||
| 	if v.Scheme != "" && v.Scheme != "file://" { | ||||
| 		return "", "", false | ||||
| 	} | ||||
| 
 | ||||
| 	if !path.IsAbs(s) { | ||||
| 		s, err = filepath.Abs(s) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 	} | ||||
| 	s = path.Clean(s) | ||||
| 	return s, "", true | ||||
| } | ||||
| 
 | ||||
| func probeMem(s string) (string, string, bool) { | ||||
| 	if s != "" { | ||||
| 		return "", "", false | ||||
| 	} | ||||
| 	return "", "", true | ||||
| } | ||||
| 
 | ||||
| func ToConnDataMode(connStr string, mode DbMode) (ConnData, error) { | ||||
| 	o, err := ToConnData(connStr) | ||||
| 	if err != nil { | ||||
| 		return o, err | ||||
| 	} | ||||
| 	o.mode = mode | ||||
| 	return o, nil | ||||
| } | ||||
| 
 | ||||
| func ToConnData(connStr string) (ConnData, error) { | ||||
| 	var o ConnData | ||||
| 
 | ||||
| 	if connStr == "" { | ||||
| 	v, domain, ok := probeMem(connStr) | ||||
| 	if ok { | ||||
| 		o.typ = DBTYPE_MEM | ||||
| 		return o, nil | ||||
| 	} | ||||
| 
 | ||||
| 	v, domain, ok := probePostgres(connStr) | ||||
| 	v, domain, ok = probePostgres(connStr) | ||||
| 	if ok { | ||||
| 		o.typ = DBTYPE_POSTGRES | ||||
| 		o.str = v | ||||
| @ -82,5 +94,12 @@ func ToConnData(connStr string) (ConnData, error) { | ||||
| 		return o, nil | ||||
| 	} | ||||
| 
 | ||||
| 	v, _, ok = probeFs(connStr) | ||||
| 	if ok { | ||||
| 		o.typ = DBTYPE_FS | ||||
| 		o.str = v | ||||
| 		return o, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return o, fmt.Errorf("invalid connection string: %s", connStr) | ||||
| } | ||||
|  | ||||
| @ -5,24 +5,53 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| func TestParseConnStr(t *testing.T) { | ||||
| 	_, err := ToConnData("postgres://foo:bar@localhost:5432/baz") | ||||
| 	v, err := ToConnData("postgres://foo:bar@localhost:5432/baz") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err)	 | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ToConnData("/foo/bar") | ||||
| 	if v.DbType() != DBTYPE_POSTGRES { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_POSTGRES, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("gdbm:///foo/bar") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err)	 | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ToConnData("/foo/bar/") | ||||
| 	if v.DbType() != DBTYPE_GDBM { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_GDBM, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("/foo/bar") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err)	 | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ToConnData("foo/bar") | ||||
| 	if v.DbType() != DBTYPE_FS { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_FS, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("/foo/bar/") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if v.DbType() != DBTYPE_FS { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_FS, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("foo/bar") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if v.DbType() != DBTYPE_FS { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_FS, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if v.DbType() != DBTYPE_MEM { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_MEM, v.DbType()) | ||||
| 	} | ||||
| 	v, err = ToConnData("http://foo/bar") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error") | ||||
| 	} | ||||
| 	_, err = ToConnData("http://foo/bar") | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error") | ||||
| 	if v.DbType() != DBTYPE_NONE { | ||||
| 		t.Fatalf("expected type %v, got %v", DBTYPE_NONE, v.DbType()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,10 @@ | ||||
| package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"git.defalsify.org/vise.git/db" | ||||
| 	"git.defalsify.org/vise.git/persist" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/grassrootseconomics/go-vise/db" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @ -10,14 +12,18 @@ const ( | ||||
| ) | ||||
| 
 | ||||
| type Storage struct { | ||||
| 	Persister *persist.Persister | ||||
| 	UserdataDb db.Db	 | ||||
| 	Persister  *persist.Persister | ||||
| 	UserdataDb db.Db | ||||
| } | ||||
| 
 | ||||
| func (s *Storage) Close(ctx context.Context) error { | ||||
| 	return s.UserdataDb.Close(ctx) | ||||
| } | ||||
| 
 | ||||
| type StorageProvider interface { | ||||
| 	Get(sessionId string) (*Storage, error) | ||||
| 	Put(sessionId string, storage *Storage) error | ||||
| 	Close() error | ||||
| 	Get(ctx context.Context, sessionId string) (*Storage, error) | ||||
| 	Put(ctx context.Context, sessionId string, storage *Storage) error | ||||
| 	Close(ctx context.Context) error | ||||
| } | ||||
| 
 | ||||
| type SimpleStorageProvider struct { | ||||
| @ -29,20 +35,22 @@ func NewSimpleStorageProvider(stateStore db.Db, userdataStore db.Db) StorageProv | ||||
| 	pe = pe.WithFlush() | ||||
| 	return &SimpleStorageProvider{ | ||||
| 		Storage: &Storage{ | ||||
| 			Persister: pe, | ||||
| 			Persister:  pe, | ||||
| 			UserdataDb: userdataStore, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (p *SimpleStorageProvider) Get(sessionId string) (*Storage, error) { | ||||
| func (p *SimpleStorageProvider) Get(ctx context.Context, sessionId string) (*Storage, error) { | ||||
| 	p.Storage.UserdataDb.Start(ctx) | ||||
| 	return p.Storage, nil | ||||
| } | ||||
| 
 | ||||
| func (p *SimpleStorageProvider) Put(sessionId string, storage *Storage) error { | ||||
| func (p *SimpleStorageProvider) Put(ctx context.Context, sessionId string, storage *Storage) error { | ||||
| 	storage.UserdataDb.Stop(ctx) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *SimpleStorageProvider) Close() error { | ||||
| 	return p.Storage.UserdataDb.Close() | ||||
| func (p *SimpleStorageProvider) Close(ctx context.Context) error { | ||||
| 	return p.Storage.Close(ctx) | ||||
| } | ||||
|  | ||||
| @ -2,23 +2,23 @@ package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	 | ||||
| 
 | ||||
| 	"github.com/grassrootseconomics/go-vise/db" | ||||
| 	fsdb "github.com/grassrootseconomics/go-vise/db/fs" | ||||
| 	memdb "github.com/grassrootseconomics/go-vise/db/mem" | ||||
| 	"github.com/grassrootseconomics/go-vise/db/postgres" | ||||
| 	"github.com/grassrootseconomics/go-vise/lang" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| 	slogging "github.com/grassrootseconomics/go-vise/slog" | ||||
| 	"github.com/jackc/pgx/v5/pgxpool" | ||||
| 	"git.defalsify.org/vise.git/db" | ||||
| 	fsdb "git.defalsify.org/vise.git/db/fs" | ||||
| 	"git.defalsify.org/vise.git/db/postgres" | ||||
| 	"git.defalsify.org/vise.git/lang" | ||||
| 	"git.defalsify.org/vise.git/logging" | ||||
| 	"git.defalsify.org/vise.git/persist" | ||||
| 	"git.defalsify.org/vise.git/resource" | ||||
| 	gdbmstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db/gdbm" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	logg = logging.NewVanilla().WithDomain("storage") | ||||
| 	logg = slogging.Get().With("component", "storage") | ||||
| ) | ||||
| 
 | ||||
| type StorageService interface { | ||||
| @ -27,68 +27,101 @@ type StorageService interface { | ||||
| 	GetResource(ctx context.Context) (resource.Resource, error) | ||||
| } | ||||
| 
 | ||||
| // TODO: Support individual backend for each store (conndata)
 | ||||
| type MenuStorageService struct { | ||||
| 	conn ConnData | ||||
| 	resourceDir   string | ||||
| 	poResource    resource.Resource | ||||
| 	resourceStore db.Db | ||||
| 	stateStore    db.Db | ||||
| 	userDataStore db.Db | ||||
| 	conns      Conns | ||||
| 	poResource resource.Resource | ||||
| 	store      map[int8]db.Db | ||||
| } | ||||
| 
 | ||||
| func NewMenuStorageService(conn ConnData, resourceDir string) *MenuStorageService { | ||||
| func NewMenuStorageService(conn Conns) *MenuStorageService { | ||||
| 	return &MenuStorageService{ | ||||
| 		conn: conn, | ||||
| 		resourceDir: resourceDir, | ||||
| 		conns: conn, | ||||
| 		store: make(map[int8]db.Db), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) WithResourceDir(resourceDir string) *MenuStorageService { | ||||
| 	ms.resourceDir = resourceDir | ||||
| func (ms *MenuStorageService) WithDb(store db.Db, typ int8) *MenuStorageService { | ||||
| 	var err error | ||||
| 	if ms.store[typ] != nil { | ||||
| 		panic(fmt.Errorf("db already set for typ: %d", typ)) | ||||
| 	} | ||||
| 	ms.store[typ] = store | ||||
| 	ms.conns[typ], err = ToConnData(store.Connection()) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return ms | ||||
| } | ||||
| 
 | ||||
| // TODO: allow fsdb, memdb
 | ||||
| func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, section string, typ string) (db.Db, error) { | ||||
| 	var newDb db.Db | ||||
| func (ms *MenuStorageService) checkDb(ctx context.Context, typ int8) db.Db { | ||||
| 	store := ms.store[typ] | ||||
| 	if store != nil { | ||||
| 		return store | ||||
| 	} | ||||
| 	connData := ms.conns[typ] | ||||
| 	logg.DebugCtxf(ctx, "db check", "conn", connData, "store", DbStoreDebug[typ]) | ||||
| 	v := ms.conns.Have(&connData) | ||||
| 	if v == -1 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	src := ms.store[v] | ||||
| 	if src == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	ms.store[typ] = ms.store[v] | ||||
| 	logg.DebugCtxf(ctx, "found existing db", "typ", typ, "srctyp", v, "store", ms.store[typ], "srcstore", ms.store[v], "conn", connData) | ||||
| 	return ms.store[typ] | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, section string, typ int8) (db.Db, error) { | ||||
| 	var err error | ||||
| 
 | ||||
| 	if existingDb != nil { | ||||
| 		return existingDb, nil | ||||
| 	newDb := ms.checkDb(ctx, typ) | ||||
| 	if newDb != nil { | ||||
| 		logg.InfoCtxf(ctx, "using existing db", "typ", typ, "db", newDb) | ||||
| 		return newDb, nil | ||||
| 	} | ||||
| 
 | ||||
| 	connStr := ms.conn.String() | ||||
| 	dbTyp := ms.conn.DbType() | ||||
| 	connData := ms.conns[typ] | ||||
| 	connStr := connData.Raw() | ||||
| 	dbTyp := connData.DbType() | ||||
| 	if dbTyp == DBTYPE_POSTGRES { | ||||
| 		// TODO: move to vise
 | ||||
| 		err = ensureSchemaExists(ctx, ms.conn) | ||||
| 		err = ensureSchemaExists(ctx, connData) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		newDb = postgres.NewPgDb().WithSchema(ms.conn.Domain()) | ||||
| 	} else if dbTyp == DBTYPE_GDBM { | ||||
| 		err = ms.ensureDbDir() | ||||
| 		newDb = postgres.NewPgDb().WithSchema(connData.Domain()) | ||||
| 
 | ||||
| 	} else if dbTyp == DBTYPE_FS { | ||||
| 		err = ms.ensureDbDir(connStr) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		connStr = path.Join(connStr, section) | ||||
| 		newDb = gdbmstorage.NewThreadGdbmDb() | ||||
| 		fsdbInstance := fsdb.NewFsDb() | ||||
| 		if connData.Mode() == DBMODE_BINARY { | ||||
| 			fsdbInstance = fsdbInstance.WithBinary() | ||||
| 		} | ||||
| 		newDb = fsdbInstance | ||||
| 	} else if dbTyp == DBTYPE_MEM { | ||||
| 		logg.WarnCtxf(ctx, "using volatile storage (memdb)") | ||||
| 		newDb = memdb.NewMemDb() | ||||
| 	} else { | ||||
| 		return nil, fmt.Errorf("unsupported connection string: '%s'\n", ms.conn.String()) | ||||
| 		return nil, fmt.Errorf("unsupported connection string: '%s'\n", connData.Raw()) | ||||
| 	} | ||||
| 	logg.DebugCtxf(ctx, "connecting to db", "conn", connStr, "conndata", ms.conn, "typ", typ) | ||||
| 	logg.InfoCtxf(ctx, "connecting to db", "conn", connData, "typ", typ) | ||||
| 	err = newDb.Connect(ctx, connStr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	ms.store[typ] = newDb | ||||
| 
 | ||||
| 	return newDb, nil | ||||
| } | ||||
| 
 | ||||
| // WithGettext triggers use of gettext for translation of templates and menus.
 | ||||
| //
 | ||||
| // The first language in `lns` will be used as default language, to resolve node keys to 
 | ||||
| // The first language in `lns` will be used as default language, to resolve node keys to
 | ||||
| // language strings.
 | ||||
| //
 | ||||
| // If `lns` is an empty array, gettext will not be used.
 | ||||
| @ -99,7 +132,7 @@ func (ms *MenuStorageService) WithGettext(path string, lns []lang.Language) *Men | ||||
| 	} | ||||
| 	rs := resource.NewPoResource(lns[0], path) | ||||
| 
 | ||||
| 	for _, ln := range(lns) { | ||||
| 	for _, ln := range lns { | ||||
| 		rs = rs.WithLanguage(ln) | ||||
| 	} | ||||
| 
 | ||||
| @ -125,38 +158,49 @@ func ensureSchemaExists(ctx context.Context, conn ConnData) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func applySession(ctx context.Context, store db.Db) error { | ||||
| 	sessionId, ok := ctx.Value("SessionId").(string) | ||||
| 	if !ok { | ||||
| 		logg.DebugCtxf(ctx, "missing session to apply", "store", store) | ||||
| 		return nil | ||||
| 		//return fmt.Errorf("missing session to apply to store: %v", store)
 | ||||
| 	} | ||||
| 	store.SetSession(sessionId) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) { | ||||
| 	stateStore, err := ms.GetStateStore(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	err = applySession(ctx, stateStore) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	pr := persist.NewPersister(stateStore) | ||||
| 	logg.TraceCtxf(ctx, "menu storage service", "persist", pr, "store", stateStore) | ||||
| 	return pr, nil | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) GetUserdataDb(ctx context.Context) (db.Db, error) { | ||||
| 	if ms.userDataStore != nil { | ||||
| 		return ms.userDataStore, nil | ||||
| 	} | ||||
| 
 | ||||
| 	userDataStore, err := ms.getOrCreateDb(ctx, ms.userDataStore, "userdata.gdbm", "userdata") | ||||
| 	userStore, err := ms.getOrCreateDb(ctx, "userdata.gdbm", STORETYPE_USER) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ms.userDataStore = userDataStore | ||||
| 	return ms.userDataStore, nil | ||||
| 	err = applySession(ctx, userStore) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return userStore, nil | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) GetResource(ctx context.Context) (resource.Resource, error) { | ||||
| 	ms.resourceStore = fsdb.NewFsDb() | ||||
| 	err := ms.resourceStore.Connect(ctx, ms.resourceDir) | ||||
| 	store, err := ms.getOrCreateDb(ctx, "resource.gdbm", STORETYPE_RESOURCE) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	rfs := resource.NewDbResource(ms.resourceStore) | ||||
| 	rfs := resource.NewDbResource(store) | ||||
| 	if ms.poResource != nil { | ||||
| 		logg.InfoCtxf(ctx, "using poresource for menu and template") | ||||
| 		rfs.WithMenuGetter(ms.poResource.GetMenu) | ||||
| @ -166,33 +210,34 @@ func (ms *MenuStorageService) GetResource(ctx context.Context) (resource.Resourc | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) GetStateStore(ctx context.Context) (db.Db, error) { | ||||
| 	if ms.stateStore != nil { | ||||
| 		return ms.stateStore, nil | ||||
| 	} | ||||
| 
 | ||||
| 	stateStore, err := ms.getOrCreateDb(ctx, ms.stateStore, "state.gdbm", "state") | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ms.stateStore = stateStore | ||||
| 	return ms.stateStore, nil | ||||
| 	return ms.getOrCreateDb(ctx, "state.gdbm", STORETYPE_STATE) | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) ensureDbDir() error { | ||||
| 	err := os.MkdirAll(ms.conn.String(), 0700) | ||||
| func (ms *MenuStorageService) ensureDbDir(path string) error { | ||||
| 	err := os.MkdirAll(path, 0700) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("state dir create exited with error: %v\n", err) | ||||
| 		return fmt.Errorf("store dir create exited with error: %v\n", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (ms *MenuStorageService) Close() error { | ||||
| 	errA := ms.stateStore.Close() | ||||
| 	errB := ms.userDataStore.Close() | ||||
| 	errC := ms.resourceStore.Close() | ||||
| 	if errA != nil || errB != nil || errC != nil { | ||||
| 		return fmt.Errorf("%v %v %v", errA, errB, errC) | ||||
| // TODO: how to handle persister here?
 | ||||
| func (ms *MenuStorageService) Close(ctx context.Context) error { | ||||
| 	var errs []error | ||||
| 	var haveErr bool | ||||
| 	for i := range _STORETYPE_MAX { | ||||
| 		err := ms.store[int8(i)].Close(ctx) | ||||
| 		if err != nil { | ||||
| 			haveErr = true | ||||
| 		} | ||||
| 		errs = append(errs, err) | ||||
| 	} | ||||
| 	if haveErr { | ||||
| 		errStr := "" | ||||
| 		for i, err := range errs { | ||||
| 			errStr += fmt.Sprintf("(%d: %v)", i, err) | ||||
| 		} | ||||
| 		return errors.New(errStr) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										114
									
								
								storage/storage_service_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								storage/storage_service_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| package storage | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	fsdb "github.com/grassrootseconomics/go-vise/db/fs" | ||||
| ) | ||||
| 
 | ||||
| func TestMenuStorageServiceOneSet(t *testing.T) { | ||||
| 	d, err := os.MkdirTemp("", "visedriver-menustorageservice") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(d) | ||||
| 	conns := NewConns() | ||||
| 	connData, err := ToConnData(d) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	conns.Set(connData, STORETYPE_STATE) | ||||
| 
 | ||||
| 	ctx := context.Background() | ||||
| 	ms := NewMenuStorageService(conns) | ||||
| 	_, err = ms.GetStateStore(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ms.GetResource(ctx) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error getting resource") | ||||
| 	} | ||||
| 	_, err = ms.GetUserdataDb(ctx) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error getting userdata") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestMenuStorageServiceExplicit(t *testing.T) { | ||||
| 	d, err := os.MkdirTemp("", "visedriver-menustorageservice") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(d) | ||||
| 	conns := NewConns() | ||||
| 	connData, err := ToConnData(d) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	conns.Set(connData, STORETYPE_STATE) | ||||
| 
 | ||||
| 	ctx := context.Background() | ||||
| 	d, err = os.MkdirTemp("", "visedriver-menustorageservice") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(d) | ||||
| 	store := fsdb.NewFsDb() | ||||
| 	err = store.Connect(ctx, d) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	ms := NewMenuStorageService(conns) | ||||
| 	ms = ms.WithDb(store, STORETYPE_RESOURCE) | ||||
| 	_, err = ms.GetStateStore(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ms.GetResource(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ms.GetUserdataDb(ctx) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error getting userdata") | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestMenuStorageServiceReuse(t *testing.T) { | ||||
| 	d, err := os.MkdirTemp("", "visedriver-menustorageservice") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(d) | ||||
| 	conns := NewConns() | ||||
| 	connData, err := ToConnData(d) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	conns.Set(connData, STORETYPE_STATE) | ||||
| 	conns.Set(connData, STORETYPE_USER) | ||||
| 
 | ||||
| 	ctx := context.Background() | ||||
| 	ctx = context.WithValue(ctx, "SessionId", "foo") | ||||
| 	ms := NewMenuStorageService(conns) | ||||
| 	stateStore, err := ms.GetStateStore(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	_, err = ms.GetResource(ctx) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error getting resource") | ||||
| 	} | ||||
| 	userStore, err := ms.GetUserdataDb(ctx) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if userStore != stateStore { | ||||
| 		t.Fatalf("expected same store, but they are %p and %p", userStore, stateStore) | ||||
| 	} | ||||
| } | ||||
| @ -10,7 +10,7 @@ type MockEngine struct { | ||||
| 	InitFunc   func(context.Context) (bool, error) | ||||
| 	ExecFunc   func(context.Context, []byte) (bool, error) | ||||
| 	FlushFunc  func(context.Context, io.Writer) (int, error) | ||||
| 	FinishFunc func() error | ||||
| 	FinishFunc func(context.Context) error | ||||
| } | ||||
| 
 | ||||
| func (m *MockEngine) Init(ctx context.Context) (bool, error) { | ||||
| @ -25,6 +25,6 @@ func (m *MockEngine) Flush(ctx context.Context, w io.Writer) (int, error) { | ||||
| 	return m.FlushFunc(ctx, w) | ||||
| } | ||||
| 
 | ||||
| func (m *MockEngine) Finish() error { | ||||
| 	return m.FinishFunc() | ||||
| func (m *MockEngine) Finish(ctx context.Context) error { | ||||
| 	return m.FinishFunc(ctx) | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| package httpmocks | ||||
| 
 | ||||
| import ( | ||||
| 	"git.defalsify.org/vise.git/engine" | ||||
| 	"git.defalsify.org/vise.git/persist" | ||||
| 	"git.defalsify.org/vise.git/resource" | ||||
| 	"context" | ||||
| 
 | ||||
| 	"git.grassecon.net/grassrootseconomics/visedriver/request" | ||||
| 	"github.com/grassrootseconomics/go-vise/engine" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| ) | ||||
| 
 | ||||
| // MockRequestHandler implements request.RequestHandler interface for testing
 | ||||
| @ -13,8 +15,8 @@ type MockRequestHandler struct { | ||||
| 	GetConfigFunc        func() engine.Config | ||||
| 	GetEngineFunc        func(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine | ||||
| 	OutputFunc           func(rs request.RequestSession) (request.RequestSession, error) | ||||
| 	ResetFunc            func(rs request.RequestSession) (request.RequestSession, error) | ||||
| 	ShutdownFunc         func() | ||||
| 	ResetFunc            func(ctx context.Context, rs request.RequestSession) (request.RequestSession, error) | ||||
| 	ShutdownFunc         func(ctx context.Context) | ||||
| 	GetRequestParserFunc func() request.RequestParser | ||||
| } | ||||
| 
 | ||||
| @ -34,12 +36,12 @@ func (m *MockRequestHandler) Output(rs request.RequestSession) (request.RequestS | ||||
| 	return m.OutputFunc(rs) | ||||
| } | ||||
| 
 | ||||
| func (m *MockRequestHandler) Reset(rs request.RequestSession) (request.RequestSession, error) { | ||||
| 	return m.ResetFunc(rs) | ||||
| func (m *MockRequestHandler) Reset(ctx context.Context, rs request.RequestSession) (request.RequestSession, error) { | ||||
| 	return m.ResetFunc(ctx, rs) | ||||
| } | ||||
| 
 | ||||
| func (m *MockRequestHandler) Shutdown() { | ||||
| 	m.ShutdownFunc() | ||||
| func (m *MockRequestHandler) Shutdown(ctx context.Context) { | ||||
| 	m.ShutdownFunc(ctx) | ||||
| } | ||||
| 
 | ||||
| func (m *MockRequestHandler) GetRequestParser() request.RequestParser { | ||||
|  | ||||
| @ -22,4 +22,4 @@ func (m *MockWriter) Header() http.Header { | ||||
| 	return http.Header{} | ||||
| } | ||||
| 
 | ||||
| func (m *MockWriter) WriteHeader(statusCode int) {} | ||||
| func (m *MockWriter) WriteHeader(statusCode int) {} | ||||
|  | ||||
							
								
								
									
										41
									
								
								testutil/mocks/storage.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								testutil/mocks/storage.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| package mocks | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 
 | ||||
| 	"github.com/grassrootseconomics/go-vise/db" | ||||
| 	memdb "github.com/grassrootseconomics/go-vise/db/mem" | ||||
| 	"github.com/grassrootseconomics/go-vise/persist" | ||||
| 	"github.com/grassrootseconomics/go-vise/resource" | ||||
| ) | ||||
| 
 | ||||
| type MemStorageService struct { | ||||
| 	Db db.Db | ||||
| 	pe *persist.Persister | ||||
| 	rs resource.Resource | ||||
| } | ||||
| 
 | ||||
| func NewMemStorageService(ctx context.Context) *MemStorageService { | ||||
| 	svc := &MemStorageService{ | ||||
| 		Db: memdb.NewMemDb(), | ||||
| 	} | ||||
| 	err := svc.Db.Connect(ctx, "") | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	svc.pe = persist.NewPersister(svc.Db) | ||||
| 	svc.rs = resource.NewMenuResource() | ||||
| 	return svc | ||||
| } | ||||
| 
 | ||||
| func (mss *MemStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) { | ||||
| 	return mss.pe, nil | ||||
| } | ||||
| 
 | ||||
| func (mss *MemStorageService) GetUserdataDb(ctx context.Context) (db.Db, error) { | ||||
| 	return mss.Db, nil | ||||
| } | ||||
| 
 | ||||
| func (mss *MemStorageService) GetResource(ctx context.Context) (resource.Resource, error) { | ||||
| 	return mss.rs, nil | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user