forked from urdt/ussd
Compare commits
113 Commits
lash/persi
...
master
Author | SHA1 | Date | |
---|---|---|---|
235af3519d | |||
1292851226 | |||
97fcdda12f | |||
055c2db790 | |||
ecfdab47a8 | |||
fda68231ea | |||
d08afff443 | |||
17ba6a06ba | |||
dbd59a4023 | |||
9b33117cb1 | |||
70b2fa4ac2 | |||
fd6ff86579 | |||
549782f230 | |||
8cf4848b45 | |||
9f6c0a1111 | |||
|
1ab49647f6 | ||
|
8d4d8a48e0 | ||
7aea2af9a1 | |||
5cd791aae7 | |||
df5e5f1a4b | |||
64c1fe5276 | |||
f38ea59569 | |||
6cc285d1e8 | |||
0d7f7aaca1 | |||
|
c820e89cb7 | ||
e05f8e7291 | |||
2383e8ead3 | |||
1a4ee0d3e1 | |||
6f3b30e2fe | |||
b1e4b63c6a | |||
3129e8210e | |||
|
604c16ec90 | ||
|
a3e821fb16 | ||
|
890f50704f | ||
|
3416fdf50c | ||
43892f0d8c | |||
8e6b1e6f52 | |||
|
ff943a125c | ||
9cbbdff993 | |||
316358765d | |||
14737b5f12 | |||
caff27b43d | |||
589a94216b | |||
a659fb06fa | |||
f733fe5636 | |||
18423fcd9c | |||
72a3681767 | |||
160ccbb220 | |||
22f96363ba | |||
3e7f90733e | |||
321f038c7c | |||
7a9de79aae | |||
862830e9de | |||
bc0e536d3d | |||
82884a75a3 | |||
93c44861e0 | |||
4ecfc9de38 | |||
a84c3e0852 | |||
8efed966a0 | |||
e7c4b5bca7 | |||
ed632248c5 | |||
5c202741d6 | |||
c5ebdbf85b | |||
c4282a870e | |||
91cd6077ce | |||
c0ed6fa9c8 | |||
22c9c3e0f0 | |||
a709d24520 | |||
e56138e416 | |||
b420a9bba0 | |||
a20ab79355 | |||
e0ec15b272 | |||
9e998f9a29 | |||
1c7c0af712 | |||
d40a4a171f | |||
ba430a5849 | |||
0f21b01813 | |||
10586baf0d | |||
e979742424 | |||
ff3f049226 | |||
13b45c49da | |||
a72fb08dc8 | |||
944fa89b3c | |||
48e1b02e0e | |||
3e0bbe5ffe | |||
fd586d09c3 | |||
|
c2a4efde2b | ||
|
c9deca1180 | ||
419cd185fc | |||
0091fbcabb | |||
|
6d4f3109f8 | ||
|
35cf3a1cd1 | ||
|
1a782c1db9 | ||
|
3d2ca606ca | ||
aab6660edd | |||
c46f41e25f | |||
00c0445eed | |||
c8c6b05b8a | |||
a17cf78d29 | |||
9847433e0a | |||
7ce50398d1 | |||
e30bc177e9 | |||
b9ff467c0c | |||
07df450b3c | |||
8925e26c4c | |||
9b89462797 | |||
7880294c6f | |||
451b15fb6b | |||
d20700ca74 | |||
9cf1cbe425 | |||
584d02db29 | |||
5937c6bf5c | |||
|
10b3083647 |
@ -7,7 +7,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@ -30,10 +29,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
|
||||
build = "dev"
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
build = "dev"
|
||||
menuSeparator = ": "
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -45,14 +44,14 @@ type atRequestParser struct{}
|
||||
func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
|
||||
rqv, ok := rq.(*http.Request)
|
||||
if !ok {
|
||||
log.Printf("got an invalid request:", rq)
|
||||
logg.Warnf("got an invalid request", "req", rq)
|
||||
return "", handlers.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Capture body (if any) for logging
|
||||
body, err := io.ReadAll(rqv.Body)
|
||||
if err != nil {
|
||||
log.Printf("failed to read request body:", err)
|
||||
logg.Warnf("failed to read request body", "err", err)
|
||||
return "", fmt.Errorf("failed to read request body: %v", err)
|
||||
}
|
||||
// Reset the body for further reading
|
||||
@ -62,13 +61,13 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
|
||||
bodyLog := map[string]string{"body": string(body)}
|
||||
logBytes, err := json.Marshal(bodyLog)
|
||||
if err != nil {
|
||||
log.Printf("failed to marshal request body:", err)
|
||||
logg.Warnf("failed to marshal request body", "err", err)
|
||||
} else {
|
||||
log.Printf("Received request:", string(logBytes))
|
||||
logg.Debugf("received request", "bytes", logBytes)
|
||||
}
|
||||
|
||||
if err := rqv.ParseForm(); err != nil {
|
||||
log.Printf("failed to parse form data: %v", err)
|
||||
logg.Warnf("failed to parse form data", "err", err)
|
||||
return "", fmt.Errorf("failed to parse form data: %v", err)
|
||||
}
|
||||
|
||||
@ -79,7 +78,7 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
|
||||
|
||||
formattedNumber, err := common.FormatPhoneNumber(phoneNumber)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
logg.Warnf("failed to format phone number", "err", err)
|
||||
return "", fmt.Errorf("failed to format number")
|
||||
}
|
||||
|
||||
@ -131,9 +130,10 @@ func main() {
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
cfg := engine.Config{
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
menuSeparator = ": "
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -70,9 +71,10 @@ func main() {
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
cfg := engine.Config{
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
menuSeparator = ": "
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -58,9 +59,10 @@ func main() {
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
cfg := engine.Config{
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
Root: "root",
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
}
|
||||
|
||||
if engineDebug {
|
||||
|
14
cmd/main.go
14
cmd/main.go
@ -18,8 +18,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
menuSeparator = ": "
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -49,10 +50,11 @@ func main() {
|
||||
pfp := path.Join(scriptDir, "pp.csv")
|
||||
|
||||
cfg := engine.Config{
|
||||
Root: "root",
|
||||
SessionId: sessionId,
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
Root: "root",
|
||||
SessionId: sessionId,
|
||||
OutputSize: uint32(size),
|
||||
FlagCount: uint32(128),
|
||||
MenuSeparator: menuSeparator,
|
||||
}
|
||||
|
||||
resourceDir := scriptDir
|
||||
|
71
common/db.go
71
common/db.go
@ -7,32 +7,84 @@ import (
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
)
|
||||
|
||||
// DataType is a subprefix value used in association with vise/db.DATATYPE_USERDATA.
|
||||
//
|
||||
// All keys are used only within the context of a single account. Unless otherwise specified, the user context is the session id.
|
||||
//
|
||||
// * The first byte is vise/db.DATATYPE_USERDATA
|
||||
// * The last 2 bytes are the DataTyp value, big-endian.
|
||||
// * The intermediate bytes are the id of the user context.
|
||||
//
|
||||
// All values are strings
|
||||
type DataTyp uint16
|
||||
|
||||
const (
|
||||
DATA_ACCOUNT DataTyp = iota
|
||||
DATA_ACCOUNT_CREATED
|
||||
DATA_TRACKING_ID
|
||||
// API Tracking id to follow status of account creation
|
||||
DATA_TRACKING_ID = iota
|
||||
// EVM address returned from API on account creation
|
||||
DATA_PUBLIC_KEY
|
||||
DATA_CUSTODIAL_ID
|
||||
// Currently active PIN used to authenticate ussd state change requests
|
||||
DATA_ACCOUNT_PIN
|
||||
DATA_ACCOUNT_STATUS
|
||||
// The first name of the user
|
||||
DATA_FIRST_NAME
|
||||
// The last name of the user
|
||||
DATA_FAMILY_NAME
|
||||
// The year-of-birth of the user
|
||||
DATA_YOB
|
||||
// The location of the user
|
||||
DATA_LOCATION
|
||||
// The gender of the user
|
||||
DATA_GENDER
|
||||
// The offerings description of the user
|
||||
DATA_OFFERINGS
|
||||
// The ethereum address of the recipient of an ongoing send request
|
||||
DATA_RECIPIENT
|
||||
// The voucher value amount of an ongoing send request
|
||||
DATA_AMOUNT
|
||||
// A general swap field for temporary values
|
||||
DATA_TEMPORARY_VALUE
|
||||
// Currently active voucher symbol of user
|
||||
DATA_ACTIVE_SYM
|
||||
// Voucher balance of user's currently active voucher
|
||||
DATA_ACTIVE_BAL
|
||||
// String boolean indicating whether use of PIN is blocked
|
||||
DATA_BLOCKED_NUMBER
|
||||
// Reverse mapping of a user's evm address to a session id.
|
||||
DATA_PUBLIC_KEY_REVERSE
|
||||
// Decimal count of the currently active voucher
|
||||
DATA_ACTIVE_DECIMAL
|
||||
// EVM address of the currently active voucher
|
||||
DATA_ACTIVE_ADDRESS
|
||||
DATA_TRANSACTIONS
|
||||
)
|
||||
|
||||
const (
|
||||
// List of valid voucher symbols in the user context.
|
||||
DATA_VOUCHER_SYMBOLS DataTyp = 256 + iota
|
||||
// List of voucher balances for vouchers valid in the user context.
|
||||
DATA_VOUCHER_BALANCES
|
||||
// List of voucher decimal counts for vouchers valid in the user context.
|
||||
DATA_VOUCHER_DECIMALS
|
||||
// List of voucher EVM addresses for vouchers valid in the user context.
|
||||
DATA_VOUCHER_ADDRESSES
|
||||
// List of senders for valid transactions in the user context.
|
||||
)
|
||||
|
||||
const (
|
||||
DATA_TX_SENDERS = 512 + iota
|
||||
// List of recipients for valid transactions in the user context.
|
||||
DATA_TX_RECIPIENTS
|
||||
// List of voucher values for valid transactions in the user context.
|
||||
DATA_TX_VALUES
|
||||
// List of voucher EVM addresses for valid transactions in the user context.
|
||||
DATA_TX_ADDRESSES
|
||||
// List of valid transaction hashes in the user context.
|
||||
DATA_TX_HASHES
|
||||
// List of transaction dates for valid transactions in the user context.
|
||||
DATA_TX_DATES
|
||||
// List of voucher symbols for valid transactions in the user context.
|
||||
DATA_TX_SYMBOLS
|
||||
// List of voucher decimal counts for valid transactions in the user context.
|
||||
DATA_TX_DECIMALS
|
||||
)
|
||||
|
||||
var (
|
||||
@ -69,3 +121,10 @@ func StringToDataTyp(str string) (DataTyp, error) {
|
||||
return 0, errors.New("invalid DataTyp string")
|
||||
}
|
||||
}
|
||||
|
||||
// ToBytes converts DataTyp or int to a byte slice
|
||||
func ToBytes[T ~uint16 | int](value T) []byte {
|
||||
bytes := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(bytes, uint16(value))
|
||||
return bytes
|
||||
}
|
||||
|
@ -57,25 +57,25 @@ func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetad
|
||||
// GetTransferData retrieves and matches transfer data
|
||||
// returns a formatted string of the full transaction/statement
|
||||
func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) {
|
||||
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"}
|
||||
data := make(map[string]string)
|
||||
keys := []DataTyp{DATA_TX_SENDERS, DATA_TX_RECIPIENTS, DATA_TX_VALUES, DATA_TX_ADDRESSES, DATA_TX_HASHES, DATA_TX_DATES, DATA_TX_SYMBOLS}
|
||||
data := make(map[DataTyp]string)
|
||||
|
||||
for _, key := range keys {
|
||||
value, err := db.Get(ctx, []byte(key))
|
||||
value, err := db.Get(ctx, ToBytes(key))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get %s: %v", key, err)
|
||||
return "", fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
|
||||
}
|
||||
data[key] = string(value)
|
||||
}
|
||||
|
||||
// Split the data
|
||||
senders := strings.Split(string(data["txfrom"]), "\n")
|
||||
recipients := strings.Split(string(data["txto"]), "\n")
|
||||
values := strings.Split(string(data["txval"]), "\n")
|
||||
addresses := strings.Split(string(data["txaddr"]), "\n")
|
||||
hashes := strings.Split(string(data["txhash"]), "\n")
|
||||
dates := strings.Split(string(data["txdate"]), "\n")
|
||||
syms := strings.Split(string(data["txsym"]), "\n")
|
||||
senders := strings.Split(string(data[DATA_TX_SENDERS]), "\n")
|
||||
recipients := strings.Split(string(data[DATA_TX_RECIPIENTS]), "\n")
|
||||
values := strings.Split(string(data[DATA_TX_VALUES]), "\n")
|
||||
addresses := strings.Split(string(data[DATA_TX_ADDRESSES]), "\n")
|
||||
hashes := strings.Split(string(data[DATA_TX_HASHES]), "\n")
|
||||
dates := strings.Split(string(data[DATA_TX_DATES]), "\n")
|
||||
syms := strings.Split(string(data[DATA_TX_SYMBOLS]), "\n")
|
||||
|
||||
// Check if index is within range
|
||||
if index < 1 || index > len(senders) {
|
||||
@ -84,18 +84,18 @@ func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string,
|
||||
|
||||
// Adjust for 0-based indexing
|
||||
i := index - 1
|
||||
transactionType := "received"
|
||||
party := fmt.Sprintf("from: %s", strings.TrimSpace(senders[i]))
|
||||
transactionType := "Received"
|
||||
party := fmt.Sprintf("From: %s", strings.TrimSpace(senders[i]))
|
||||
if strings.TrimSpace(senders[i]) == publicKey {
|
||||
transactionType = "sent"
|
||||
party = fmt.Sprintf("to: %s", strings.TrimSpace(recipients[i]))
|
||||
transactionType = "Sent"
|
||||
party = fmt.Sprintf("To: %s", strings.TrimSpace(recipients[i]))
|
||||
}
|
||||
|
||||
formattedDate := formatDate(strings.TrimSpace(dates[i]))
|
||||
|
||||
// Build the full transaction detail
|
||||
detail := fmt.Sprintf(
|
||||
"%s %s %s\n%s\ncontract address: %s\ntxhash: %s\ndate: %s",
|
||||
"%s %s %s\n%s\nContract address: %s\nTxhash: %s\nDate: %s",
|
||||
transactionType,
|
||||
strings.TrimSpace(values[i]),
|
||||
strings.TrimSpace(syms[i]),
|
||||
|
@ -20,7 +20,7 @@ type UserDataStore struct {
|
||||
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
|
||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
||||
store.SetSession(sessionId)
|
||||
k := PackKey(typ, []byte(sessionId))
|
||||
k := ToBytes(typ)
|
||||
return store.Get(ctx, k)
|
||||
}
|
||||
|
||||
@ -29,6 +29,6 @@ func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ
|
||||
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
|
||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
||||
store.SetSession(sessionId)
|
||||
k := PackKey(typ, []byte(sessionId))
|
||||
k := ToBytes(typ)
|
||||
return store.Put(ctx, k, value)
|
||||
}
|
||||
|
@ -64,22 +64,23 @@ func ScaleDownBalance(balance, decimals string) string {
|
||||
|
||||
// GetVoucherData retrieves and matches voucher data
|
||||
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
|
||||
keys := []string{"sym", "bal", "deci", "addr"}
|
||||
data := make(map[string]string)
|
||||
keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES}
|
||||
data := make(map[DataTyp]string)
|
||||
|
||||
for _, key := range keys {
|
||||
value, err := db.Get(ctx, []byte(key))
|
||||
value, err := db.Get(ctx, ToBytes(key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get %s: %v", key, err)
|
||||
return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
|
||||
}
|
||||
data[key] = string(value)
|
||||
}
|
||||
|
||||
symbol, balance, decimal, address := MatchVoucher(input,
|
||||
data["sym"],
|
||||
data["bal"],
|
||||
data["deci"],
|
||||
data["addr"])
|
||||
data[DATA_VOUCHER_SYMBOLS],
|
||||
data[DATA_VOUCHER_BALANCES],
|
||||
data[DATA_VOUCHER_DECIMALS],
|
||||
data[DATA_VOUCHER_ADDRESSES],
|
||||
)
|
||||
|
||||
if symbol == "" {
|
||||
return nil, nil
|
||||
|
@ -8,8 +8,9 @@ import (
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
memdb "git.defalsify.org/vise.git/db/mem"
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
)
|
||||
|
||||
@ -83,19 +84,21 @@ func TestGetVoucherData(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
|
||||
|
||||
prefix := ToBytes(visedb.DATATYPE_USERDATA)
|
||||
spdb := storage.NewSubPrefixDb(db, prefix)
|
||||
|
||||
// Test voucher data
|
||||
mockData := map[string][]byte{
|
||||
"sym": []byte("1:SRF\n2:MILO"),
|
||||
"bal": []byte("1:100\n2:200"),
|
||||
"deci": []byte("1:6\n2:4"),
|
||||
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||
mockData := map[DataTyp][]byte{
|
||||
DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
|
||||
DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
|
||||
DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
|
||||
DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||
}
|
||||
|
||||
// Put the data
|
||||
for key, value := range mockData {
|
||||
err = spdb.Put(ctx, []byte(key), []byte(value))
|
||||
err = spdb.Put(ctx, []byte(ToBytes(key)), []byte(value))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
5
debug/cap.go
Normal file
5
debug/cap.go
Normal file
@ -0,0 +1,5 @@
|
||||
package debug
|
||||
|
||||
var (
|
||||
DebugCap uint32
|
||||
)
|
84
debug/db.go
Normal file
84
debug/db.go
Normal file
@ -0,0 +1,84 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/binary"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
"git.defalsify.org/vise.git/db"
|
||||
)
|
||||
|
||||
var (
|
||||
dbTypStr map[common.DataTyp]string = make(map[common.DataTyp]string)
|
||||
)
|
||||
|
||||
type KeyInfo struct {
|
||||
SessionId string
|
||||
Typ uint8
|
||||
SubTyp common.DataTyp
|
||||
Label string
|
||||
Description string
|
||||
}
|
||||
|
||||
func (k KeyInfo) String() string {
|
||||
v := uint16(k.SubTyp)
|
||||
s := subTypToString(k.SubTyp)
|
||||
if s == "" {
|
||||
v = uint16(k.Typ)
|
||||
s = typToString(k.Typ)
|
||||
}
|
||||
return fmt.Sprintf("Session Id: %s\nTyp: %s (%d)\n", k.SessionId, s, v)
|
||||
}
|
||||
|
||||
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
||||
o := KeyInfo{}
|
||||
b := []byte(sessionId)
|
||||
|
||||
if len(k) <= len(b) {
|
||||
return o, fmt.Errorf("storage key missing")
|
||||
}
|
||||
|
||||
o.SessionId = sessionId
|
||||
|
||||
o.Typ = uint8(k[0])
|
||||
k = k[1:]
|
||||
o.SessionId = string(k[:len(b)])
|
||||
k = k[len(b):]
|
||||
|
||||
if o.Typ == db.DATATYPE_USERDATA {
|
||||
if len(k) == 0 {
|
||||
return o, fmt.Errorf("missing subtype key")
|
||||
}
|
||||
v := binary.BigEndian.Uint16(k[:2])
|
||||
o.SubTyp = common.DataTyp(v)
|
||||
o.Label = subTypToString(o.SubTyp)
|
||||
k = k[2:]
|
||||
} else {
|
||||
o.Label = typToString(o.Typ)
|
||||
}
|
||||
|
||||
if len(k) != 0 {
|
||||
return o, fmt.Errorf("excess key information")
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func FromKey(k []byte) (KeyInfo, error) {
|
||||
o := KeyInfo{}
|
||||
|
||||
if len(k) < 4 {
|
||||
return o, fmt.Errorf("insufficient key length")
|
||||
}
|
||||
|
||||
sessionIdBytes := k[1:len(k)-2]
|
||||
return ToKeyInfo(k, string(sessionIdBytes))
|
||||
}
|
||||
|
||||
func subTypToString(v common.DataTyp) string {
|
||||
return dbTypStr[v + db.DATATYPE_USERDATA + 1]
|
||||
}
|
||||
|
||||
func typToString(v uint8) string {
|
||||
return dbTypStr[common.DataTyp(uint16(v))]
|
||||
}
|
48
debug/db_debug.go
Normal file
48
debug/db_debug.go
Normal file
@ -0,0 +1,48 @@
|
||||
// +build debugdb
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"git.defalsify.org/vise.git/db"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
)
|
||||
|
||||
func init() {
|
||||
DebugCap |= 1
|
||||
dbTypStr[db.DATATYPE_STATE] = "internal state"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT] = "account"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_CREATED] = "account created"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TRACKING_ID] = "tracking id"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY] = "public key"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_CUSTODIAL_ID] = "custodial id"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_PIN] = "account pin"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_STATUS] = "account status"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FIRST_NAME] = "first name"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FAMILY_NAME] = "family name"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_YOB] = "year of birth"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_LOCATION] = "location"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_GENDER] = "gender"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_OFFERINGS] = "offerings"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_RECIPIENT] = "recipient"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_AMOUNT] = "amount"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TEMPORARY_VALUE] = "temporary value"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_SYM] = "active sym"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_BAL] = "active bal"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_BLOCKED_NUMBER] = "blocked number"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_DECIMAL] = "active decimal"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_ADDRESS] = "active address"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_BALANCES] = "voucher balances"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SENDERS] = "tx senders"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_RECIPIENTS] = "tx recipients"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_VALUES] = "tx values"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_ADDRESSES] = "tx addresses"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_HASHES] = "tx hashes"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DATES] = "tx dates"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SYMBOLS] = "tx symbols"
|
||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DECIMALS] = "tx decimals"
|
||||
}
|
78
debug/db_test.go
Normal file
78
debug/db_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
"git.defalsify.org/vise.git/db"
|
||||
)
|
||||
|
||||
func TestDebugDbSubKeyInfo(t *testing.T) {
|
||||
s := "foo"
|
||||
b := []byte{0x20}
|
||||
b = append(b, []byte(s)...)
|
||||
b = append(b, []byte{0x00, 0x02}...)
|
||||
r, err := ToKeyInfo(b, s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.SessionId != s {
|
||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
||||
}
|
||||
if r.Typ != 32 {
|
||||
t.Fatalf("expected 64, got %d", r.Typ)
|
||||
}
|
||||
if r.SubTyp != 2 {
|
||||
t.Fatalf("expected 2, got %d", r.SubTyp)
|
||||
}
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "tracking id" {
|
||||
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugDbKeyInfo(t *testing.T) {
|
||||
s := "bar"
|
||||
b := []byte{0x10}
|
||||
b = append(b, []byte(s)...)
|
||||
r, err := ToKeyInfo(b, s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.SessionId != s {
|
||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
||||
}
|
||||
if r.Typ != 16 {
|
||||
t.Fatalf("expected 16, got %d", r.Typ)
|
||||
}
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "internal state" {
|
||||
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugDbKeyInfoRestore(t *testing.T) {
|
||||
s := "bar"
|
||||
b := []byte{db.DATATYPE_USERDATA}
|
||||
b = append(b, []byte(s)...)
|
||||
k := common.ToBytes(common.DATA_ACTIVE_SYM)
|
||||
b = append(b, k...)
|
||||
|
||||
r, err := ToKeyInfo(b, s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.SessionId != s {
|
||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
||||
}
|
||||
if r.Typ != 32 {
|
||||
t.Fatalf("expected 32, got %d", r.Typ)
|
||||
}
|
||||
if DebugCap & 1 > 0 {
|
||||
if r.Label != "active sym" {
|
||||
t.Fatalf("expected 'active sym', got '%s'", r.Label)
|
||||
}
|
||||
}
|
||||
}
|
79
devtools/gen/main.go
Normal file
79
devtools/gen/main.go
Normal file
@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
"git.grassecon.net/urdt/ussd/config"
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
"git.grassecon.net/urdt/ussd/initializers"
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
)
|
||||
|
||||
func init() {
|
||||
initializers.LoadEnvVariables()
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
var dbDir string
|
||||
var sessionId string
|
||||
var database string
|
||||
var engineDebug bool
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&database, "db", "gdbm", "database to be used")
|
||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.Parse()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Database", database)
|
||||
|
||||
resourceDir := scriptDir
|
||||
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
|
||||
|
||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
userStore := common.UserDataStore{store}
|
||||
|
||||
h := sha1.New()
|
||||
h.Write([]byte(sessionId))
|
||||
address := h.Sum(nil)
|
||||
addressString := fmt.Sprintf("%x", address)
|
||||
|
||||
err = userStore.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(addressString))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = userStore.WriteEntry(ctx, addressString, common.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
78
devtools/store/main.go
Normal file
78
devtools/store/main.go
Normal file
@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/config"
|
||||
"git.grassecon.net/urdt/ussd/initializers"
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
"git.grassecon.net/urdt/ussd/debug"
|
||||
"git.defalsify.org/vise.git/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
)
|
||||
|
||||
func init() {
|
||||
initializers.LoadEnvVariables()
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
config.LoadConfig()
|
||||
|
||||
var dbDir string
|
||||
var sessionId string
|
||||
var database string
|
||||
var engineDebug bool
|
||||
|
||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||
flag.StringVar(&database, "db", "gdbm", "database to be used")
|
||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
||||
flag.Parse()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Database", database)
|
||||
|
||||
resourceDir := scriptDir
|
||||
menuStorageService := storage.NewMenuStorageService(dbDir, resourceDir)
|
||||
|
||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
d, err := store.Dump(ctx, []byte(sessionId))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for true {
|
||||
k, v := d.Next(ctx)
|
||||
if k == nil {
|
||||
break
|
||||
}
|
||||
o, err := debug.FromKey(k)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%vValue: %v\n\n", o, v)
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
28
doc/data.md
Normal file
28
doc/data.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Internals
|
||||
|
||||
## Version
|
||||
|
||||
This document describes component versions:
|
||||
|
||||
* `urdt-ussd` `v0.5.0-beta`
|
||||
* `go-vise` `v0.2.2`
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
0x322b3235343132330002
|
||||
|
||||
prefix sessionid subprefix
|
||||
32 2b323534313233 0002
|
||||
```
|
||||
|
||||
### Sub-prefixes
|
||||
|
||||
All sub-prefixes are defined as constants in the `git.grassecon.net/urdt-ussd/common` package. The constant names have the prefix `DATA_`
|
||||
|
||||
Please refer to inline godoc documentation for the `git.grassecon.net/urdt-ussd/common` package for details on each data item.
|
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module git.grassecon.net/urdt/ussd
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80
|
||||
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
|
||||
|
4
go.sum
4
go.sum
@ -1,5 +1,5 @@
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a h1:LvGKktk0kUnuRN3nF9r15D8OoV0sFaMmQr52kGq2gtE=
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241122120231-9e9ee5bdfa7a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80 h1:GYUVXRUtMpA40T4COeAduoay6CIgXjD5cfDYZOTFIKw=
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80/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/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
|
||||
|
@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/asm"
|
||||
"git.defalsify.org/vise.git/db"
|
||||
@ -64,7 +65,11 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
|
||||
}
|
||||
|
||||
func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) {
|
||||
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService)
|
||||
replaceSeparatorFunc := func(input string) string {
|
||||
return strings.ReplaceAll(input, ":", ls.Cfg.MenuSeparator)
|
||||
}
|
||||
|
||||
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService, replaceSeparatorFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -121,6 +126,8 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
|
||||
ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions)
|
||||
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList)
|
||||
ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement)
|
||||
ls.DbRs.AddLocalFunc("update_all_profile_items", ussdHandlers.UpdateAllProfileItems)
|
||||
ls.DbRs.AddLocalFunc("set_back", ussdHandlers.SetBack)
|
||||
|
||||
return ussdHandlers, nil
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"git.defalsify.org/vise.git/state"
|
||||
"git.grassecon.net/urdt/ussd/common"
|
||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||
"git.grassecon.net/urdt/ussd/models"
|
||||
"git.grassecon.net/urdt/ussd/remote"
|
||||
"gopkg.in/leonelquinteros/gotext.v1"
|
||||
|
||||
@ -68,32 +69,38 @@ func (fm *FlagManager) GetFlag(label string) (uint32, error) {
|
||||
}
|
||||
|
||||
type Handlers struct {
|
||||
pe *persist.Persister
|
||||
st *state.State
|
||||
ca cache.Memory
|
||||
userdataStore common.DataStore
|
||||
adminstore *utils.AdminStore
|
||||
flagManager *asm.FlagParser
|
||||
accountService remote.AccountServiceInterface
|
||||
prefixDb storage.PrefixDb
|
||||
pe *persist.Persister
|
||||
st *state.State
|
||||
ca cache.Memory
|
||||
userdataStore common.DataStore
|
||||
adminstore *utils.AdminStore
|
||||
flagManager *asm.FlagParser
|
||||
accountService remote.AccountServiceInterface
|
||||
prefixDb storage.PrefixDb
|
||||
profile *models.Profile
|
||||
ReplaceSeparatorFunc func(string) string
|
||||
}
|
||||
|
||||
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) {
|
||||
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface, replaceSeparatorFunc func(string) string) (*Handlers, error) {
|
||||
if userdataStore == nil {
|
||||
return nil, fmt.Errorf("cannot create handler with nil userdata store")
|
||||
}
|
||||
userDb := &common.UserDataStore{
|
||||
Db: userdataStore,
|
||||
}
|
||||
// Instantiate the SubPrefixDb with "vouchers" prefix
|
||||
prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
|
||||
|
||||
// Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix
|
||||
prefix := common.ToBytes(db.DATATYPE_USERDATA)
|
||||
prefixDb := storage.NewSubPrefixDb(userdataStore, prefix)
|
||||
|
||||
h := &Handlers{
|
||||
userdataStore: userDb,
|
||||
flagManager: appFlags,
|
||||
adminstore: adminstore,
|
||||
accountService: accountService,
|
||||
prefixDb: prefixDb,
|
||||
userdataStore: userDb,
|
||||
flagManager: appFlags,
|
||||
adminstore: adminstore,
|
||||
accountService: accountService,
|
||||
prefixDb: prefixDb,
|
||||
profile: &models.Profile{Max: 6},
|
||||
ReplaceSeparatorFunc: replaceSeparatorFunc,
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
@ -152,7 +159,8 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
|
||||
code := strings.Split(symbol, "_")[1]
|
||||
|
||||
if !utils.IsValidISO639(code) {
|
||||
return res, nil
|
||||
//Fallback to english instead?
|
||||
code = "eng"
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
|
||||
res.Content = code
|
||||
@ -210,7 +218,7 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
_, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
|
||||
_, err = store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
|
||||
@ -410,7 +418,10 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
|
||||
firstName := string(input)
|
||||
store := h.userdataStore
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
|
||||
if allowUpdate {
|
||||
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
|
||||
@ -418,11 +429,16 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
|
||||
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
|
||||
return res, err
|
||||
if firstNameSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(0, firstName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,7 +458,9 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
|
||||
familyName := string(input)
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
@ -451,11 +469,16 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
|
||||
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
|
||||
return res, err
|
||||
if familyNameSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(1, familyName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,7 +496,10 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
|
||||
yob := string(input)
|
||||
store := h.userdataStore
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
yobSet := h.st.MatchFlag(flag_yob_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
@ -482,11 +508,16 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
|
||||
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
|
||||
return res, err
|
||||
if yobSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(3, yob)
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,7 +536,9 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
|
||||
store := h.userdataStore
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
locationSet := h.st.MatchFlag(flag_location_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
@ -514,11 +547,17 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
|
||||
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
|
||||
return res, err
|
||||
if locationSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
} else {
|
||||
h.profile.InsertOrShift(4, location)
|
||||
}
|
||||
}
|
||||
|
||||
@ -537,7 +576,10 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
|
||||
gender := strings.Split(symbol, "_")[1]
|
||||
store := h.userdataStore
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
genderSet := h.st.MatchFlag(flag_gender_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
@ -546,11 +588,16 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
|
||||
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
|
||||
return res, err
|
||||
if genderSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(2, gender)
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,7 +617,10 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
|
||||
store := h.userdataStore
|
||||
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||
|
||||
allowUpdate := h.st.MatchFlag(flag_allow_update, true)
|
||||
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
|
||||
|
||||
if allowUpdate {
|
||||
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
|
||||
@ -579,11 +629,16 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
|
||||
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||
} else {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||
return res, err
|
||||
if offeringsSet {
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
|
||||
return res, err
|
||||
}
|
||||
} else {
|
||||
h.profile.InsertOrShift(5, offerings)
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,6 +731,18 @@ func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []by
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Setback sets the flag_back_set flag when the navigation is back
|
||||
func (h *Handlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
//TODO:
|
||||
//Add check if the navigation is lateral nav instead of checking the input.
|
||||
if string(input) == "0" {
|
||||
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
|
||||
res.FlagSet = append(res.FlagSet, flag_back_set)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CheckAccountStatus queries the API using the TrackingId and sets flags
|
||||
// based on the account status
|
||||
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
@ -761,12 +828,11 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if len(date) == 4 {
|
||||
if utils.IsValidYOb(date) {
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
|
||||
} else {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -815,7 +881,17 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = l.Get("Balance: %s\n", fmt.Sprintf("%s %s", activeBal, activeSym))
|
||||
// Convert activeBal from []byte to float64
|
||||
balFloat, err := strconv.ParseFloat(string(activeBal), 64)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to parse activeBal as float", "value", string(activeBal), "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Format to 2 decimal places
|
||||
balStr := fmt.Sprintf("%.2f %s", balFloat, activeSym)
|
||||
|
||||
res.Content = l.Get("Balance: %s\n", balStr)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@ -1266,6 +1342,17 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
var res resource.Result
|
||||
var profileInfo []byte
|
||||
var err error
|
||||
|
||||
flag_firstname_set, _ := h.flagManager.GetFlag("flag_firstname_set")
|
||||
flag_familyname_set, _ := h.flagManager.GetFlag("flag_familyname_set")
|
||||
flag_yob_set, _ := h.flagManager.GetFlag("flag_yob_set")
|
||||
flag_gender_set, _ := h.flagManager.GetFlag("flag_gender_set")
|
||||
flag_location_set, _ := h.flagManager.GetFlag("flag_location_set")
|
||||
flag_offerings_set, _ := h.flagManager.GetFlag("flag_offerings_set")
|
||||
flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_back_set)
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
@ -1292,6 +1379,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_firstname_set)
|
||||
res.Content = string(profileInfo)
|
||||
case common.DATA_FAMILY_NAME:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
|
||||
@ -1303,6 +1391,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_familyname_set)
|
||||
res.Content = string(profileInfo)
|
||||
|
||||
case common.DATA_GENDER:
|
||||
@ -1315,6 +1404,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_gender_set)
|
||||
res.Content = string(profileInfo)
|
||||
case common.DATA_YOB:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB)
|
||||
@ -1326,8 +1416,8 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_yob_set)
|
||||
res.Content = string(profileInfo)
|
||||
|
||||
case common.DATA_LOCATION:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
|
||||
if err != nil {
|
||||
@ -1338,6 +1428,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_location_set)
|
||||
res.Content = string(profileInfo)
|
||||
case common.DATA_OFFERINGS:
|
||||
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
|
||||
@ -1349,6 +1440,7 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
|
||||
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err)
|
||||
return res, err
|
||||
}
|
||||
res.FlagSet = append(res.FlagSet, flag_offerings_set)
|
||||
res.Content = string(profileInfo)
|
||||
default:
|
||||
break
|
||||
@ -1392,14 +1484,7 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
|
||||
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
|
||||
|
||||
// Construct the full name
|
||||
name := defaultValue
|
||||
if familyName != defaultValue {
|
||||
if firstName == defaultValue {
|
||||
name = familyName
|
||||
} else {
|
||||
name = firstName + " " + familyName
|
||||
}
|
||||
}
|
||||
name := utils.ConstructName(firstName, familyName, defaultValue)
|
||||
|
||||
// Calculate age from year of birth
|
||||
age := defaultValue
|
||||
@ -1573,15 +1658,15 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
|
||||
data := common.ProcessVouchers(vouchersResp)
|
||||
|
||||
// Store all voucher data
|
||||
dataMap := map[string]string{
|
||||
"sym": data.Symbols,
|
||||
"bal": data.Balances,
|
||||
"deci": data.Decimals,
|
||||
"addr": data.Addresses,
|
||||
dataMap := map[common.DataTyp]string{
|
||||
common.DATA_VOUCHER_SYMBOLS: data.Symbols,
|
||||
common.DATA_VOUCHER_BALANCES: data.Balances,
|
||||
common.DATA_VOUCHER_DECIMALS: data.Decimals,
|
||||
common.DATA_VOUCHER_ADDRESSES: data.Addresses,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||
if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
@ -1594,13 +1679,15 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
|
||||
var res resource.Result
|
||||
|
||||
// Read vouchers from the store
|
||||
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
|
||||
voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Content = string(voucherData)
|
||||
formattedData := h.ReplaceSeparatorFunc(string(voucherData))
|
||||
|
||||
res.Content = string(formattedData)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@ -1614,6 +1701,10 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
|
||||
|
||||
inputStr := string(input)
|
||||
@ -1638,7 +1729,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
|
||||
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
|
||||
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@ -1736,19 +1827,19 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by
|
||||
data := common.ProcessTransfers(transactionsResp)
|
||||
|
||||
// Store all transaction data
|
||||
dataMap := map[string]string{
|
||||
"txfrom": data.Senders,
|
||||
"txto": data.Recipients,
|
||||
"txval": data.TransferValues,
|
||||
"txaddr": data.Addresses,
|
||||
"txhash": data.TxHashes,
|
||||
"txdate": data.Dates,
|
||||
"txsym": data.Symbols,
|
||||
"txdeci": data.Decimals,
|
||||
dataMap := map[common.DataTyp]string{
|
||||
common.DATA_TX_SENDERS: data.Senders,
|
||||
common.DATA_TX_RECIPIENTS: data.Recipients,
|
||||
common.DATA_TX_VALUES: data.TransferValues,
|
||||
common.DATA_TX_ADDRESSES: data.Addresses,
|
||||
common.DATA_TX_HASHES: data.TxHashes,
|
||||
common.DATA_TX_DATES: data.Dates,
|
||||
common.DATA_TX_SYMBOLS: data.Symbols,
|
||||
common.DATA_TX_DECIMALS: data.Decimals,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||
if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
@ -1759,13 +1850,14 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetTransactionsList fetches the list of transactions and formats them
|
||||
// GetTransactionsList reads the list of transactions from the db and formats them
|
||||
func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
@ -1774,22 +1866,22 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []
|
||||
}
|
||||
|
||||
// Read transactions from the store and format them
|
||||
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom"))
|
||||
TransactionSenders, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SENDERS))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionSyms, err := h.prefixDb.Get(ctx, []byte("txsym"))
|
||||
TransactionSyms, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_SYMBOLS))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionValues, err := h.prefixDb.Get(ctx, []byte("txval"))
|
||||
TransactionValues, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_VALUES))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionDates, err := h.prefixDb.Get(ctx, []byte("txdate"))
|
||||
TransactionDates, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_TX_DATES))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
|
||||
return res, err
|
||||
@ -1808,12 +1900,14 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []
|
||||
value := strings.TrimSpace(values[i])
|
||||
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
|
||||
|
||||
status := "received"
|
||||
status := "Received"
|
||||
if sender == string(publicKey) {
|
||||
status = "sent"
|
||||
status = "Sent"
|
||||
}
|
||||
|
||||
formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date))
|
||||
// Use the ReplaceSeparator function for the menu separator
|
||||
transactionLine := fmt.Sprintf("%d%s%s %s %s %s", i+1, h.ReplaceSeparatorFunc(":"), status, value, sym, date)
|
||||
formattedTransactions = append(formattedTransactions, transactionLine)
|
||||
}
|
||||
|
||||
res.Content = strings.Join(formattedTransactions, "\n")
|
||||
@ -1869,3 +1963,53 @@ func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, inp
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *Handlers) insertProfileItems(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||
var err error
|
||||
store := h.userdataStore
|
||||
profileFlagNames := []string{
|
||||
"flag_firstname_set",
|
||||
"flag_familyname_set",
|
||||
"flag_yob_set",
|
||||
"flag_gender_set",
|
||||
"flag_location_set",
|
||||
"flag_offerings_set",
|
||||
}
|
||||
profileDataKeys := []common.DataTyp{
|
||||
common.DATA_FIRST_NAME,
|
||||
common.DATA_FAMILY_NAME,
|
||||
common.DATA_GENDER,
|
||||
common.DATA_YOB,
|
||||
common.DATA_LOCATION,
|
||||
common.DATA_OFFERINGS,
|
||||
}
|
||||
for index, profileItem := range h.profile.ProfileItems {
|
||||
// Ensure the profileItem is not "0"(is set)
|
||||
if profileItem != "0" {
|
||||
err = store.WriteEntry(ctx, sessionId, profileDataKeys[index], []byte(profileItem))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write profile entry with", "key", profileDataKeys[index], "value", profileItem, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the flag for the current index
|
||||
flag, _ := h.flagManager.GetFlag(profileFlagNames[index])
|
||||
res.FlagSet = append(res.FlagSet, flag)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAllProfileItems is used to persist all the new profile information and setup the required profile flags
|
||||
func (h *Handlers) UpdateAllProfileItems(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
err := h.insertProfileItems(ctx, sessionId, &res)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.defalsify.org/vise.git/lang"
|
||||
@ -22,6 +23,7 @@ import (
|
||||
testdataloader "github.com/peteole/testdata-loader"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
visedb "git.defalsify.org/vise.git/db"
|
||||
memdb "git.defalsify.org/vise.git/db/mem"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
)
|
||||
@ -31,6 +33,11 @@ var (
|
||||
flagsPath = path.Join(baseDir, "services", "registration", "pp.csv")
|
||||
)
|
||||
|
||||
// mockReplaceSeparator function
|
||||
var mockReplaceSeparator = func(input string) string {
|
||||
return strings.ReplaceAll(input, ":", ": ")
|
||||
}
|
||||
|
||||
// InitializeTestStore sets up and returns an in-memory database and store.
|
||||
func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) {
|
||||
ctx := context.Background()
|
||||
@ -56,7 +63,8 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storage.SubPr
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
|
||||
prefix := common.ToBytes(visedb.DATATYPE_USERDATA)
|
||||
spdb := storage.NewSubPrefixDb(db, prefix)
|
||||
|
||||
return spdb
|
||||
}
|
||||
@ -65,12 +73,15 @@ func TestNewHandlers(t *testing.T) {
|
||||
_, store := InitializeTestStore(t)
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
accountService := testservice.TestAccountService{}
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
accountService := testservice.TestAccountService{}
|
||||
|
||||
// Test case for valid UserDataStore
|
||||
t.Run("Valid UserDataStore", func(t *testing.T) {
|
||||
handlers, err := NewHandlers(fm.parser, store, nil, &accountService)
|
||||
handlers, err := NewHandlers(fm.parser, store, nil, &accountService, mockReplaceSeparator)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
@ -80,19 +91,30 @@ func TestNewHandlers(t *testing.T) {
|
||||
if handlers.userdataStore == nil {
|
||||
t.Fatal("expected userdataStore to be set in handlers")
|
||||
}
|
||||
if handlers.ReplaceSeparatorFunc == nil {
|
||||
t.Fatal("expected ReplaceSeparatorFunc to be set in handlers")
|
||||
}
|
||||
|
||||
// Test ReplaceSeparatorFunc functionality
|
||||
input := "1:Menu item"
|
||||
expectedOutput := "1: Menu item"
|
||||
if handlers.ReplaceSeparatorFunc(input) != expectedOutput {
|
||||
t.Fatalf("ReplaceSeparatorFunc function did not return expected output: got %v, want %v", handlers.ReplaceSeparatorFunc(input), expectedOutput)
|
||||
}
|
||||
})
|
||||
|
||||
// Test case for nil userdataStore
|
||||
// Test case for nil UserDataStore
|
||||
t.Run("Nil UserDataStore", func(t *testing.T) {
|
||||
handlers, err := NewHandlers(fm.parser, nil, nil, &accountService)
|
||||
handlers, err := NewHandlers(fm.parser, nil, nil, &accountService, mockReplaceSeparator)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, got none")
|
||||
}
|
||||
if handlers != nil {
|
||||
t.Fatal("expected handlers to be nil")
|
||||
}
|
||||
if err.Error() != "cannot create handler with nil userdata store" {
|
||||
t.Fatalf("expected specific error, got %v", err)
|
||||
expectedError := "cannot create handler with nil userdata store"
|
||||
if err.Error() != expectedError {
|
||||
t.Fatalf("expected error '%s', got '%v'", expectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -179,11 +201,14 @@ func TestSaveFirstname(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(128)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
firstName := "John"
|
||||
|
||||
@ -191,6 +216,8 @@ func TestSaveFirstname(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: store,
|
||||
@ -203,7 +230,7 @@ func TestSaveFirstname(t *testing.T) {
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
|
||||
storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
|
||||
@ -218,11 +245,16 @@ func TestSaveFamilyname(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(128)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_firstname_set}
|
||||
|
||||
// Define test data
|
||||
familyName := "Doeee"
|
||||
|
||||
@ -242,7 +274,7 @@ func TestSaveFamilyname(t *testing.T) {
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
|
||||
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
|
||||
@ -257,11 +289,14 @@ func TestSaveYoB(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
yob := "1980"
|
||||
|
||||
@ -269,6 +304,8 @@ func TestSaveYoB(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_yob_set}
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: store,
|
||||
@ -281,7 +318,7 @@ func TestSaveYoB(t *testing.T) {
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_YOB entry has been updated with the temporary value
|
||||
storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
|
||||
@ -296,11 +333,14 @@ func TestSaveLocation(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_location_set, _ := fm.GetFlag("flag_location_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
location := "Kilifi"
|
||||
|
||||
@ -308,6 +348,8 @@ func TestSaveLocation(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_location_set}
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: store,
|
||||
@ -320,7 +362,7 @@ func TestSaveLocation(t *testing.T) {
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_LOCATION entry has been updated with the temporary value
|
||||
storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
|
||||
@ -335,11 +377,14 @@ func TestSaveOfferings(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Define test data
|
||||
offerings := "Bananas"
|
||||
|
||||
@ -347,6 +392,8 @@ func TestSaveOfferings(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_offerings_set}
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
userdataStore: store,
|
||||
@ -359,7 +406,7 @@ func TestSaveOfferings(t *testing.T) {
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value
|
||||
storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
|
||||
@ -374,9 +421,10 @@ func TestSaveGender(t *testing.T) {
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
|
||||
flag_allow_update, _ := fm.GetFlag("flag_allow_update")
|
||||
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
|
||||
|
||||
// Set the flag in the State
|
||||
mockState := state.NewState(16)
|
||||
mockState := state.NewState(108)
|
||||
mockState.SetFlag(flag_allow_update)
|
||||
|
||||
// Define test cases
|
||||
@ -420,12 +468,16 @@ func TestSaveGender(t *testing.T) {
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
expectedResult := resource.Result{}
|
||||
|
||||
// Call the method
|
||||
res, err := h.SaveGender(ctx, "save_gender", tt.input)
|
||||
|
||||
expectedResult.FlagSet = []uint32{flag_gender_set}
|
||||
|
||||
// Assert results
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, resource.Result{}, res)
|
||||
assert.Equal(t, expectedResult, res)
|
||||
|
||||
// Verify that the DATA_GENDER entry has been updated with the temporary value
|
||||
storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
|
||||
@ -1498,10 +1550,10 @@ func TestValidateRecipient(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Test with invalid recepient",
|
||||
input: []byte("9234adf5"),
|
||||
input: []byte("7?1234"),
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_invalid_recipient},
|
||||
Content: "9234adf5",
|
||||
Content: "7?1234",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1517,22 +1569,40 @@ func TestValidateRecipient(t *testing.T) {
|
||||
input: []byte("0711223344"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test with address",
|
||||
input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
{
|
||||
name: "Test with alias recepient",
|
||||
input: []byte("alias123"),
|
||||
expectedResult: resource.Result{},
|
||||
},
|
||||
}
|
||||
|
||||
// store a public key for the valid recipient
|
||||
err = store.WriteEntry(ctx, "0711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
err = store.WriteEntry(ctx, "+254711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
// Create the Handlers instance
|
||||
h := &Handlers{
|
||||
flagManager: fm.parser,
|
||||
userdataStore: store,
|
||||
flagManager: fm.parser,
|
||||
userdataStore: store,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
|
||||
aliasResponse := &dataserviceapi.AliasAddress{
|
||||
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
||||
}
|
||||
|
||||
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
|
||||
|
||||
// Call the method
|
||||
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
|
||||
|
||||
@ -1564,7 +1634,7 @@ func TestCheckBalance(t *testing.T) {
|
||||
publicKey: "0X98765432109",
|
||||
activeSym: "ETH",
|
||||
activeBal: "1.5",
|
||||
expectedResult: resource.Result{Content: "Balance: 1.5 ETH\n"},
|
||||
expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
@ -1919,7 +1989,7 @@ func TestCheckVouchers(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read voucher sym data from the store
|
||||
voucherData, err := spdb.Get(ctx, []byte("sym"))
|
||||
voucherData, err := spdb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1932,26 +2002,31 @@ func TestCheckVouchers(t *testing.T) {
|
||||
|
||||
func TestGetVoucherList(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
spdb := InitializeTestSubPrefixDb(t, ctx)
|
||||
|
||||
// Initialize Handlers
|
||||
h := &Handlers{
|
||||
prefixDb: spdb,
|
||||
prefixDb: spdb,
|
||||
ReplaceSeparatorFunc: mockReplaceSeparator,
|
||||
}
|
||||
|
||||
expectedSym := []byte("1:SRF\n2:MILO")
|
||||
mockSyms := []byte("1:SRF\n2:MILO")
|
||||
|
||||
// Put voucher sym data from the store
|
||||
err := spdb.Put(ctx, []byte("sym"), expectedSym)
|
||||
err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), mockSyms)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expectedSyms := []byte("1: SRF\n2: MILO")
|
||||
|
||||
res, err := h.GetVoucherList(ctx, "", []byte(""))
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res.Content, string(expectedSym))
|
||||
assert.Equal(t, res.Content, string(expectedSyms))
|
||||
}
|
||||
|
||||
func TestViewVoucher(t *testing.T) {
|
||||
@ -1973,16 +2048,16 @@ func TestViewVoucher(t *testing.T) {
|
||||
}
|
||||
|
||||
// Define mock voucher data
|
||||
mockData := map[string][]byte{
|
||||
"sym": []byte("1:SRF\n2:MILO"),
|
||||
"bal": []byte("1:100\n2:200"),
|
||||
"deci": []byte("1:6\n2:4"),
|
||||
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||
mockData := map[common.DataTyp][]byte{
|
||||
common.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
|
||||
common.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
|
||||
common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
|
||||
common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
||||
}
|
||||
|
||||
// Put the data
|
||||
for key, value := range mockData {
|
||||
err = spdb.Put(ctx, []byte(key), []byte(value))
|
||||
err = spdb.Put(ctx, []byte(common.ToBytes(key)), []byte(value))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -1990,7 +2065,7 @@ func TestViewVoucher(t *testing.T) {
|
||||
|
||||
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res.Content, "SRF\n100")
|
||||
assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100")
|
||||
}
|
||||
|
||||
func TestSetVoucher(t *testing.T) {
|
||||
|
@ -115,7 +115,8 @@ func(tdb *ThreadGdbmDb) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func(tdb *ThreadGdbmDb) Dump(_ context.Context, _ []byte) (*db.Dumper, error) {
|
||||
logg.Warnf("method not implemented for thread gdbm db")
|
||||
return nil, nil
|
||||
func(tdb *ThreadGdbmDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
|
||||
tdb.reserve()
|
||||
defer tdb.release()
|
||||
return tdb.db.Dump(ctx, key)
|
||||
}
|
||||
|
@ -6,10 +6,6 @@ import (
|
||||
"git.defalsify.org/vise.git/db"
|
||||
)
|
||||
|
||||
const (
|
||||
DATATYPE_USERSUB = 64
|
||||
)
|
||||
|
||||
// PrefixDb interface abstracts the database operations.
|
||||
type PrefixDb interface {
|
||||
Get(ctx context.Context, key []byte) ([]byte, error)
|
||||
@ -35,13 +31,13 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
|
||||
}
|
||||
|
||||
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
||||
s.store.SetPrefix(DATATYPE_USERSUB)
|
||||
s.store.SetPrefix(db.DATATYPE_USERDATA)
|
||||
key = s.toKey(key)
|
||||
return s.store.Get(ctx, key)
|
||||
}
|
||||
|
||||
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
||||
s.store.SetPrefix(DATATYPE_USERSUB)
|
||||
s.store.SetPrefix(db.DATATYPE_USERDATA)
|
||||
key = s.toKey(key)
|
||||
return s.store.Put(ctx, key, val)
|
||||
}
|
@ -49,6 +49,6 @@ func (m *MockAccountService) TokenTransfer(ctx context.Context, amount, from, to
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) {
|
||||
args := m.Called()
|
||||
args := m.Called(alias)
|
||||
return args.Get(0).(*dataserviceapi.AliasAddress), args.Error(1)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
|
||||
// It adjusts for cases where the current date is before the birthday in the current year.
|
||||
@ -25,11 +28,29 @@ func CalculateAge(birthdate, today time.Time) int {
|
||||
// It subtracts the YOB from the current year to determine the age.
|
||||
//
|
||||
// Parameters:
|
||||
// yob: The year of birth as an integer.
|
||||
//
|
||||
// yob: The year of birth as an integer.
|
||||
//
|
||||
// Returns:
|
||||
// The calculated age as an integer.
|
||||
//
|
||||
// The calculated age as an integer.
|
||||
func CalculateAgeWithYOB(yob int) int {
|
||||
currentYear := time.Now().Year()
|
||||
return currentYear - yob
|
||||
}
|
||||
currentYear := time.Now().Year()
|
||||
return currentYear - yob
|
||||
}
|
||||
|
||||
|
||||
//IsValidYob checks if the provided yob can be considered valid
|
||||
func IsValidYOb(yob string) bool {
|
||||
currentYear := time.Now().Year()
|
||||
yearOfBirth, err := strconv.ParseInt(yob, 10, 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if yearOfBirth >= 1900 && int(yearOfBirth) <= currentYear {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
17
internal/utils/name.go
Normal file
17
internal/utils/name.go
Normal file
@ -0,0 +1,17 @@
|
||||
package utils
|
||||
|
||||
func ConstructName(firstName, familyName, defaultValue string) string {
|
||||
name := defaultValue
|
||||
if familyName != defaultValue {
|
||||
if firstName != defaultValue {
|
||||
name = firstName + " " + familyName
|
||||
} else {
|
||||
name = familyName
|
||||
}
|
||||
} else {
|
||||
if firstName != defaultValue {
|
||||
name = firstName
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -62,7 +62,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Select language:\n0:english\n1:kiswahili"
|
||||
"expectedContent": "Select language:\n0:English\n1:Kiswahili"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -95,7 +95,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -141,7 +141,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1235",
|
||||
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
|
||||
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
@ -167,7 +167,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_firstname",
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -187,6 +187,26 @@
|
||||
},
|
||||
{
|
||||
"input": "foo",
|
||||
"expectedContent": "Enter family name:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "bar",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
@ -197,10 +217,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -208,7 +224,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_familyname",
|
||||
"name": "menu_my_account_edit_familyname_when_all_account__details_have_been_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -238,10 +254,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -250,7 +262,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_gender",
|
||||
"name": "menu_my_account_edit_gender_when_all_account__details_have_been_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -280,10 +292,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -291,7 +299,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_yob",
|
||||
"name": "menu_my_account_edit_yob_when_all_account__details_have_been_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -321,10 +329,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -332,7 +336,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_location",
|
||||
"name": "menu_my_account_edit_location_when_all_account_details_have_been_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -362,10 +366,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -373,7 +373,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "menu_my_account_edit_offerings",
|
||||
"name": "menu_my_account_edit_offerings_when_all_account__details_have_been_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
@ -403,10 +403,6 @@
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
@ -434,16 +430,12 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back"
|
||||
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 84\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
|
@ -3,6 +3,7 @@ package menutraversaltest
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
@ -15,14 +16,15 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
testData = driver.ReadData()
|
||||
testStore = ".test_state"
|
||||
groupTestFile = "group_test.json"
|
||||
sessionID string
|
||||
src = rand.NewSource(42)
|
||||
g = rand.New(src)
|
||||
testData = driver.ReadData()
|
||||
testStore = ".test_state"
|
||||
sessionID string
|
||||
src = rand.NewSource(42)
|
||||
g = rand.New(src)
|
||||
)
|
||||
|
||||
var groupTestFile = flag.String("test-file", "group_test.json", "The test file to use for running the group tests")
|
||||
|
||||
func GenerateSessionId() string {
|
||||
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
|
||||
v, err := uu.NewV4()
|
||||
@ -337,7 +339,7 @@ func TestMainMenuSend(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGroups(t *testing.T) {
|
||||
groups, err := driver.LoadTestGroups(groupTestFile)
|
||||
groups, err := driver.LoadTestGroups(*groupTestFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load test groups: %v", err)
|
||||
}
|
||||
|
68
menutraversal_test/profile_edit_start_familyname.json
Normal file
68
menutraversal_test/profile_edit_start_familyname.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_family_name",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "Enter family name:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "bar",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
61
menutraversal_test/profile_edit_start_firstname.json
Normal file
61
menutraversal_test/profile_edit_start_firstname.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_firstname",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your first names:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "foo",
|
||||
"expectedContent": "Enter family name:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "bar",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
55
menutraversal_test/profile_edit_start_gender.json
Normal file
55
menutraversal_test/profile_edit_start_gender.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_gender",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
46
menutraversal_test/profile_edit_start_location.json
Normal file
46
menutraversal_test/profile_edit_start_location.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_location",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "5",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
42
menutraversal_test/profile_edit_start_offerings.json
Normal file
42
menutraversal_test/profile_edit_start_offerings.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_offerings",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "6",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
50
menutraversal_test/profile_edit_start_yob.json
Normal file
50
menutraversal_test/profile_edit_start_yob.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_all_account_details_starting_from_yob",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "4",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
70
menutraversal_test/profile_edit_when_adjacent_item_set.json
Normal file
70
menutraversal_test/profile_edit_when_adjacent_item_set.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"groups": [
|
||||
{
|
||||
"name": "menu_my_account_edit_familyname_when_adjacent_profile_information_set",
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "My Account\n1:Profile\n2:Change language\n3:Check balances\n4:Check statement\n5:PIN options\n6:My Address\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "3",
|
||||
"expectedContent": "Select gender: \n1:Male\n2:Female\n3:Unspecified\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
"expectedContent": "Enter your year of birth\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "1940",
|
||||
"expectedContent": "Enter your location:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Kilifi",
|
||||
"expectedContent": "Enter the services or goods you offer: \n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "Bananas",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "2",
|
||||
"expectedContent": "Enter family name:\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "foo2",
|
||||
"expectedContent": "Please enter your PIN:"
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Profile updated successfully\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "My profile\n1:Edit name\n2:Edit family name\n3:Edit gender\n4:Edit year of birth\n5:Edit location\n6:Edit offerings\n7:View profile\n0:Back"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
|
||||
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -40,11 +40,11 @@
|
||||
"steps": [
|
||||
{
|
||||
"input": "",
|
||||
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
|
||||
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
|
||||
"expectedContent": "Do you agree to terms and conditions?\nhttps://grassecon.org/pages/terms-and-conditions\n\n0:Yes\n1:No"
|
||||
},
|
||||
{
|
||||
"input": "1",
|
||||
|
18
models/profile.go
Normal file
18
models/profile.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
type Profile struct {
|
||||
ProfileItems []string
|
||||
Max int
|
||||
}
|
||||
|
||||
func (p *Profile) InsertOrShift(index int, value string) {
|
||||
if index < len(p.ProfileItems) {
|
||||
p.ProfileItems = append(p.ProfileItems[:index], value)
|
||||
} else {
|
||||
for len(p.ProfileItems) < index {
|
||||
p.ProfileItems = append(p.ProfileItems, "0")
|
||||
}
|
||||
p.ProfileItems = append(p.ProfileItems, "0")
|
||||
p.ProfileItems[index] = value
|
||||
}
|
||||
}
|
1
services/registration/_catch_swa
Normal file
1
services/registration/_catch_swa
Normal file
@ -0,0 +1 @@
|
||||
Tatizo la kimtambo limetokea,tafadhali jaribu tena baadaye.
|
@ -2,9 +2,17 @@ CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_familyname flag_allow_update 1
|
||||
LOAD get_current_profile_info 0
|
||||
RELOAD get_current_profile_info
|
||||
MAP get_current_profile_info
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_familyname 0
|
||||
RELOAD set_back
|
||||
CATCH _ flag_back_set 1
|
||||
LOAD save_familyname 64
|
||||
RELOAD save_familyname
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
CATCH pin_entry flag_familyname_set 1
|
||||
CATCH select_gender flag_gender_set 0
|
||||
CATCH edit_yob flag_yob_set 0
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_familyname_set 0
|
||||
INCMP select_gender *
|
||||
|
@ -5,7 +5,14 @@ RELOAD get_current_profile_info
|
||||
MAP get_current_profile_info
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_firstname 0
|
||||
RELOAD set_back
|
||||
CATCH _ flag_back_set 1
|
||||
LOAD save_firstname 128
|
||||
RELOAD save_firstname
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
CATCH pin_entry flag_firstname_set 1
|
||||
CATCH edit_family_name flag_familyname_set 0
|
||||
CATCH edit_gender flag_gender_set 0
|
||||
CATCH edit_yob flag_yob_set 0
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_firstname_set 0
|
||||
|
@ -2,9 +2,14 @@ CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_location flag_allow_update 1
|
||||
LOAD get_current_profile_info 0
|
||||
RELOAD get_current_profile_info
|
||||
LOAD save_location 16
|
||||
MOUT back 0
|
||||
HALT
|
||||
LOAD save_location 0
|
||||
RELOAD set_back
|
||||
CATCH _ flag_back_set 1
|
||||
RELOAD save_location
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
CATCH pin_entry flag_location_set 1
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_location_set 0
|
||||
INCMP edit_offerings *
|
||||
|
@ -2,9 +2,13 @@ CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_offerings flag_allow_update 1
|
||||
LOAD get_current_profile_info 0
|
||||
RELOAD get_current_profile_info
|
||||
LOAD save_offerings 0
|
||||
LOAD save_offerings 8
|
||||
MOUT back 0
|
||||
HALT
|
||||
RELOAD set_back
|
||||
CATCH _ flag_back_set 1
|
||||
RELOAD save_offerings
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
CATCH pin_entry flag_offerings_set 1
|
||||
CATCH pin_entry flag_offerings_set 0
|
||||
INCMP update_profile_items *
|
||||
|
@ -11,7 +11,8 @@ MOUT edit_offerings 6
|
||||
MOUT view 7
|
||||
MOUT back 0
|
||||
HALT
|
||||
INCMP my_account 0
|
||||
LOAD set_back 6
|
||||
INCMP ^ 0
|
||||
INCMP edit_first_name 1
|
||||
INCMP edit_family_name 2
|
||||
INCMP select_gender 3
|
||||
|
@ -5,10 +5,15 @@ RELOAD get_current_profile_info
|
||||
MAP get_current_profile_info
|
||||
MOUT back 0
|
||||
HALT
|
||||
RELOAD set_back
|
||||
CATCH _ flag_back_set 1
|
||||
LOAD verify_yob 6
|
||||
RELOAD verify_yob
|
||||
CATCH incorrect_date_format flag_incorrect_date_format 1
|
||||
LOAD save_yob 0
|
||||
LOAD save_yob 32
|
||||
RELOAD save_yob
|
||||
INCMP _ 0
|
||||
INCMP pin_entry *
|
||||
CATCH pin_entry flag_yob_set 1
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_yob_set 0
|
||||
INCMP edit_location *
|
||||
|
1
services/registration/english_menu
Normal file
1
services/registration/english_menu
Normal file
@ -0,0 +1 @@
|
||||
English
|
@ -1 +1 @@
|
||||
Incorrect pin
|
||||
Incorrect PIN
|
@ -1 +1 @@
|
||||
PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.
|
||||
PIN mpya na udhibitisho wa PIN mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.
|
||||
|
1
services/registration/kiswahili_menu
Normal file
1
services/registration/kiswahili_menu
Normal file
@ -0,0 +1 @@
|
||||
Kiswahili
|
@ -13,7 +13,7 @@ msgstr "Kwa usaidizi zaidi,piga: 0757628885"
|
||||
msgid "Balance: %s\n"
|
||||
msgstr "Salio: %s\n"
|
||||
|
||||
msid "Your invite request for %s to Sarafu Network failed. Please try again later."
|
||||
msgid "Your invite request for %s to Sarafu Network failed. Please try again later."
|
||||
msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu halikufaulu. Tafadhali jaribu tena baadaye."
|
||||
|
||||
msgid "Your invitation to %s to join Sarafu Network has been sent."
|
||||
@ -23,4 +23,7 @@ msgid "Your request failed. Please try again later."
|
||||
msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye."
|
||||
|
||||
msgid "Community Balance: 0.00"
|
||||
msgid "Salio la Kikundi: 0.00"
|
||||
msgstr "Salio la Kikundi: 0.00"
|
||||
|
||||
msgid "Symbol: %s\nBalance: %s"
|
||||
msgstr "Sarafu: %s\nSalio: %s"
|
||||
|
@ -2,9 +2,9 @@ LOAD set_default_voucher 8
|
||||
RELOAD set_default_voucher
|
||||
LOAD check_vouchers 10
|
||||
RELOAD check_vouchers
|
||||
LOAD check_balance 64
|
||||
LOAD check_balance 128
|
||||
RELOAD check_balance
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
MAP check_balance
|
||||
MOUT send 1
|
||||
MOUT vouchers 2
|
||||
|
@ -1 +1 @@
|
||||
Salio lako ni: 0.00 SRF
|
||||
{{.check_balance}}
|
1
services/registration/next_menu
Normal file
1
services/registration/next_menu
Normal file
@ -0,0 +1 @@
|
||||
Next
|
1
services/registration/next_menu_swa
Normal file
1
services/registration/next_menu_swa
Normal file
@ -0,0 +1 @@
|
||||
Mbele
|
@ -1 +1 @@
|
||||
no
|
||||
No
|
@ -1 +1 @@
|
||||
la
|
||||
La
|
@ -21,3 +21,10 @@ flag,flag_admin_privilege,27,this is set when a user has admin privileges.
|
||||
flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
|
||||
flag,flag_no_transfers,29,this is set when a user does not have any transactions
|
||||
flag,flag_incorrect_statement,30,this is set when the selected statement is invalid
|
||||
flag,flag_firstname_set,31,this is set when the first name of the profile is set
|
||||
flag,flag_familyname_set,32,this is set when the family name of the profile is set
|
||||
flag,flag_yob_set,33,this is set when the yob of the profile is set
|
||||
flag,flag_gender_set,34,this is set when the gender of the profile is set
|
||||
flag,flag_location_set,35,this is set when the location of the profile is set
|
||||
flag,flag_offerings_set,36,this is set when the offerings of the profile is set
|
||||
flag,flag_back_set,37,this is set when it is a back navigation
|
||||
|
|
1
services/registration/prev_menu
Normal file
1
services/registration/prev_menu
Normal file
@ -0,0 +1 @@
|
||||
Prev
|
1
services/registration/prev_menu_swa
Normal file
1
services/registration/prev_menu_swa
Normal file
@ -0,0 +1 @@
|
||||
Nyuma
|
@ -1,3 +1,5 @@
|
||||
LOAD update_all_profile_items 0
|
||||
RELOAD update_all_profile_items
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
|
@ -1,3 +1,4 @@
|
||||
LOAD set_language 6
|
||||
RELOAD set_language
|
||||
CATCH terms flag_account_created 0
|
||||
MOVE language_changed
|
||||
|
@ -1,4 +1,10 @@
|
||||
LOAD save_gender 0
|
||||
LOAD save_gender 32
|
||||
RELOAD save_gender
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
CATCH pin_entry flag_gender_set 1
|
||||
CATCH edit_yob flag_yob_set 0
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_gender_set 0
|
||||
MOVE edit_yob
|
||||
|
@ -1,4 +1,10 @@
|
||||
LOAD save_gender 0
|
||||
LOAD save_gender 16
|
||||
RELOAD save_gender
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
CATCH pin_entry flag_gender_set 1
|
||||
CATCH edit_yob flag_yob_set 0
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_gender_set 0
|
||||
MOVE edit_yob
|
||||
|
@ -1,3 +1,4 @@
|
||||
LOAD set_language 6
|
||||
RELOAD set_language
|
||||
CATCH terms flag_account_created 0
|
||||
MOVE language_changed
|
||||
|
@ -1,4 +1,10 @@
|
||||
LOAD save_gender 0
|
||||
LOAD save_gender 8
|
||||
RELOAD save_gender
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH update_gender flag_allow_update 1
|
||||
MOVE pin_entry
|
||||
CATCH pin_entry flag_gender_set 1
|
||||
CATCH edit_yob flag_yob_set 0
|
||||
CATCH edit_location flag_location_set 0
|
||||
CATCH edit_offerings flag_offerings_set 0
|
||||
CATCH pin_entry flag_gender_set 0
|
||||
MOVE edit_yob
|
||||
|
@ -1 +1,2 @@
|
||||
Do you agree to terms and conditions?
|
||||
Do you agree to terms and conditions?
|
||||
https://grassecon.org/pages/terms-and-conditions
|
||||
|
@ -1 +1,2 @@
|
||||
Kwa kutumia hii huduma umekubali sheria na masharti?
|
||||
Kwa kutumia hii huduma umekubali sheria na masharti?
|
||||
https://grassecon.org/pages/terms-and-conditions
|
||||
|
3
services/registration/update_profile_items.vis
Normal file
3
services/registration/update_profile_items.vis
Normal file
@ -0,0 +1,3 @@
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH profile_update_success flag_allow_update 1
|
||||
MOVE pin_entry
|
@ -1 +1 @@
|
||||
yes
|
||||
Yes
|
@ -1 +1 @@
|
||||
ndio
|
||||
Ndio
|
Loading…
Reference in New Issue
Block a user