Compare commits

..

2 Commits

Author SHA1 Message Date
95f6a735a0
ifx: log line to display build version 2024-11-19 16:30:41 +03:00
0bb44fc80b
feat: dockerfile and github based CI build
* fixed missing error handler in main
* add injectable build string in main
* add (dynamically linked) docker build
* add github CI workflow
* add extra but useful dev files in dev folder
2024-11-19 13:50:36 +03:00
89 changed files with 287 additions and 2017 deletions

View File

@ -10,4 +10,4 @@
!/LICENSE !/LICENSE
!/README.md !/README.md
!/go.* !/go.*
!/.env.example !/.env

View File

@ -45,7 +45,9 @@ jobs:
with: with:
context: ./ context: ./
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64 platforms:
- linux/amd64
- linux/arm64
push: true push: true
build-args: | build-args: |
BUILD=${{ env.RELEASE_SHORT_COMMIT }} BUILD=${{ env.RELEASE_SHORT_COMMIT }}

View File

@ -18,7 +18,7 @@ RUN make VISE_PATH=/build/go-vise -B
WORKDIR /build WORKDIR /build
RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM" RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM"
RUN go mod download RUN go mod download
RUN go build -tags logtrace -o ussd-africastalking -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go RUN go build -o ussd-africastalking -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go
FROM debian:bookworm-slim FROM debian:bookworm-slim
@ -33,8 +33,7 @@ COPY --from=build /build/ussd-africastalking .
COPY --from=build /build/LICENSE . COPY --from=build /build/LICENSE .
COPY --from=build /build/README.md . COPY --from=build /build/README.md .
COPY --from=build /build/services ./services COPY --from=build /build/services ./services
COPY --from=build /build/.env.example . COPY --from=build /build/.env .
RUN mv .env.example .env
EXPOSE 7123 EXPOSE 7123

View File

@ -7,6 +7,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@ -19,7 +20,6 @@ import (
"git.defalsify.org/vise.git/logging" "git.defalsify.org/vise.git/logging"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/config" "git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/initializers" "git.grassecon.net/urdt/ussd/initializers"
"git.grassecon.net/urdt/ussd/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
@ -44,14 +44,14 @@ type atRequestParser struct{}
func (arp *atRequestParser) GetSessionId(rq any) (string, error) { func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request) rqv, ok := rq.(*http.Request)
if !ok { if !ok {
logg.Warnf("got an invalid request", "req", rq) log.Println("got an invalid request:", rq)
return "", handlers.ErrInvalidRequest return "", handlers.ErrInvalidRequest
} }
// Capture body (if any) for logging // Capture body (if any) for logging
body, err := io.ReadAll(rqv.Body) body, err := io.ReadAll(rqv.Body)
if err != nil { if err != nil {
logg.Warnf("failed to read request body", "err", err) log.Println("failed to read request body:", err)
return "", fmt.Errorf("failed to read request body: %v", err) return "", fmt.Errorf("failed to read request body: %v", err)
} }
// Reset the body for further reading // Reset the body for further reading
@ -61,13 +61,13 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
bodyLog := map[string]string{"body": string(body)} bodyLog := map[string]string{"body": string(body)}
logBytes, err := json.Marshal(bodyLog) logBytes, err := json.Marshal(bodyLog)
if err != nil { if err != nil {
logg.Warnf("failed to marshal request body", "err", err) log.Println("failed to marshal request body:", err)
} else { } else {
logg.Debugf("received request", "bytes", logBytes) log.Println("Received request:", string(logBytes))
} }
if err := rqv.ParseForm(); err != nil { if err := rqv.ParseForm(); err != nil {
logg.Warnf("failed to parse form data", "err", err) log.Println("failed to parse form data: %v", err)
return "", fmt.Errorf("failed to parse form data: %v", err) return "", fmt.Errorf("failed to parse form data: %v", err)
} }
@ -76,13 +76,7 @@ func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
return "", fmt.Errorf("no phone number found") return "", fmt.Errorf("no phone number found")
} }
formattedNumber, err := common.FormatPhoneNumber(phoneNumber) return phoneNumber, nil
if err != nil {
logg.Warnf("failed to format phone number", "err", err)
return "", fmt.Errorf("failed to format number")
}
return formattedNumber, nil
} }
func (arp *atRequestParser) GetInput(rq any) ([]byte, error) { func (arp *atRequestParser) GetInput(rq any) ([]byte, error) {

View File

@ -7,84 +7,32 @@ import (
"git.defalsify.org/vise.git/logging" "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 type DataTyp uint16
const ( const (
// API Tracking id to follow status of account creation DATA_ACCOUNT DataTyp = iota
DATA_TRACKING_ID = iota DATA_ACCOUNT_CREATED
// EVM address returned from API on account creation DATA_TRACKING_ID
DATA_PUBLIC_KEY DATA_PUBLIC_KEY
// Currently active PIN used to authenticate ussd state change requests DATA_CUSTODIAL_ID
DATA_ACCOUNT_PIN DATA_ACCOUNT_PIN
// The first name of the user DATA_ACCOUNT_STATUS
DATA_FIRST_NAME DATA_FIRST_NAME
// The last name of the user
DATA_FAMILY_NAME DATA_FAMILY_NAME
// The year-of-birth of the user
DATA_YOB DATA_YOB
// The location of the user
DATA_LOCATION DATA_LOCATION
// The gender of the user
DATA_GENDER DATA_GENDER
// The offerings description of the user
DATA_OFFERINGS DATA_OFFERINGS
// The ethereum address of the recipient of an ongoing send request
DATA_RECIPIENT DATA_RECIPIENT
// The voucher value amount of an ongoing send request
DATA_AMOUNT DATA_AMOUNT
// A general swap field for temporary values
DATA_TEMPORARY_VALUE DATA_TEMPORARY_VALUE
// Currently active voucher symbol of user
DATA_ACTIVE_SYM DATA_ACTIVE_SYM
// Voucher balance of user's currently active voucher
DATA_ACTIVE_BAL DATA_ACTIVE_BAL
// String boolean indicating whether use of PIN is blocked
DATA_BLOCKED_NUMBER DATA_BLOCKED_NUMBER
// Reverse mapping of a user's evm address to a session id.
DATA_PUBLIC_KEY_REVERSE DATA_PUBLIC_KEY_REVERSE
// Decimal count of the currently active voucher
DATA_ACTIVE_DECIMAL DATA_ACTIVE_DECIMAL
// EVM address of the currently active voucher
DATA_ACTIVE_ADDRESS 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 ( var (
@ -121,10 +69,3 @@ func StringToDataTyp(str string) (DataTyp, error) {
return 0, errors.New("invalid DataTyp string") 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
}

View File

@ -1,73 +0,0 @@
package common
import (
"errors"
"fmt"
"regexp"
"strings"
)
// Define the regex patterns as constants
const (
phoneRegex = `^(?:\+254|254|0)?((?:7[0-9]{8})|(?:1[01][0-9]{7}))$`
addressRegex = `^0x[a-fA-F0-9]{40}$`
aliasRegex = `^[a-zA-Z0-9]+$`
)
// IsValidPhoneNumber checks if the given number is a valid phone number
func IsValidPhoneNumber(phonenumber string) bool {
match, _ := regexp.MatchString(phoneRegex, phonenumber)
return match
}
// IsValidAddress checks if the given address is a valid Ethereum address
func IsValidAddress(address string) bool {
match, _ := regexp.MatchString(addressRegex, address)
return match
}
// IsValidAlias checks if the alias is a valid alias format
func IsValidAlias(alias string) bool {
match, _ := regexp.MatchString(aliasRegex, alias)
return match
}
// CheckRecipient validates the recipient format based on the criteria
func CheckRecipient(recipient string) (string, error) {
if IsValidPhoneNumber(recipient) {
return "phone number", nil
}
if IsValidAddress(recipient) {
return "address", nil
}
if IsValidAlias(recipient) {
return "alias", nil
}
return "", fmt.Errorf("invalid recipient: must be a phone number, address or alias")
}
// FormatPhoneNumber formats a Kenyan phone number to "+254xxxxxxxx".
func FormatPhoneNumber(phone string) (string, error) {
if !IsValidPhoneNumber(phone) {
return "", errors.New("invalid phone number")
}
// Remove any leading "+" and spaces
phone = strings.TrimPrefix(phone, "+")
phone = strings.ReplaceAll(phone, " ", "")
// Replace leading "0" with "254" if present
if strings.HasPrefix(phone, "0") {
phone = "254" + phone[1:]
}
// Add "+" if not already present
if !strings.HasPrefix(phone, "254") {
return "", errors.New("unexpected format")
}
return "+" + phone, nil
}

View File

@ -1,119 +0,0 @@
package common
import (
"context"
"fmt"
"strings"
"time"
"git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
// TransferMetadata helps organize data fields
type TransferMetadata struct {
Senders string
Recipients string
TransferValues string
Addresses string
TxHashes string
Dates string
Symbols string
Decimals string
}
// ProcessTransfers converts transfers into formatted strings
func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetadata {
var data TransferMetadata
var senders, recipients, transferValues, addresses, txHashes, dates, symbols, decimals []string
for _, t := range transfers {
senders = append(senders, t.Sender)
recipients = append(recipients, t.Recipient)
// Scale down the amount
scaledBalance := ScaleDownBalance(t.TransferValue, t.TokenDecimals)
transferValues = append(transferValues, scaledBalance)
addresses = append(addresses, t.ContractAddress)
txHashes = append(txHashes, t.TxHash)
dates = append(dates, fmt.Sprintf("%s", t.DateBlock))
symbols = append(symbols, t.TokenSymbol)
decimals = append(decimals, t.TokenDecimals)
}
data.Senders = strings.Join(senders, "\n")
data.Recipients = strings.Join(recipients, "\n")
data.TransferValues = strings.Join(transferValues, "\n")
data.Addresses = strings.Join(addresses, "\n")
data.TxHashes = strings.Join(txHashes, "\n")
data.Dates = strings.Join(dates, "\n")
data.Symbols = strings.Join(symbols, "\n")
data.Decimals = strings.Join(decimals, "\n")
return data
}
// 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 := []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, ToBytes(key))
if err != nil {
return "", fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
}
data[key] = string(value)
}
// Split the data
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) {
return "", fmt.Errorf("transaction not found: index %d out of range", index)
}
// Adjust for 0-based indexing
i := index - 1
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]))
}
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",
transactionType,
strings.TrimSpace(values[i]),
strings.TrimSpace(syms[i]),
party,
strings.TrimSpace(addresses[i]),
strings.TrimSpace(hashes[i]),
formattedDate,
)
return detail, nil
}
// Helper function to format date in desired output
func formatDate(dateStr string) string {
parsedDate, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dateStr)
if err != nil {
fmt.Println("Error parsing date:", err)
return ""
}
return parsedDate.Format("2006-01-02 03:04:05 PM")
}

View File

@ -20,7 +20,7 @@ type UserDataStore struct {
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) { func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId) store.SetSession(sessionId)
k := ToBytes(typ) k := PackKey(typ, []byte(sessionId))
return store.Get(ctx, k) 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 { func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
store.SetPrefix(db.DATATYPE_USERDATA) store.SetPrefix(db.DATATYPE_USERDATA)
store.SetSession(sessionId) store.SetSession(sessionId)
k := ToBytes(typ) k := PackKey(typ, []byte(sessionId))
return store.Put(ctx, k, value) return store.Put(ctx, k, value)
} }

View File

@ -64,23 +64,22 @@ func ScaleDownBalance(balance, decimals string) string {
// GetVoucherData retrieves and matches voucher data // GetVoucherData retrieves and matches voucher data
func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) { func GetVoucherData(ctx context.Context, db storage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES} keys := []string{"sym", "bal", "deci", "addr"}
data := make(map[DataTyp]string) data := make(map[string]string)
for _, key := range keys { for _, key := range keys {
value, err := db.Get(ctx, ToBytes(key)) value, err := db.Get(ctx, []byte(key))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err) return nil, fmt.Errorf("failed to get %s: %v", key, err)
} }
data[key] = string(value) data[key] = string(value)
} }
symbol, balance, decimal, address := MatchVoucher(input, symbol, balance, decimal, address := MatchVoucher(input,
data[DATA_VOUCHER_SYMBOLS], data["sym"],
data[DATA_VOUCHER_BALANCES], data["bal"],
data[DATA_VOUCHER_DECIMALS], data["deci"],
data[DATA_VOUCHER_ADDRESSES], data["addr"])
)
if symbol == "" { if symbol == "" {
return nil, nil return nil, nil
@ -152,7 +151,7 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str
return data, nil return data, nil
} }
// UpdateVoucherData updates the active voucher data in the DataStore. // UpdateVoucherData sets the active voucher data in the DataStore.
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error { func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
logg.TraceCtxf(ctx, "dtal", "data", data) logg.TraceCtxf(ctx, "dtal", "data", data)
// Active voucher data entries // Active voucher data entries

View File

@ -8,9 +8,8 @@ import (
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
@ -84,21 +83,19 @@ func TestGetVoucherData(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
prefix := ToBytes(visedb.DATATYPE_USERDATA)
spdb := storage.NewSubPrefixDb(db, prefix)
// Test voucher data // Test voucher data
mockData := map[DataTyp][]byte{ mockData := map[string][]byte{
DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), "sym": []byte("1:SRF\n2:MILO"),
DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), "bal": []byte("1:100\n2:200"),
DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), "deci": []byte("1:6\n2:4"),
DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), "addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
} }
// Put the data // Put the data
for key, value := range mockData { for key, value := range mockData {
err = spdb.Put(ctx, []byte(ToBytes(key)), []byte(value)) err = spdb.Put(ctx, []byte(key), []byte(value))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -15,7 +15,6 @@ const (
voucherHoldingsPathPrefix = "/api/v1/holdings" voucherHoldingsPathPrefix = "/api/v1/holdings"
voucherTransfersPathPrefix = "/api/v1/transfers/last10" voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token" voucherDataPathPrefix = "/api/v1/token"
AliasPrefix = "api/v1/alias"
) )
var ( var (
@ -33,7 +32,6 @@ var (
VoucherHoldingsURL string VoucherHoldingsURL string
VoucherTransfersURL string VoucherTransfersURL string
VoucherDataURL string VoucherDataURL string
CheckAliasURL string
) )
func setBase() error { func setBase() error {
@ -68,7 +66,6 @@ func LoadConfig() error {
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix) VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix) VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix) VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
CheckAliasURL, _ = url.JoinPath(dataURLBase, AliasPrefix)
return nil return nil
} }

View File

@ -1,5 +0,0 @@
package debug
var (
DebugCap uint32
)

View File

@ -1,84 +0,0 @@
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))]
}

View File

@ -1,48 +0,0 @@
// +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"
}

View File

@ -1,78 +0,0 @@
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)
}
}
}

View File

@ -1,79 +0,0 @@
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)
}
}

View File

@ -1,78 +0,0 @@
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)
}
}

View File

@ -1,28 +0,0 @@
# 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.

34
go.mod
View File

@ -2,38 +2,44 @@ module git.grassecon.net/urdt/ussd
go 1.23.0 go 1.23.0
toolchain go1.23.2
require ( require (
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80 git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
github.com/alecthomas/assert/v2 v2.2.2 github.com/alecthomas/assert/v2 v2.2.2
github.com/gofrs/uuid v4.4.0+incompatible
github.com/grassrootseconomics/eth-custodial v1.3.0-beta github.com/grassrootseconomics/eth-custodial v1.3.0-beta
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta
github.com/joho/godotenv v1.5.1
github.com/peteole/testdata-loader v0.3.0 github.com/peteole/testdata-loader v0.3.0
github.com/stretchr/testify v1.9.0
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 gopkg.in/leonelquinteros/gotext.v1 v1.3.1
) )
require github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a // indirect
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/joho/godotenv v1.5.1
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
)
require ( require (
github.com/alecthomas/participle/v2 v2.0.0 // indirect github.com/alecthomas/participle/v2 v2.0.0 // indirect
github.com/alecthomas/repr v0.2.0 // indirect github.com/alecthomas/repr v0.2.0 // indirect
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

8
go.sum
View File

@ -1,5 +1,5 @@
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80 h1:GYUVXRUtMpA40T4COeAduoay6CIgXjD5cfDYZOTFIKw= git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b h1:dxBplsIlzJHV+5EH+gzB+w08Blt7IJbb2jeRe1OEjLU=
git.defalsify.org/vise.git v0.2.1-0.20241212145627-683015d4df80/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
@ -18,8 +18,8 @@ github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU= github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo= github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk= github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a h1:q/YH7nE2j8epNmFnTu0tU1vwtCxtQ6nH+d7hRVV5krU=
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0= github.com/grassrootseconomics/ussd-data-service v0.0.0-20241003123429-4904b4438a3a/go.mod h1:hdKaKwqiW6/kphK4j/BhmuRlZDLo1+DYo3gYw5O0siw=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y= github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=

View File

@ -55,9 +55,6 @@ func(f *BaseSessionHandler) Process(rqs RequestSession) (RequestSession, error)
} }
f.hn = f.hn.WithPersister(rqs.Storage.Persister) f.hn = f.hn.WithPersister(rqs.Storage.Persister)
defer func() {
f.hn.Exit()
}()
eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister) eni := f.GetEngine(rqs.Config, f.rs, rqs.Storage.Persister)
en, ok := eni.(*engine.DefaultEngine) en, ok := eni.(*engine.DefaultEngine)
if !ok { if !ok {

View File

@ -118,11 +118,6 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin) ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin) ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo) ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo)
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 return ussdHandlers, nil
} }

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/asm"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
@ -20,31 +21,26 @@ import (
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/common" "git.grassecon.net/urdt/ussd/common"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/models"
"git.grassecon.net/urdt/ussd/remote" "git.grassecon.net/urdt/ussd/remote"
"gopkg.in/leonelquinteros/gotext.v1" "gopkg.in/leonelquinteros/gotext.v1"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
var ( var (
logg = logging.NewVanilla().WithDomain("ussdmenuhandler") logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale") translationDir = path.Join(scriptDir, "locale")
okResponse *api.OKResponse
errResponse *api.ErrResponse
) )
// Define the regex patterns as constants // Define the regex patterns as constants
const ( const (
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
pinPattern = `^\d{4}$` pinPattern = `^\d{4}$`
) )
// isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin)
return match
}
// FlagManager handles centralized flag management // FlagManager handles centralized flag management
type FlagManager struct { type FlagManager struct {
parser *asm.FlagParser parser *asm.FlagParser
@ -77,7 +73,6 @@ type Handlers struct {
flagManager *asm.FlagParser flagManager *asm.FlagParser
accountService remote.AccountServiceInterface accountService remote.AccountServiceInterface
prefixDb storage.PrefixDb prefixDb storage.PrefixDb
profile *models.Profile
} }
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) (*Handlers, error) {
@ -87,10 +82,8 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
userDb := &common.UserDataStore{ userDb := &common.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
// Instantiate the SubPrefixDb with "vouchers" prefix
// Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix prefixDb := storage.NewSubPrefixDb(userdataStore, []byte("vouchers"))
prefix := common.ToBytes(db.DATATYPE_USERDATA)
prefixDb := storage.NewSubPrefixDb(userdataStore, prefix)
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
@ -98,11 +91,21 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
adminstore: adminstore, adminstore: adminstore,
accountService: accountService, accountService: accountService,
prefixDb: prefixDb, prefixDb: prefixDb,
profile: &models.Profile{Max: 6},
} }
return h, nil return h, nil
} }
// isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin)
return match
}
func isValidPhoneNumber(phonenumber string) bool {
match, _ := regexp.MatchString(phoneRegex, phonenumber)
return match
}
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers { func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
if h.pe != nil { if h.pe != nil {
panic("persister already set") panic("persister already set")
@ -117,9 +120,6 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca) logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
return r, nil return r, nil
} }
defer func() {
h.Exit()
}()
h.st = h.pe.GetState() h.st = h.pe.GetState()
h.ca = h.pe.GetMemory() h.ca = h.pe.GetMemory()
@ -139,16 +139,13 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca) logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
return r, fmt.Errorf("cannot get state and memory for handler") return r, fmt.Errorf("cannot get state and memory for handler")
} }
h.pe = nil
logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca) logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca)
return r, nil return r, nil
} }
func (h *Handlers) Exit() {
h.pe = nil
}
// SetLanguage sets the language across the menu // SetLanguage sets the language across the menu
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -157,8 +154,7 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
code := strings.Split(symbol, "_")[1] code := strings.Split(symbol, "_")[1]
if !utils.IsValidISO639(code) { if !utils.IsValidISO639(code) {
//Fallback to english instead? return res, nil
code = "eng"
} }
res.FlagSet = append(res.FlagSet, state.FLAG_LANG) res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = code res.Content = code
@ -216,7 +212,7 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
_, err = store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY) _, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist") logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
@ -416,10 +412,7 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
firstName := string(input) firstName := string(input)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
firstNameSet := h.st.MatchFlag(flag_firstname_set, true)
if allowUpdate { if allowUpdate {
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
@ -427,16 +420,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_firstname_set)
} else { } else {
if firstNameSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err) return res, err
return res, err
}
} else {
h.profile.InsertOrShift(0, firstName)
} }
} }
@ -456,9 +444,7 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
familyName := string(input) familyName := string(input)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
familyNameSet := h.st.MatchFlag(flag_familyname_set, true)
if allowUpdate { if allowUpdate {
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -467,16 +453,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_familyname_set)
} else { } else {
if familyNameSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err) return res, err
return res, err
}
} else {
h.profile.InsertOrShift(1, familyName)
} }
} }
@ -494,10 +475,7 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
yob := string(input) yob := string(input)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
yobSet := h.st.MatchFlag(flag_yob_set, true)
if allowUpdate { if allowUpdate {
temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -506,16 +484,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_yob_set)
} else { } else {
if yobSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err) return res, err
return res, err
}
} else {
h.profile.InsertOrShift(3, yob)
} }
} }
@ -534,9 +507,7 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
locationSet := h.st.MatchFlag(flag_location_set, true)
if allowUpdate { if allowUpdate {
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -545,17 +516,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_location_set)
} else { } else {
if locationSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err) return res, err
return res, err
}
res.FlagSet = append(res.FlagSet, flag_location_set)
} else {
h.profile.InsertOrShift(4, location)
} }
} }
@ -574,10 +539,7 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
gender := strings.Split(symbol, "_")[1] gender := strings.Split(symbol, "_")[1]
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
genderSet := h.st.MatchFlag(flag_gender_set, true)
if allowUpdate { if allowUpdate {
temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -586,16 +548,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_gender_set)
} else { } else {
if genderSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err) return res, err
return res, err
}
} else {
h.profile.InsertOrShift(2, gender)
} }
} }
@ -615,10 +572,7 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
store := h.userdataStore store := h.userdataStore
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") 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) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
offeringsSet := h.st.MatchFlag(flag_offerings_set, true)
if allowUpdate { if allowUpdate {
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE) temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
@ -627,16 +581,11 @@ 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) logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_offerings_set)
} else { } else {
if offeringsSet { err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings)) if err != nil {
if err != nil { logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err) return res, err
return res, err
}
} else {
h.profile.InsertOrShift(5, offerings)
} }
} }
@ -729,18 +678,6 @@ func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []by
return res, nil 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 // CheckAccountStatus queries the API using the TrackingId and sets flags
// based on the account status // based on the account status
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
@ -826,11 +763,12 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
return res, nil return res, nil
} }
if utils.IsValidYOb(date) { if len(date) == 4 {
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
} else { } else {
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
} }
return res, nil return res, nil
} }
@ -879,17 +817,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, err return res, err
} }
// Convert activeBal from []byte to float64 res.Content = l.Get("Balance: %s\n", fmt.Sprintf("%s %s", activeBal, activeSym))
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 return res, nil
} }
@ -949,7 +877,7 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
} }
blockedNumber := string(input) blockedNumber := string(input)
_, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY) _, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY)
if !common.IsValidPhoneNumber(blockedNumber) { if !isValidPhoneNumber(blockedNumber) {
res.FlagSet = append(res.FlagSet, flag_unregistered_number) res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil return res, nil
} }
@ -970,9 +898,10 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
return res, nil return res, nil
} }
// ValidateRecipient validates that the given input is valid. // ValidateRecipient validates that the given input is a valid phone number.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
var err error
store := h.userdataStore store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
@ -980,16 +909,13 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
recipient := string(input) recipient := string(input)
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
if recipient != "0" { if recipient != "0" {
recipientType, err := common.CheckRecipient(recipient) if !isValidPhoneNumber(recipient) {
if err != nil {
// Invalid recipient format (not a phone number, address, or valid alias format)
res.FlagSet = append(res.FlagSet, flag_invalid_recipient) res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
res.Content = recipient res.Content = recipient
@ -1003,61 +929,25 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, err return res, err
} }
switch recipientType { publicKey, err := store.ReadEntry(ctx, recipient, common.DATA_PUBLIC_KEY)
case "phone number": if err != nil {
// format the phone number if db.IsNotFound(err) {
formattedNumber, err := common.FormatPhoneNumber(recipient) logg.InfoCtxf(ctx, "Unregistered number")
if err != nil {
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err)
return res, err
}
// Check if the phone number is registered res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
publicKey, err := store.ReadEntry(ctx, formattedNumber, common.DATA_PUBLIC_KEY)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient)
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
res.Content = recipient
return res, nil
}
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Save the publicKey as the recipient
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
return res, err
}
case "address":
// Save the valid Ethereum address as the recipient
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", recipient, "error", err)
return res, err
}
case "alias":
// Call the API to validate and retrieve the address for the alias
r, aliasErr := h.accountService.CheckAliasAddress(ctx, recipient)
if aliasErr != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
res.Content = recipient res.Content = recipient
logg.ErrorCtxf(ctx, "failed on CheckAliasAddress", "error", aliasErr) return res, nil
return res, err
} }
// Alias validation succeeded, save the Ethereum address logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(r.Address)) return res, err
if err != nil { }
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", r.Address, "error", err)
return res, err err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
} if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
return res, nil
} }
} }
@ -1340,17 +1230,6 @@ func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input
var res resource.Result var res resource.Result
var profileInfo []byte var profileInfo []byte
var err error 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) sessionId, ok := ctx.Value("SessionId").(string)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
@ -1377,7 +1256,6 @@ 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) logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_firstname_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_FAMILY_NAME: case common.DATA_FAMILY_NAME:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@ -1389,7 +1267,6 @@ 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) logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_familyname_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_GENDER: case common.DATA_GENDER:
@ -1402,7 +1279,6 @@ 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) logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_gender_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_YOB: case common.DATA_YOB:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@ -1414,8 +1290,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) logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_yob_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_LOCATION: case common.DATA_LOCATION:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
if err != nil { if err != nil {
@ -1426,7 +1302,6 @@ 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) logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_location_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
case common.DATA_OFFERINGS: case common.DATA_OFFERINGS:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS) profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@ -1438,7 +1313,6 @@ 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) logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err)
return res, err return res, err
} }
res.FlagSet = append(res.FlagSet, flag_offerings_set)
res.Content = string(profileInfo) res.Content = string(profileInfo)
default: default:
break break
@ -1482,7 +1356,14 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)) offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
// Construct the full name // Construct the full name
name := utils.ConstructName(firstName, familyName, defaultValue) name := defaultValue
if familyName != defaultValue {
if firstName == defaultValue {
name = familyName
} else {
name = firstName + " " + familyName
}
}
// Calculate age from year of birth // Calculate age from year of birth
age := defaultValue age := defaultValue
@ -1621,50 +1502,18 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
// check the current active sym and update the data
activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if activeSym != nil {
activeSymStr := string(activeSym)
// Find the matching voucher data
var activeData *dataserviceapi.TokenHoldings
for _, voucher := range vouchersResp {
if voucher.TokenSymbol == activeSymStr {
activeData = &voucher
break
}
}
if activeData == nil {
logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr)
return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr)
}
// Scale down the balance
scaledBalance := common.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals)
// Update the balance field with the scaled value
activeData.Balance = scaledBalance
// Pass the matching voucher data to UpdateVoucherData
if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil {
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
return res, err
}
}
data := common.ProcessVouchers(vouchersResp) data := common.ProcessVouchers(vouchersResp)
// Store all voucher data // Store all voucher data
dataMap := map[common.DataTyp]string{ dataMap := map[string]string{
common.DATA_VOUCHER_SYMBOLS: data.Symbols, "sym": data.Symbols,
common.DATA_VOUCHER_BALANCES: data.Balances, "bal": data.Balances,
common.DATA_VOUCHER_DECIMALS: data.Decimals, "deci": data.Decimals,
common.DATA_VOUCHER_ADDRESSES: data.Addresses, "addr": data.Addresses,
} }
for key, value := range dataMap { for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)); err != nil { if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
return res, nil return res, nil
} }
} }
@ -1677,7 +1526,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
var res resource.Result var res resource.Result
// Read vouchers from the store // Read vouchers from the store
voucherData, err := h.prefixDb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS)) voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err) logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
return res, err return res, err
@ -1697,10 +1546,6 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, fmt.Errorf("missing session") 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") flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
inputStr := string(input) inputStr := string(input)
@ -1725,7 +1570,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
} }
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher) res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance) res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
return res, nil return res, nil
} }
@ -1781,228 +1626,10 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by
return res, nil return res, nil
} }
res.Content = fmt.Sprintf( tokenSymbol := voucherData.TokenSymbol
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation, tokenName := voucherData.TokenName
)
res.Content = fmt.Sprintf("%s %s", tokenSymbol, tokenName)
return res, nil return res, nil
} }
// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb
func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Fetch transactions from the API using the public key
transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey))
if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
return res, err
}
// Return if there are no transactions
if len(transactionsResp) == 0 {
res.FlagSet = append(res.FlagSet, flag_no_transfers)
return res, nil
}
data := common.ProcessTransfers(transactionsResp)
// Store all transaction data
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(common.ToBytes(key)), []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
return res, err
}
}
res.FlagReset = append(res.FlagReset, flag_no_transfers)
return res, nil
}
// GetTransactionsList fetches the list of transactions 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 {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Read transactions from the store and format them
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, 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, 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, common.ToBytes(common.DATA_TX_DATES))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
return res, err
}
// Parse the data
senders := strings.Split(string(TransactionSenders), "\n")
syms := strings.Split(string(TransactionSyms), "\n")
values := strings.Split(string(TransactionValues), "\n")
dates := strings.Split(string(TransactionDates), "\n")
var formattedTransactions []string
for i := 0; i < len(senders); i++ {
sender := strings.TrimSpace(senders[i])
sym := strings.TrimSpace(syms[i])
value := strings.TrimSpace(values[i])
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
status := "received"
if sender == string(publicKey) {
status = "sent"
}
formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date))
}
res.Content = strings.Join(formattedTransactions, "\n")
return res, nil
}
// ViewTransactionStatement retrieves the transaction statement
// and displays it to the user
func (h *Handlers) ViewTransactionStatement(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 {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement")
inputStr := string(input)
if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" {
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
return res, nil
}
// Convert input string to integer
index, err := strconv.Atoi(strings.TrimSpace(inputStr))
if err != nil {
return res, fmt.Errorf("invalid input: must be a number between 1 and 10")
}
if index < 1 || index > 10 {
return res, fmt.Errorf("invalid input: index must be between 1 and 10")
}
statement, err := common.GetTransferData(ctx, h.prefixDb, string(publicKey), index)
if err != nil {
return res, fmt.Errorf("failed to retrieve transfer data: %v", err)
}
if statement == "" {
res.FlagSet = append(res.FlagSet, flag_incorrect_statement)
return res, nil
}
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
res.Content = statement
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
}

View File

@ -22,7 +22,6 @@ import (
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
visedb "git.defalsify.org/vise.git/db"
memdb "git.defalsify.org/vise.git/db/mem" memdb "git.defalsify.org/vise.git/db/mem"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
@ -57,8 +56,7 @@ func InitializeTestSubPrefixDb(t *testing.T, ctx context.Context) *storage.SubPr
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
prefix := common.ToBytes(visedb.DATATYPE_USERDATA) spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
spdb := storage.NewSubPrefixDb(db, prefix)
return spdb return spdb
} }
@ -181,14 +179,11 @@ func TestSaveFirstname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_firstname_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(128) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
firstName := "John" firstName := "John"
@ -196,8 +191,6 @@ func TestSaveFirstname(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@ -210,7 +203,7 @@ func TestSaveFirstname(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FIRST_NAME entry has been updated with the temporary value // Verify that the DATA_FIRST_NAME entry has been updated with the temporary value
storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME) storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
@ -225,16 +218,11 @@ func TestSaveFamilyname(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_firstname_set, _ := fm.GetFlag("flag_familyname_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(128) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
expectedResult.FlagSet = []uint32{flag_firstname_set}
// Define test data // Define test data
familyName := "Doeee" familyName := "Doeee"
@ -254,7 +242,7 @@ func TestSaveFamilyname(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value // Verify that the DATA_FAMILY_NAME entry has been updated with the temporary value
storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME) storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
@ -269,14 +257,11 @@ func TestSaveYoB(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_yob_set, _ := fm.GetFlag("flag_yob_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(108) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
yob := "1980" yob := "1980"
@ -284,8 +269,6 @@ func TestSaveYoB(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_yob_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@ -298,7 +281,7 @@ func TestSaveYoB(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_YOB entry has been updated with the temporary value // Verify that the DATA_YOB entry has been updated with the temporary value
storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB) storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
@ -313,14 +296,11 @@ func TestSaveLocation(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_location_set, _ := fm.GetFlag("flag_location_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(108) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
location := "Kilifi" location := "Kilifi"
@ -328,8 +308,6 @@ func TestSaveLocation(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_location_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@ -342,7 +320,7 @@ func TestSaveLocation(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_LOCATION entry has been updated with the temporary value // Verify that the DATA_LOCATION entry has been updated with the temporary value
storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION) storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
@ -357,14 +335,11 @@ func TestSaveOfferings(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_offerings_set, _ := fm.GetFlag("flag_offerings_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(108) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
expectedResult := resource.Result{}
// Define test data // Define test data
offerings := "Bananas" offerings := "Bananas"
@ -372,8 +347,6 @@ func TestSaveOfferings(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expectedResult.FlagSet = []uint32{flag_offerings_set}
// Create the Handlers instance with the mock store // Create the Handlers instance with the mock store
h := &Handlers{ h := &Handlers{
userdataStore: store, userdataStore: store,
@ -386,7 +359,7 @@ func TestSaveOfferings(t *testing.T) {
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_OFFERINGS entry has been updated with the temporary value // Verify that the DATA_OFFERINGS entry has been updated with the temporary value
storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS) storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
@ -401,10 +374,9 @@ func TestSaveGender(t *testing.T) {
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
flag_gender_set, _ := fm.GetFlag("flag_gender_set")
// Set the flag in the State // Set the flag in the State
mockState := state.NewState(108) mockState := state.NewState(16)
mockState.SetFlag(flag_allow_update) mockState.SetFlag(flag_allow_update)
// Define test cases // Define test cases
@ -448,16 +420,12 @@ func TestSaveGender(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
expectedResult := resource.Result{}
// Call the method // Call the method
res, err := h.SaveGender(ctx, "save_gender", tt.input) res, err := h.SaveGender(ctx, "save_gender", tt.input)
expectedResult.FlagSet = []uint32{flag_gender_set}
// Assert results // Assert results
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedResult, res) assert.Equal(t, resource.Result{}, res)
// Verify that the DATA_GENDER entry has been updated with the temporary value // Verify that the DATA_GENDER entry has been updated with the temporary value
storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER) storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
@ -1530,10 +1498,10 @@ func TestValidateRecipient(t *testing.T) {
}{ }{
{ {
name: "Test with invalid recepient", name: "Test with invalid recepient",
input: []byte("7?1234"), input: []byte("9234adf5"),
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_recipient}, FlagSet: []uint32{flag_invalid_recipient},
Content: "7?1234", Content: "9234adf5",
}, },
}, },
{ {
@ -1549,40 +1517,22 @@ func TestValidateRecipient(t *testing.T) {
input: []byte("0711223344"), input: []byte("0711223344"),
expectedResult: resource.Result{}, 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 // store a public key for the valid recipient
err = store.WriteEntry(ctx, "+254711223344", common.DATA_PUBLIC_KEY, []byte(publicKey)) err = store.WriteEntry(ctx, "0711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
// Create the Handlers instance // Create the Handlers instance
h := &Handlers{ h := &Handlers{
flagManager: fm.parser, flagManager: fm.parser,
userdataStore: store, userdataStore: store,
accountService: mockAccountService,
} }
aliasResponse := &dataserviceapi.AliasAddress{
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
// Call the method // Call the method
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input) res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
@ -1614,7 +1564,7 @@ func TestCheckBalance(t *testing.T) {
publicKey: "0X98765432109", publicKey: "0X98765432109",
activeSym: "ETH", activeSym: "ETH",
activeBal: "1.5", activeBal: "1.5",
expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"}, expectedResult: resource.Result{Content: "Balance: 1.5 ETH\n"},
expectError: false, expectError: false,
}, },
} }
@ -1969,7 +1919,7 @@ func TestCheckVouchers(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Read voucher sym data from the store // Read voucher sym data from the store
voucherData, err := spdb.Get(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS)) voucherData, err := spdb.Get(ctx, []byte("sym"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1993,7 +1943,7 @@ func TestGetVoucherList(t *testing.T) {
expectedSym := []byte("1:SRF\n2:MILO") expectedSym := []byte("1:SRF\n2:MILO")
// Put voucher sym data from the store // Put voucher sym data from the store
err := spdb.Put(ctx, common.ToBytes(common.DATA_VOUCHER_SYMBOLS), expectedSym) err := spdb.Put(ctx, []byte("sym"), expectedSym)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -2023,16 +1973,16 @@ func TestViewVoucher(t *testing.T) {
} }
// Define mock voucher data // Define mock voucher data
mockData := map[common.DataTyp][]byte{ mockData := map[string][]byte{
common.DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"), "sym": []byte("1:SRF\n2:MILO"),
common.DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"), "bal": []byte("1:100\n2:200"),
common.DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"), "deci": []byte("1:6\n2:4"),
common.DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"), "addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
} }
// Put the data // Put the data
for key, value := range mockData { for key, value := range mockData {
err = spdb.Put(ctx, []byte(common.ToBytes(key)), []byte(value)) err = spdb.Put(ctx, []byte(key), []byte(value))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -2040,7 +1990,7 @@ func TestViewVoucher(t *testing.T) {
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1")) res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100") assert.Equal(t, res.Content, "SRF\n100")
} }
func TestSetVoucher(t *testing.T) { func TestSetVoucher(t *testing.T) {
@ -2074,42 +2024,3 @@ func TestSetVoucher(t *testing.T) {
assert.Equal(t, string(tempData.TokenSymbol), res.Content) assert.Equal(t, string(tempData.TokenSymbol), res.Content)
} }
func TestGetVoucherDetails(t *testing.T) {
ctx, store := InitializeTestStore(t)
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
mockAccountService := new(mocks.MockAccountService)
sessionId := "session123"
ctx = context.WithValue(ctx, "SessionId", sessionId)
expectedResult := resource.Result{}
tokA_AAddress := "0x0000000000000000000000000000000000000000"
h := &Handlers{
userdataStore: store,
flagManager: fm.parser,
accountService: mockAccountService,
}
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress))
if err != nil {
t.Fatal(err)
}
tokenDetails := &models.VoucherDataResult{
TokenName: "Token A",
TokenSymbol: "TOKA",
TokenLocation: "Kilifi,Kenya",
TokenCommodity: "Farming",
}
expectedResult.Content = fmt.Sprintf(
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation,
)
mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil)
res, err := h.GetVoucherDetails(ctx, "SessionId", []byte(""))
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
}

View File

@ -6,6 +6,10 @@ import (
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
) )
const (
DATATYPE_USERSUB = 64
)
// PrefixDb interface abstracts the database operations. // PrefixDb interface abstracts the database operations.
type PrefixDb interface { type PrefixDb interface {
Get(ctx context.Context, key []byte) ([]byte, error) Get(ctx context.Context, key []byte) ([]byte, error)
@ -31,13 +35,13 @@ func (s *SubPrefixDb) toKey(k []byte) []byte {
} }
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) { func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
s.store.SetPrefix(db.DATATYPE_USERDATA) s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key) key = s.toKey(key)
return s.store.Get(ctx, key) return s.store.Get(ctx, key)
} }
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error { func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
s.store.SetPrefix(db.DATATYPE_USERDATA) s.store.SetPrefix(DATATYPE_USERSUB)
key = s.toKey(key) key = s.toKey(key)
return s.store.Put(ctx, key, val) return s.store.Put(ctx, key, val)
} }

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/lang" "git.defalsify.org/vise.git/lang"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
) )
var ( var (
@ -114,9 +114,3 @@ func(tdb *ThreadGdbmDb) Close() error {
tdb.db = nil tdb.db = nil
return err return err
} }
func(tdb *ThreadGdbmDb) Dump(ctx context.Context, key []byte) (*db.Dumper, error) {
tdb.reserve()
defer tdb.release()
return tdb.db.Dump(ctx, key)
}

View File

@ -41,13 +41,10 @@ func buildConnStr() string {
dbName := initializers.GetEnv("DB_NAME", "") dbName := initializers.GetEnv("DB_NAME", "")
port := initializers.GetEnv("DB_PORT", "5432") port := initializers.GetEnv("DB_PORT", "5432")
connString := fmt.Sprintf( return fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s", "postgres://%s:%s@%s:%s/%s",
user, password, host, port, dbName, user, password, host, port, dbName,
) )
logg.Debugf("pg conn string", "conn", connString)
return connString
} }
func NewMenuStorageService(dbDir string, resourceDir string) *MenuStorageService { func NewMenuStorageService(dbDir string, resourceDir string) *MenuStorageService {

View File

@ -47,8 +47,3 @@ func (m *MockAccountService) TokenTransfer(ctx context.Context, amount, from, to
args := m.Called() args := m.Called()
return args.Get(0).(*models.TokenTransferResponse), args.Error(1) return args.Get(0).(*models.TokenTransferResponse), args.Error(1)
} }
func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) {
args := m.Called(alias)
return args.Get(0).(*dataserviceapi.AliasAddress), args.Error(1)
}

View File

@ -33,8 +33,8 @@ func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey
} }
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) { func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return []dataserviceapi.TokenHoldings{ return []dataserviceapi.TokenHoldings {
dataserviceapi.TokenHoldings{ dataserviceapi.TokenHoldings {
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
TokenSymbol: "SRF", TokenSymbol: "SRF",
TokenDecimals: "6", TokenDecimals: "6",
@ -56,7 +56,3 @@ func (tas *TestAccountService) TokenTransfer(ctx context.Context, amount, from,
TrackingId: "e034d147-747d-42ea-928d-b5a7cb3426af", TrackingId: "e034d147-747d-42ea-928d-b5a7cb3426af",
}, nil }, nil
} }
func (m TestAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) {
return &dataserviceapi.AliasAddress{}, nil
}

View File

@ -2,8 +2,8 @@
package testtag package testtag
import "git.grassecon.net/urdt/ussd/remote" import "git.grassecon.net/urdt/ussd/internal/handlers/server"
var ( var (
AccountService remote.AccountServiceInterface AccountService server.AccountServiceInterface
) )

View File

@ -1,9 +1,6 @@
package utils package utils
import ( import "time"
"strconv"
"time"
)
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy // 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. // It adjusts for cases where the current date is before the birthday in the current year.
@ -28,29 +25,11 @@ func CalculateAge(birthdate, today time.Time) int {
// It subtracts the YOB from the current year to determine the age. // It subtracts the YOB from the current year to determine the age.
// //
// Parameters: // Parameters:
// // yob: The year of birth as an integer.
// yob: The year of birth as an integer.
// //
// Returns: // Returns:
// // The calculated age as an integer.
// The calculated age as an integer.
func CalculateAgeWithYOB(yob int) int { func CalculateAgeWithYOB(yob int) int {
currentYear := time.Now().Year() currentYear := time.Now().Year()
return currentYear - yob 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
}
}

View File

@ -1,17 +0,0 @@
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
}

View File

@ -54,7 +54,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@ -62,7 +62,7 @@
}, },
{ {
"input": "1234", "input": "1234",
"expectedContent": "Select language:\n0:English\n1:Kiswahili" "expectedContent": "Select language:\n0:english\n1:kiswahili"
}, },
{ {
"input": "0", "input": "0",
@ -95,7 +95,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@ -141,7 +141,7 @@
}, },
{ {
"input": "1235", "input": "1235",
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit" "expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
}, },
{ {
"input": "1", "input": "1",
@ -167,7 +167,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_all_account_details_starting_from_firstname", "name": "menu_my_account_edit_firstname",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -187,26 +187,6 @@
}, },
{ {
"input": "foo", "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:" "expectedContent": "Please enter your PIN:"
}, },
{ {
@ -217,6 +197,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -224,7 +208,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_familyname_when_all_account__details_have_been_set", "name": "menu_my_account_edit_familyname",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -254,6 +238,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -262,7 +250,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_gender_when_all_account__details_have_been_set", "name": "menu_my_account_edit_gender",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -292,6 +280,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -299,7 +291,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_yob_when_all_account__details_have_been_set", "name": "menu_my_account_edit_yob",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -329,6 +321,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -336,7 +332,7 @@
] ]
}, },
{ {
"name": "menu_my_account_edit_location_when_all_account_details_have_been_set", "name": "menu_my_account_edit_location",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -366,6 +362,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "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_when_all_account__details_have_been_set", "name": "menu_my_account_edit_offerings",
"steps": [ "steps": [
{ {
"input": "", "input": "",
@ -403,6 +403,10 @@
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"
@ -430,12 +434,16 @@
}, },
{ {
"input": "1234", "input": "1234",
"expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 84\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back" "expectedContent": "My profile:\nName: foo bar\nGender: male\nAge: 79\nLocation: Kilifi\nYou provide: Bananas\n\n0:Back"
}, },
{ {
"input": "0", "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" "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", "input": "0",
"expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit" "expectedContent": "{balance}\n\n1:Send\n2:My Vouchers\n3:My Account\n4:Help\n9:Quit"

View File

@ -3,7 +3,6 @@ package menutraversaltest
import ( import (
"bytes" "bytes"
"context" "context"
"flag"
"log" "log"
"math/rand" "math/rand"
"os" "os"
@ -16,15 +15,14 @@ import (
) )
var ( var (
testData = driver.ReadData() testData = driver.ReadData()
testStore = ".test_state" testStore = ".test_state"
sessionID string groupTestFile = "group_test.json"
src = rand.NewSource(42) sessionID string
g = rand.New(src) 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 { func GenerateSessionId() string {
uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g)) uu := uuid.NewGenWithOptions(uuid.WithRandomReader(g))
v, err := uu.NewV4() v, err := uu.NewV4()
@ -339,7 +337,7 @@ func TestMainMenuSend(t *testing.T) {
} }
func TestGroups(t *testing.T) { func TestGroups(t *testing.T) {
groups, err := driver.LoadTestGroups(*groupTestFile) groups, err := driver.LoadTestGroups(groupTestFile)
if err != nil { if err != nil {
log.Fatalf("Failed to load test groups: %v", err) log.Fatalf("Failed to load test groups: %v", err)
} }

View File

@ -1,68 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,61 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,55 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,46 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,42 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,50 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -1,70 +0,0 @@
{
"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"
}
]
}
]
}

View File

@ -7,11 +7,11 @@
"steps": [ "steps": [
{ {
"input": "", "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", "input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No" "expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
}, },
{ {
"input": "0", "input": "0",
@ -40,11 +40,11 @@
"steps": [ "steps": [
{ {
"input": "", "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", "input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No" "expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
}, },
{ {
"input": "1", "input": "1",
@ -61,7 +61,7 @@
}, },
{ {
"input": "1", "input": "1",
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back" "expectedContent": "Enter recipient's phone number:\n0:Back"
}, },
{ {
"input": "000", "input": "000",
@ -69,7 +69,7 @@
}, },
{ {
"input": "1", "input": "1",
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back" "expectedContent": "Enter recipient's phone number:\n0:Back"
}, },
{ {
"input": "0712345678", "input": "0712345678",

View File

@ -1,18 +0,0 @@
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
}
}

View File

@ -1,10 +1,8 @@
package models package models
type VoucherDataResult struct { type VoucherDataResult struct {
TokenName string `json:"tokenName"` TokenName string `json:"tokenName"`
TokenSymbol string `json:"tokenSymbol"` TokenSymbol string `json:"tokenSymbol"`
TokenDecimals int `json:"tokenDecimals"` TokenDecimals int `json:"tokenDecimals"`
SinkAddress string `json:"sinkAddress"` SinkAddress string `json:"sinkAddress"`
TokenCommodity string `json:"tokenCommodity"`
TokenLocation string `json:"tokenLocation"`
} }

View File

@ -24,7 +24,6 @@ type AccountServiceInterface interface {
FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error)
VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error)
TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error)
CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error)
} }
type AccountService struct { type AccountService struct {
@ -210,26 +209,6 @@ func (as *AccountService) TokenTransfer(ctx context.Context, amount, from, to, t
return &r, nil return &r, nil
} }
// CheckAliasAddress retrieves the address of an alias from the API endpoint.
// Parameters:
// - alias: The alias of the user.
func (as *AccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) {
var r dataserviceapi.AliasAddress
ep, err := url.JoinPath(config.CheckAliasURL, alias)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
return &r, err
}
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) { func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse var okResponse api.OKResponse
var errResponse api.ErrResponse var errResponse api.ErrResponse

View File

@ -1 +0,0 @@
Tatizo la kimtambo limetokea,tafadhali jaribu tena baadaye.

View File

@ -1 +0,0 @@
Please enter your PIN to view statement:

View File

@ -1,12 +0,0 @@
LOAD check_transactions 0
RELOAD check_transactions
CATCH no_transfers flag_no_transfers 1
LOAD authorize_account 6
MOUT back 0
MOUT quit 9
HALT
RELOAD authorize_account
CATCH incorrect_pin flag_incorrect_pin 1
INCMP _ 0
INCMP quit 9
INCMP transactions *

View File

@ -1 +0,0 @@
Tafadhali weka PIN yako kuona taarifa ya matumizi:

View File

@ -2,17 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_familyname flag_allow_update 1 CATCH update_familyname flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
RELOAD set_back LOAD save_familyname 0
CATCH _ flag_back_set 1
LOAD save_familyname 64
RELOAD save_familyname RELOAD save_familyname
CATCH pin_entry flag_familyname_set 1 INCMP _ 0
CATCH select_gender flag_gender_set 0 INCMP pin_entry *
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 *

View File

@ -5,14 +5,7 @@ RELOAD get_current_profile_info
MAP get_current_profile_info MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
RELOAD set_back LOAD save_firstname 0
CATCH _ flag_back_set 1
LOAD save_firstname 128
RELOAD save_firstname RELOAD save_firstname
CATCH pin_entry flag_firstname_set 1 INCMP _ 0
CATCH edit_family_name flag_familyname_set 0 INCMP pin_entry *
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

View File

@ -2,14 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_location flag_allow_update 1 CATCH update_location flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
LOAD save_location 16
MOUT back 0 MOUT back 0
HALT HALT
RELOAD set_back LOAD save_location 0
CATCH _ flag_back_set 1
RELOAD save_location RELOAD save_location
INCMP _ 0 INCMP _ 0
CATCH pin_entry flag_location_set 1 INCMP pin_entry *
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_location_set 0
INCMP edit_offerings *

View File

@ -2,13 +2,9 @@ CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_offerings flag_allow_update 1 CATCH update_offerings flag_allow_update 1
LOAD get_current_profile_info 0 LOAD get_current_profile_info 0
RELOAD get_current_profile_info RELOAD get_current_profile_info
LOAD save_offerings 8 LOAD save_offerings 0
MOUT back 0 MOUT back 0
HALT HALT
RELOAD set_back
CATCH _ flag_back_set 1
RELOAD save_offerings RELOAD save_offerings
INCMP _ 0 INCMP _ 0
CATCH pin_entry flag_offerings_set 1 INCMP pin_entry *
CATCH pin_entry flag_offerings_set 0
INCMP update_profile_items *

View File

@ -11,8 +11,7 @@ MOUT edit_offerings 6
MOUT view 7 MOUT view 7
MOUT back 0 MOUT back 0
HALT HALT
LOAD set_back 6 INCMP my_account 0
INCMP ^ 0
INCMP edit_first_name 1 INCMP edit_first_name 1
INCMP edit_family_name 2 INCMP edit_family_name 2
INCMP select_gender 3 INCMP select_gender 3

View File

@ -5,15 +5,10 @@ RELOAD get_current_profile_info
MAP get_current_profile_info MAP get_current_profile_info
MOUT back 0 MOUT back 0
HALT HALT
RELOAD set_back
CATCH _ flag_back_set 1
LOAD verify_yob 6 LOAD verify_yob 6
RELOAD verify_yob RELOAD verify_yob
CATCH incorrect_date_format flag_incorrect_date_format 1 CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 32 LOAD save_yob 0
RELOAD save_yob RELOAD save_yob
CATCH pin_entry flag_yob_set 1 INCMP _ 0
CATCH edit_location flag_location_set 0 INCMP pin_entry *
CATCH edit_offerings flag_offerings_set 0
CATCH pin_entry flag_yob_set 0
INCMP edit_location *

View File

@ -1 +0,0 @@
English

View File

@ -1 +1 @@
Incorrect PIN Incorrect pin

View File

@ -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.

View File

@ -1 +0,0 @@
Kiswahili

View File

@ -13,7 +13,7 @@ msgstr "Kwa usaidizi zaidi,piga: 0757628885"
msgid "Balance: %s\n" msgid "Balance: %s\n"
msgstr "Salio: %s\n" msgstr "Salio: %s\n"
msgid "Your invite request for %s to Sarafu Network failed. Please try again later." msid "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." 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." msgid "Your invitation to %s to join Sarafu Network has been sent."
@ -23,7 +23,4 @@ msgid "Your request failed. Please try again later."
msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye." msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye."
msgid "Community Balance: 0.00" msgid "Community Balance: 0.00"
msgstr "Salio la Kikundi: 0.00" msgid "Salio la Kikundi: 0.00"
msgid "Symbol: %s\nBalance: %s"
msgstr "Sarafu: %s\nSalio: %s"

View File

@ -1,10 +1,10 @@
LOAD set_default_voucher 8 LOAD set_default_voucher 8
RELOAD set_default_voucher RELOAD set_default_voucher
LOAD check_balance 64
RELOAD check_balance
LOAD check_vouchers 10 LOAD check_vouchers 10
RELOAD check_vouchers RELOAD check_vouchers
LOAD check_balance 128 CATCH api_failure flag_api_call_error 1
RELOAD check_balance
CATCH api_failure flag_api_call_error 1
MAP check_balance MAP check_balance
MOUT send 1 MOUT send 1
MOUT vouchers 2 MOUT vouchers 2

View File

@ -11,6 +11,5 @@ INCMP main 0
INCMP edit_profile 1 INCMP edit_profile 1
INCMP change_language 2 INCMP change_language 2
INCMP balances 3 INCMP balances 3
INCMP check_statement 4
INCMP pin_management 5 INCMP pin_management 5
INCMP address 6 INCMP address 6

View File

@ -1 +1 @@
{{.check_balance}} Salio lako ni: 0.00 SRF

View File

@ -1 +0,0 @@
Next

View File

@ -1 +0,0 @@
Mbele

View File

@ -1 +1 @@
No no

View File

@ -1 +1 @@
La la

View File

@ -1 +0,0 @@
No transfers history

View File

@ -1,5 +0,0 @@
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9

View File

@ -1 +0,0 @@
Hakuna historia kwa akaunti yako

View File

@ -19,12 +19,3 @@ flag,flag_api_call_error,25,this is set when communication to an external servic
flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher
flag,flag_admin_privilege,27,this is set when a user has admin privileges. 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_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 flag flag_language_set 8 checks whether the user has set their prefered language
19 flag flag_no_active_voucher 26 this is set when a user does not have an active voucher
20 flag flag_admin_privilege 27 this is set when a user has admin privileges.
21 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

View File

@ -1 +0,0 @@
Prev

View File

@ -1 +0,0 @@
Nyuma

View File

@ -1,5 +1,3 @@
LOAD update_all_profile_items 0
RELOAD update_all_profile_items
MOUT back 0 MOUT back 0
MOUT quit 9 MOUT quit 9
HALT HALT

View File

@ -1 +1 @@
Enter recipient's phone number/address/alias: Enter recipient's phone number:

View File

@ -1,4 +1,3 @@
LOAD set_language 6 LOAD set_language 6
RELOAD set_language
CATCH terms flag_account_created 0 CATCH terms flag_account_created 0
MOVE language_changed MOVE language_changed

View File

@ -1,10 +1,4 @@
LOAD save_gender 32 LOAD save_gender 0
RELOAD save_gender
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1 CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1 MOVE pin_entry
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

View File

@ -1,10 +1,4 @@
LOAD save_gender 16 LOAD save_gender 0
RELOAD save_gender
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1 CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1 MOVE pin_entry
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

View File

@ -1,4 +1,3 @@
LOAD set_language 6 LOAD set_language 6
RELOAD set_language
CATCH terms flag_account_created 0 CATCH terms flag_account_created 0
MOVE language_changed MOVE language_changed

View File

@ -1,10 +1,4 @@
LOAD save_gender 8 LOAD save_gender 0
RELOAD save_gender
CATCH incorrect_pin flag_incorrect_pin 1 CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_gender flag_allow_update 1 CATCH update_gender flag_allow_update 1
CATCH pin_entry flag_gender_set 1 MOVE pin_entry
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

View File

@ -1 +0,0 @@
{{.get_transactions}}

View File

@ -1,15 +0,0 @@
LOAD get_transactions 0
MAP get_transactions
MOUT back 0
MOUT quit 99
MNEXT next 11
MPREV prev 22
HALT
LOAD view_statement 0
RELOAD view_statement
CATCH . flag_incorrect_statement 1
INCMP ^ 0
INCMP quit 99
INCMP > 11
INCMP < 22
INCMP view_statement *

View File

@ -1 +0,0 @@
{{.get_transactions}}

View File

@ -1,3 +0,0 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
MOVE pin_entry

View File

@ -1 +0,0 @@
{{.view_statement}}

View File

@ -1,10 +0,0 @@
MAP view_statement
MOUT back 0
MOUT quit 9
MNEXT next 11
MPREV prev 22
HALT
INCMP _ 0
INCMP quit 9
INCMP > 11
INCMP < 22

View File

@ -1 +1 @@
Yes yes

View File

@ -1 +1 @@
Ndio ndio