Merge pull request 'lash/export-to-term' (#157) from lash/export-to-term into master

Reviewed-on: urdt/ussd#157
This commit is contained in:
lash 2024-11-04 13:54:58 +01:00
commit e2b28a31b2
56 changed files with 1048 additions and 593 deletions

View File

@ -19,9 +19,9 @@ import (
"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"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@ -131,7 +131,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {
@ -139,7 +139,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@ -16,8 +16,8 @@ import (
"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"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@ -104,9 +104,9 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {

View File

@ -18,9 +18,9 @@ import (
"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"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
httpserver "git.grassecon.net/urdt/ussd/internal/http" httpserver "git.grassecon.net/urdt/ussd/internal/http"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@ -92,14 +92,15 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdataStore) lhs.SetDataStore(&userdataStore)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{}
accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@ -13,8 +13,8 @@ import (
"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"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@ -88,7 +88,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userdatastore) lhs.SetDataStore(&userdatastore)
lhs.SetPersister(pe) lhs.SetPersister(pe)
@ -97,7 +97,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
accountService := server.AccountService{} accountService := remote.AccountService{}
hl, err := lhs.GetHandler(&accountService) hl, err := lhs.GetHandler(&accountService)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, err.Error()) fmt.Fprintf(os.Stderr, err.Error())

View File

@ -1,7 +1,9 @@
package utils package common
import ( import (
"encoding/binary" "encoding/binary"
"git.defalsify.org/vise.git/logging"
) )
type DataTyp uint16 type DataTyp uint16
@ -23,13 +25,17 @@ const (
DATA_RECIPIENT DATA_RECIPIENT
DATA_AMOUNT DATA_AMOUNT
DATA_TEMPORARY_VALUE DATA_TEMPORARY_VALUE
DATA_VOUCHER_LIST
DATA_ACTIVE_SYM DATA_ACTIVE_SYM
DATA_ACTIVE_BAL DATA_ACTIVE_BAL
DATA_BLOCKED_NUMBER
DATA_PUBLIC_KEY_REVERSE DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS DATA_ACTIVE_ADDRESS
DATA_TRANSACTIONS
)
var (
logg = logging.NewVanilla().WithDomain("urdt-common")
) )
func typToBytes(typ DataTyp) []byte { func typToBytes(typ DataTyp) []byte {

View File

@ -2,6 +2,7 @@ package common
import ( import (
"encoding/hex" "encoding/hex"
"strings"
) )
func NormalizeHex(s string) (string, error) { func NormalizeHex(s string) (string, error) {
@ -16,3 +17,15 @@ func NormalizeHex(s string) (string, error) {
} }
return hex.EncodeToString(r), nil return hex.EncodeToString(r), nil
} }
func IsSameHex(left string, right string) bool {
bl, err := NormalizeHex(left)
if err != nil {
return false
}
br, err := NormalizeHex(left)
if err != nil {
return false
}
return strings.Compare(bl, br) == 0
}

52
common/storage.go Normal file
View File

@ -0,0 +1,52 @@
package common
import (
"context"
"errors"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/persist"
"git.grassecon.net/urdt/ussd/internal/storage"
)
func StoreToDb(store *UserDataStore) db.Db {
return store.Db
}
func StoreToPrefixDb(store *UserDataStore, pfx []byte) storage.PrefixDb {
return storage.NewSubPrefixDb(store.Db, pfx)
}
type StorageServices interface {
GetPersister(ctx context.Context) (*persist.Persister, error)
GetUserdataDb(ctx context.Context) (db.Db, error)
GetResource(ctx context.Context) (resource.Resource, error)
EnsureDbDir() error
}
type StorageService struct {
svc *storage.MenuStorageService
}
func NewStorageService(dbDir string) *StorageService {
return &StorageService{
svc: storage.NewMenuStorageService(dbDir, ""),
}
}
func(ss *StorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
return ss.svc.GetPersister(ctx)
}
func(ss *StorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
return ss.svc.GetUserdataDb(ctx)
}
func(ss *StorageService) GetResource(ctx context.Context) (resource.Resource, error) {
return nil, errors.New("not implemented")
}
func(ss *StorageService) EnsureDbDir() error {
return ss.svc.EnsureDbDir()
}

View File

@ -1,4 +1,4 @@
package utils package common
import ( import (
"context" "context"
@ -16,7 +16,7 @@ type UserDataStore struct {
db.Db db.Db
} }
// ReadEntry retrieves an entry from the store based on the provided parameters. // ReadEntry retrieves an entry to the userdata store.
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)
@ -24,6 +24,8 @@ func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ
return store.Get(ctx, k) return store.Get(ctx, k)
} }
// WriteEntry adds an entry to the userdata store.
// BUG: this uses sessionId twice
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)

View File

@ -1,4 +1,4 @@
package utils package common
import ( import (
"context" "context"
@ -37,6 +37,15 @@ func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
return data return data
} }
//func StoreVouchers(db storage.PrefixDb, data VoucherMetadata) {
// value, err := db.Put(ctx, []byte(key))
// if err != nil {
// return nil, fmt.Errorf("failed to get %s: %v", key, err)
// }
// data[key] = string(value)
// }
//}
// 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 := []string{"sym", "bal", "deci", "addr"} keys := []string{"sym", "bal", "deci", "addr"}
@ -75,6 +84,7 @@ func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol,
decList := strings.Split(decimals, "\n") decList := strings.Split(decimals, "\n")
addrList := strings.Split(addresses, "\n") addrList := strings.Split(addresses, "\n")
logg.Tracef("found" , "symlist", symList, "syms", symbols, "input", input)
for i, sym := range symList { for i, sym := range symList {
parts := strings.SplitN(sym, ":", 2) parts := strings.SplitN(sym, ":", 2)
@ -125,8 +135,9 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str
return data, nil return data, nil
} }
// UpdateVoucherData sets the active voucher data in the DataStore. // UpdateVoucherData sets the active voucher data and clears the temporary 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)
// Active voucher data entries // Active voucher data entries
activeEntries := map[DataTyp][]byte{ activeEntries := map[DataTyp][]byte{
DATA_ACTIVE_SYM: []byte(data.TokenSymbol), DATA_ACTIVE_SYM: []byte(data.TokenSymbol),

View File

@ -1,14 +1,14 @@
package utils package common
import ( import (
"context" "context"
"fmt" "fmt"
"testing" "testing"
"git.grassecon.net/urdt/ussd/internal/storage"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"git.grassecon.net/urdt/ussd/internal/storage"
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"
) )
@ -132,7 +132,6 @@ func TestStoreTemporaryVoucher(t *testing.T) {
storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE) storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE) require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE)
} }
func TestGetTemporaryVoucherData(t *testing.T) { func TestGetTemporaryVoucherData(t *testing.T) {

View File

@ -1,18 +1,70 @@
package config package config
import "git.grassecon.net/urdt/ussd/initializers" import (
"net/url"
"git.grassecon.net/urdt/ussd/initializers"
)
const (
createAccountPath = "/api/v2/account/create"
trackStatusPath = "/api/track"
balancePathPrefix = "/api/account"
trackPath = "/api/v2/account/status"
voucherHoldingsPathPrefix = "/api/v1/holdings"
voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token"
)
var (
custodialURLBase string
dataURLBase string
CustodialAPIKey string
DataAPIKey string
)
var ( var (
CreateAccountURL string CreateAccountURL string
TrackStatusURL string TrackStatusURL string
BalanceURL string BalanceURL string
TrackURL string TrackURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
) )
// LoadConfig initializes the configuration values after environment variables are loaded. func setBase() error {
func LoadConfig() { var err error
CreateAccountURL = initializers.GetEnv("CREATE_ACCOUNT_URL", "http://localhost:5003/api/v2/account/create")
TrackStatusURL = initializers.GetEnv("TRACK_STATUS_URL", "https://custodial.sarafu.africa/api/track/") custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/") dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status") CustodialAPIKey = initializers.GetEnv("CUSTODIAL_API_KEY", "xd")
DataAPIKey = initializers.GetEnv("DATA_API_KEY", "xd")
_, err = url.JoinPath(custodialURLBase, "/foo")
if err != nil {
return err
}
_, err = url.JoinPath(dataURLBase, "/bar")
if err != nil {
return err
}
return nil
}
// LoadConfig initializes the configuration values after environment variables are loaded.
func LoadConfig() error {
err := setBase()
if err != nil {
return err
}
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath)
BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix)
TrackURL, _ = url.JoinPath(custodialURLBase, trackPath)
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
return nil
} }

View File

@ -0,0 +1,7 @@
{
"admins": [
{
"phonenumber" : "<replace with any admin number to test with >"
}
]
}

View File

@ -0,0 +1,47 @@
package commands
import (
"context"
"encoding/json"
"os"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/urdt/ussd/internal/utils"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type Admin struct {
PhoneNumber string `json:"phonenumber"`
}
type Config struct {
Admins []Admin `json:"admins"`
}
func Seed(ctx context.Context) error {
var config Config
adminstore, err := utils.NewAdminStore(ctx, "../admin_numbers")
store := adminstore.FsStore
if err != nil {
return err
}
defer store.Close()
data, err := os.ReadFile("admin_numbers.json")
if err != nil {
return err
}
if err := json.Unmarshal(data, &config); err != nil {
return err
}
for _, admin := range config.Admins {
err := store.Put(ctx, []byte(admin.PhoneNumber), []byte("1"))
if err != nil {
logg.Printf(logging.LVL_DEBUG, "Failed to insert admin number", admin.PhoneNumber)
return err
}
}
return nil
}

17
devtools/admin/main.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"context"
"log"
"git.grassecon.net/urdt/ussd/devtools/admin/commands"
)
func main() {
ctx := context.Background()
err := commands.Seed(ctx)
if err != nil {
log.Fatalf("Failed to initialize a list of admins with error %s", err)
}
}

View File

@ -1,13 +1,17 @@
package handlers package handlers
import ( import (
"context"
"git.defalsify.org/vise.git/asm" "git.defalsify.org/vise.git/asm"
"git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
"git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/remote"
) )
type HandlerService interface { type HandlerService interface {
@ -28,20 +32,26 @@ type LocalHandlerService struct {
DbRs *resource.DbResource DbRs *resource.DbResource
Pe *persist.Persister Pe *persist.Persister
UserdataStore *db.Db UserdataStore *db.Db
AdminStore *utils.AdminStore
Cfg engine.Config Cfg engine.Config
Rs resource.Resource Rs resource.Resource
} }
func NewLocalHandlerService(fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) { func NewLocalHandlerService(ctx context.Context, fp string, debug bool, dbResource *resource.DbResource, cfg engine.Config, rs resource.Resource) (*LocalHandlerService, error) {
parser, err := getParser(fp, debug) parser, err := getParser(fp, debug)
if err != nil { if err != nil {
return nil, err return nil, err
} }
adminstore, err := utils.NewAdminStore(ctx, "admin_numbers")
if err != nil {
return nil, err
}
return &LocalHandlerService{ return &LocalHandlerService{
Parser: parser, Parser: parser,
DbRs: dbResource, DbRs: dbResource,
Cfg: cfg, AdminStore: adminstore,
Rs: rs, Cfg: cfg,
Rs: rs,
}, nil }, nil
} }
@ -53,8 +63,8 @@ func (ls *LocalHandlerService) SetDataStore(db *db.Db) {
ls.UserdataStore = db ls.UserdataStore = db
} }
func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceInterface) (*ussd.Handlers, error) { func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceInterface) (*ussd.Handlers, error) {
ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore,accountService) ussdHandlers, err := ussd.NewHandlers(ls.Parser, *ls.UserdataStore, ls.AdminStore, accountService)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,6 +108,13 @@ func (ls *LocalHandlerService) GetHandler(accountService server.AccountServiceIn
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("view_voucher", ussdHandlers.ViewVoucher)
ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher) ls.DbRs.AddLocalFunc("set_voucher", ussdHandlers.SetVoucher)
ls.DbRs.AddLocalFunc("reset_valid_pin", ussdHandlers.ResetValidPin)
ls.DbRs.AddLocalFunc("check_pin_mismatch", ussdHandlers.CheckPinMisMatch)
ls.DbRs.AddLocalFunc("validate_blocked_number", ussdHandlers.ValidateBlockedNumber)
ls.DbRs.AddLocalFunc("retrieve_blocked_number", ussdHandlers.RetrieveBlockedNumber)
ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber)
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
return ussdHandlers, nil return ussdHandlers, nil
} }

View File

@ -1,185 +0,0 @@
package server
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/internal/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
)
var (
okResponse api.OKResponse
errResponse api.ErrResponse
)
type AccountServiceInterface interface {
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error)
CreateAccount(ctx context.Context) (*api.OKResponse, error)
CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error)
}
type AccountService struct {
}
// Parameters:
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
// AccountResponse struct can be used here to check the account status during a transaction.
//
// Returns:
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil
func (as *AccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
resp, err := http.Get(config.BalanceURL + trackingId)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var trackResp models.TrackStatusResponse
err = json.Unmarshal(body, &trackResp)
if err != nil {
return nil, err
}
return &trackResp, nil
}
func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
var err error
// Construct the URL with the path parameter
url := fmt.Sprintf("%s/%s", config.TrackURL, publicKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-GE-KEY", "xd")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse)
if err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
}
err = json.Unmarshal([]byte(body), &okResponse)
if err != nil {
return nil, err
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
}
return &okResponse, nil
}
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
resp, err := http.Get(config.BalanceURL + publicKey)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var balanceResp models.BalanceResponse
err = json.Unmarshal(body, &balanceResp)
if err != nil {
return nil, err
}
return &balanceResp, nil
}
// CreateAccount creates a new account in the custodial system.
// Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
// If there is an error during the request or processing, this will be nil.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil.
func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
var err error
// Create a new request
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-GE-KEY", "xd")
resp, err := http.DefaultClient.Do(req)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse)
if err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
}
err = json.Unmarshal([]byte(body), &okResponse)
if err != nil {
return nil, err
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
}
return &okResponse, nil
}
// FetchVouchers retrieves the token holdings for a given public key from the custodial holdings API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
file, err := os.Open("sample_tokens.json")
if err != nil {
return nil, err
}
defer file.Close()
var holdings models.VoucherHoldingResponse
if err := json.NewDecoder(file).Decode(&holdings); err != nil {
return nil, err
}
return &holdings, nil
}

View File

@ -20,8 +20,8 @@ import (
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"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/handlers/server"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/utils"
"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"
@ -35,6 +35,12 @@ var (
errResponse *api.ErrResponse errResponse *api.ErrResponse
) )
// Define the regex patterns as constants
const (
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
pinPattern = `^\d{4}$`
)
// FlagManager handles centralized flag management // FlagManager handles centralized flag management
type FlagManager struct { type FlagManager struct {
parser *asm.FlagParser parser *asm.FlagParser
@ -62,17 +68,18 @@ type Handlers struct {
pe *persist.Persister pe *persist.Persister
st *state.State st *state.State
ca cache.Memory ca cache.Memory
userdataStore utils.DataStore userdataStore common.DataStore
adminstore *utils.AdminStore
flagManager *asm.FlagParser flagManager *asm.FlagParser
accountService server.AccountServiceInterface accountService remote.AccountServiceInterface
prefixDb storage.PrefixDb prefixDb storage.PrefixDb
} }
func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService server.AccountServiceInterface) (*Handlers, error) { func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *utils.AdminStore, accountService remote.AccountServiceInterface) (*Handlers, error) {
if userdataStore == nil { if userdataStore == nil {
return nil, fmt.Errorf("cannot create handler with nil userdata store") return nil, fmt.Errorf("cannot create handler with nil userdata store")
} }
userDb := &utils.UserDataStore{ userDb := &common.UserDataStore{
Db: userdataStore, Db: userdataStore,
} }
// Instantiate the SubPrefixDb with "vouchers" prefix // Instantiate the SubPrefixDb with "vouchers" prefix
@ -81,21 +88,24 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, accountService s
h := &Handlers{ h := &Handlers{
userdataStore: userDb, userdataStore: userDb,
flagManager: appFlags, flagManager: appFlags,
adminstore: adminstore,
accountService: accountService, accountService: accountService,
prefixDb: prefixDb, prefixDb: prefixDb,
} }
return h, nil return h, nil
} }
// Define the regex pattern as a constant
const pinPattern = `^\d{4}$`
// isValidPIN checks whether the given input is a 4 digit number // isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool { func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin) match, _ := regexp.MatchString(pinPattern, pin)
return match 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")
@ -106,13 +116,25 @@ func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var r resource.Result var r resource.Result
if h.pe == nil { if h.pe == nil {
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
} }
h.st = h.pe.GetState() h.st = h.pe.GetState()
h.ca = h.pe.GetMemory() h.ca = h.pe.GetMemory()
sessionId, _ := ctx.Value("SessionId").(string)
flag_admin_privilege, _ := h.flagManager.GetFlag("flag_admin_privilege")
isAdmin, _ := h.adminstore.IsAdmin(sessionId)
if isAdmin {
r.FlagSet = append(r.FlagSet, flag_admin_privilege)
} else {
r.FlagReset = append(r.FlagReset, flag_admin_privilege)
}
if h.st == nil || h.ca == nil { if h.st == nil || h.ca == nil {
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")
@ -148,16 +170,16 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
okResponse, err := h.accountService.CreateAccount(ctx) r, err := h.accountService.CreateAccount(ctx)
if err != nil { if err != nil {
return err return err
} }
trackingId := okResponse.Result["trackingId"].(string) trackingId := r.TrackingId
publicKey := okResponse.Result["publicKey"].(string) publicKey := r.PublicKey
data := map[utils.DataTyp]string{ data := map[common.DataTyp]string{
utils.DATA_TRACKING_ID: trackingId, common.DATA_TRACKING_ID: trackingId,
utils.DATA_PUBLIC_KEY: publicKey, common.DATA_PUBLIC_KEY: publicKey,
} }
store := h.userdataStore store := h.userdataStore
for key, value := range data { for key, value := range data {
@ -170,7 +192,7 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
if err != nil { if err != nil {
return err return err
} }
err = store.WriteEntry(ctx, publicKeyNormalized, utils.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId)) err = store.WriteEntry(ctx, publicKeyNormalized, common.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
if err != nil { if err != nil {
return err return err
} }
@ -190,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, utils.DATA_ACCOUNT_CREATED) _, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist") logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist")
@ -203,6 +225,30 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
store := h.userdataStore
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
if bytes.Equal(temporaryPin, input) {
res.FlagReset = append(res.FlagReset, flag_pin_mismatch)
} else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
}
return res, nil
}
func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) VerifyNewPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{} res := resource.Result{}
_, ok := ctx.Value("SessionId").(string) _, ok := ctx.Value("SessionId").(string)
@ -234,7 +280,6 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
} }
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
accountPIN := string(input) accountPIN := string(input)
// Validate that the PIN is a 4-digit number // Validate that the PIN is a 4-digit number
@ -242,11 +287,32 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
res.FlagSet = append(res.FlagSet, flag_incorrect_pin) res.FlagSet = append(res.FlagSet, flag_incorrect_pin)
return res, nil return res, nil
} }
res.FlagReset = append(res.FlagReset, flag_incorrect_pin) res.FlagReset = append(res.FlagReset, flag_incorrect_pin)
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(accountPIN))
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(accountPIN)) sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
temporaryPin := string(input)
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -263,7 +329,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch")
store := h.userdataStore store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -272,7 +338,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
} else { } else {
res.FlagSet = append(res.FlagSet, flag_pin_mismatch) res.FlagSet = append(res.FlagSet, flag_pin_mismatch)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -294,11 +360,10 @@ func (h *Handlers) VerifyCreatePin(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
temporaryPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil { if err != nil {
return res, err return res, err
} }
if bytes.Equal(input, temporaryPin) { if bytes.Equal(input, temporaryPin) {
res.FlagSet = []uint32{flag_valid_pin} res.FlagSet = []uint32{flag_valid_pin}
res.FlagReset = []uint32{flag_pin_mismatch} res.FlagReset = []uint32{flag_pin_mismatch}
@ -307,7 +372,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
res.FlagSet = []uint32{flag_pin_mismatch} res.FlagSet = []uint32{flag_pin_mismatch}
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(temporaryPin)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -338,13 +403,13 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_FIRST_NAME, []byte(temporaryFirstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -361,6 +426,7 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
familyName := string(input) familyName := string(input)
@ -368,13 +434,13 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_FAMILY_NAME, []byte(temporaryFamilyName)) err = store.WriteEntry(ctx, sessionId, common.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -396,13 +462,13 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryYob, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_YOB, []byte(temporaryYob)) err = store.WriteEntry(ctx, sessionId, common.DATA_YOB, []byte(temporaryYob))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -426,13 +492,13 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_LOCATION, []byte(temporaryLocation)) err = store.WriteEntry(ctx, sessionId, common.DATA_LOCATION, []byte(temporaryLocation))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -456,13 +522,13 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryGender, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(temporaryGender)) err = store.WriteEntry(ctx, sessionId, common.DATA_GENDER, []byte(temporaryGender))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(gender)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -479,6 +545,7 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
offerings := string(input) offerings := string(input)
store := h.userdataStore store := h.userdataStore
@ -486,13 +553,13 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
allowUpdate := h.st.MatchFlag(flag_allow_update, true) allowUpdate := h.st.MatchFlag(flag_allow_update, true)
if allowUpdate { if allowUpdate {
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE) temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, utils.DATA_OFFERINGS, []byte(temporaryOfferings)) err = store.WriteEntry(ctx, sessionId, common.DATA_OFFERINGS, []byte(temporaryOfferings))
if err != nil { if err != nil {
return res, err return res, err
} }
} else { } else {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings)) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -511,6 +578,14 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
return res, nil return res, nil
} }
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
func (h *Handlers) ResetValidPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin")
res.FlagReset = append(res.FlagReset, flag_valid_pin)
return res, nil
}
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry. // ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -528,7 +603,7 @@ func (h *Handlers) CheckIdentifier(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
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, _ := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
res.Content = string(publicKey) res.Content = string(publicKey)
@ -549,7 +624,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
store := h.userdataStore store := h.userdataStore
AccountPin, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN) AccountPin, err := store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -594,22 +669,23 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
if !ok { if !ok {
return res, fmt.Errorf("missing session") return res, fmt.Errorf("missing session")
} }
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey)) r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
if err != nil { if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
return res, err return res, err
} }
res.FlagReset = append(res.FlagReset, flag_api_error) res.FlagReset = append(res.FlagReset, flag_api_error)
isActive := okResponse.Result["active"].(bool)
if !ok { if !ok {
return res, err return res, err
} }
if isActive { if r.Active {
res.FlagSet = append(res.FlagSet, flag_account_success) res.FlagSet = append(res.FlagSet, flag_account_success)
res.FlagReset = append(res.FlagReset, flag_account_pending) res.FlagReset = append(res.FlagReset, flag_account_pending)
} else { } else {
@ -655,7 +731,6 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
var err error var err error
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
date := string(input) date := string(input)
_, err = strconv.Atoi(date) _, err = strconv.Atoi(date)
if err != nil { if err != nil {
@ -678,7 +753,6 @@ func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []by
var res resource.Result var res resource.Result
flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format")
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
return res, nil return res, nil
} }
@ -701,7 +775,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
store := h.userdataStore store := h.userdataStore
// get the active sym and active balance // get the active sym and active balance
activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
balance := "0.00" balance := "0.00"
@ -712,7 +786,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, err return res, err
} }
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -724,6 +798,7 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error") flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
sessionId, ok := ctx.Value("SessionId").(string) sessionId, ok := ctx.Value("SessionId").(string)
@ -734,21 +809,19 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
balanceType := strings.Split(symbol, "_")[0] balanceType := strings.Split(symbol, "_")[0]
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey)) balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
if err != nil { if err != nil {
return res, nil
}
if !balanceResponse.Ok {
res.FlagSet = append(res.FlagSet, flag_api_error) res.FlagSet = append(res.FlagSet, flag_api_error)
return res, nil return res, nil
} }
res.FlagReset = append(res.FlagReset, flag_api_error) res.FlagReset = append(res.FlagReset, flag_api_error)
balance := balanceResponse.Result.Balance
balance := balanceResponse.Balance
switch balanceType { switch balanceType {
case "my": case "my":
@ -761,6 +834,67 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
return res, nil return res, nil
} }
func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
return res, err
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
return res, nil
}
return res, nil
}
func (h *Handlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
res.FlagReset = append(res.FlagReset, flag_unregistered_number)
return res, nil
}
func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
blockedNumber := string(input)
_, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY)
if !isValidPhoneNumber(blockedNumber) {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
}
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
return res, err
}
}
err = store.WriteEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER, []byte(blockedNumber))
if err != nil {
return res, nil
}
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number. // 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
@ -784,7 +918,7 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, nil return res, nil
} }
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(recipient)) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@ -807,12 +941,12 @@ func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byt
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@ -834,7 +968,7 @@ func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
store := h.userdataStore store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte("")) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(""))
if err != nil { if err != nil {
return res, nil return res, nil
} }
@ -856,7 +990,7 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
} }
store := h.userdataStore store := h.userdataStore
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -881,7 +1015,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
var balanceValue float64 var balanceValue float64
// retrieve the active balance // retrieve the active balance
activeBal, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL) activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -907,7 +1041,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
// Format the amount with 2 decimal places before saving // Format the amount with 2 decimal places before saving
formattedAmount := fmt.Sprintf("%.2f", inputAmount) formattedAmount := fmt.Sprintf("%.2f", inputAmount)
err = store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(formattedAmount)) err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(formattedAmount))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -925,13 +1059,29 @@ func (h *Handlers) GetRecipient(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
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT) recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT)
res.Content = string(recipient) res.Content = string(recipient)
return res, nil return res, nil
} }
// RetrieveBlockedNumber gets the current number during the pin reset for other's is in progress.
func (h *Handlers) RetrieveBlockedNumber(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
blockedNumber, _ := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
res.Content = string(blockedNumber)
return res, nil
}
// GetSender returns the sessionId (phoneNumber) // GetSender returns the sessionId (phoneNumber)
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) { func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result var res resource.Result
@ -957,12 +1107,12 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res
store := h.userdataStore store := h.userdataStore
// retrieve the active symbol // retrieve the active symbol
activeSym, err := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
return res, err return res, err
} }
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT) amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT)
res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym)) res.Content = fmt.Sprintf("%s %s", string(amount), string(activeSym))
@ -986,11 +1136,11 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
// Use the amount, recipient and sender to call the API and initialize the transaction // Use the amount, recipient and sender to call the API and initialize the transaction
store := h.userdataStore store := h.userdataStore
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT) amount, _ := store.ReadEntry(ctx, sessionId, common.DATA_AMOUNT)
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT) recipient, _ := store.ReadEntry(ctx, sessionId, common.DATA_RECIPIENT)
activeSym, _ := store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId)) res.Content = l.Get("Your request has been sent. %s will receive %s %s from %s.", string(recipient), string(amount), string(activeSym), string(sessionId))
@ -1030,12 +1180,12 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
} }
store := h.userdataStore store := h.userdataStore
// Retrieve user data as strings with fallback to defaultValue // Retrieve user data as strings with fallback to defaultValue
firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FIRST_NAME)) firstName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME))
familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_FAMILY_NAME)) familyName := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME))
yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_YOB)) yob := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_YOB))
gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_GENDER)) gender := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_GENDER))
location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_LOCATION)) location := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_LOCATION))
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, utils.DATA_OFFERINGS)) offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
// Construct the full name // Construct the full name
name := defaultValue name := defaultValue
@ -1092,11 +1242,11 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher") flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher")
// check if the user has an active sym // check if the user has an active sym
_, err = store.ReadEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM) _, err = store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil { if err != nil {
if db.IsNotFound(err) { if db.IsNotFound(err) {
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -1108,23 +1258,23 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
} }
// Return if there is no voucher // Return if there is no voucher
if len(vouchersResp.Result.Holdings) == 0 { if len(vouchersResp) == 0 {
res.FlagSet = append(res.FlagSet, flag_no_active_voucher) res.FlagSet = append(res.FlagSet, flag_no_active_voucher)
return res, nil return res, nil
} }
// Use only the first voucher // Use only the first voucher
firstVoucher := vouchersResp.Result.Holdings[0] firstVoucher := vouchersResp[0]
defaultSym := firstVoucher.TokenSymbol defaultSym := firstVoucher.TokenSymbol
defaultBal := firstVoucher.Balance defaultBal := firstVoucher.Balance
// set the active symbol // set the active symbol
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(defaultSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(defaultSym))
if err != nil { if err != nil {
return res, err return res, err
} }
// set the active balance // set the active balance
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(defaultBal)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(defaultBal))
if err != nil { if err != nil {
return res, err return res, err
} }
@ -1150,7 +1300,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
} }
store := h.userdataStore store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY) publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil { if err != nil {
return res, nil return res, nil
} }
@ -1161,7 +1311,7 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil return res, nil
} }
data := utils.ProcessVouchers(vouchersResp.Result.Holdings) data := common.ProcessVouchers(vouchersResp)
// Store all voucher data // Store all voucher data
dataMap := map[string]string{ dataMap := map[string]string{
@ -1211,7 +1361,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, nil return res, nil
} }
metadata, err := utils.GetVoucherData(ctx, h.prefixDb, inputStr) metadata, err := common.GetVoucherData(ctx, h.prefixDb, inputStr)
if err != nil { if err != nil {
return res, fmt.Errorf("failed to retrieve voucher data: %v", err) return res, fmt.Errorf("failed to retrieve voucher data: %v", err)
} }
@ -1221,7 +1371,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, nil return res, nil
} }
if err := utils.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil { if err := common.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
return res, err return res, err
} }
@ -1241,13 +1391,13 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re
} }
// Get temporary data // Get temporary data
tempData, err := utils.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId) tempData, err := common.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
if err != nil { if err != nil {
return res, err return res, err
} }
// Set as active and clear temporary data // Set as active and clear temporary data
if err := utils.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil { if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
return res, err return res, err
} }

View File

@ -12,14 +12,14 @@ import (
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/mocks" "git.grassecon.net/urdt/ussd/internal/testutil/mocks"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/models"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/common"
"github.com/alecthomas/assert/v2" "github.com/alecthomas/assert/v2"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -33,7 +33,7 @@ var (
) )
// InitializeTestStore sets up and returns an in-memory database and store. // InitializeTestStore sets up and returns an in-memory database and store.
func InitializeTestStore(t *testing.T) (context.Context, *utils.UserDataStore) { func InitializeTestStore(t *testing.T) (context.Context, *common.UserDataStore) {
ctx := context.Background() ctx := context.Background()
// Initialize memDb // Initialize memDb
@ -42,7 +42,7 @@ func InitializeTestStore(t *testing.T) (context.Context, *utils.UserDataStore) {
require.NoError(t, err, "Failed to connect to memDb") require.NoError(t, err, "Failed to connect to memDb")
// Create UserDataStore with memDb // Create UserDataStore with memDb
store := &utils.UserDataStore{Db: db} store := &common.UserDataStore{Db: db}
t.Cleanup(func() { t.Cleanup(func() {
db.Close() // Ensure the DB is closed after each test db.Close() // Ensure the DB is closed after each test
@ -71,7 +71,7 @@ func TestNewHandlers(t *testing.T) {
t.Logf(err.Error()) t.Logf(err.Error())
} }
t.Run("Valid UserDataStore", func(t *testing.T) { t.Run("Valid UserDataStore", func(t *testing.T) {
handlers, err := NewHandlers(fm.parser, store, &accountService) handlers, err := NewHandlers(fm.parser, store, nil, &accountService)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
@ -85,7 +85,7 @@ func TestNewHandlers(t *testing.T) {
// Test case for nil userdataStore // Test case for nil userdataStore
t.Run("Nil UserDataStore", func(t *testing.T) { t.Run("Nil UserDataStore", func(t *testing.T) {
handlers, err := NewHandlers(fm.parser, nil, &accountService) handlers, err := NewHandlers(fm.parser, nil, nil, &accountService)
if err == nil { if err == nil {
t.Fatal("expected an error, got none") t.Fatal("expected an error, got none")
} }
@ -115,18 +115,14 @@ func TestCreateAccount(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
serverResponse *api.OKResponse serverResponse *models.AccountResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test account creation success", name: "Test account creation success",
serverResponse: &api.OKResponse{ serverResponse: &models.AccountResult{
Ok: true, TrackingId: "1234567890",
Description: "Account creation successed", PublicKey: "0xD3adB33f",
Result: map[string]any{
"trackingId": "1234567890",
"publicKey": "0xD3adB33f",
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_created}, FlagSet: []uint32{flag_account_created},
@ -180,7 +176,7 @@ func TestSaveFirstname(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -192,7 +188,7 @@ func TestSaveFirstname(t *testing.T) {
// Define test data // Define test data
firstName := "John" firstName := "John"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -211,7 +207,7 @@ func TestSaveFirstname(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_FIRST_NAME) storedFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
assert.Equal(t, firstName, string(storedFirstName)) assert.Equal(t, firstName, string(storedFirstName))
} }
@ -219,7 +215,7 @@ func TestSaveFamilyname(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -231,7 +227,7 @@ func TestSaveFamilyname(t *testing.T) {
// Define test data // Define test data
familyName := "Doeee" familyName := "Doeee"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -250,7 +246,7 @@ func TestSaveFamilyname(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_FAMILY_NAME) storedFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
assert.Equal(t, familyName, string(storedFamilyName)) assert.Equal(t, familyName, string(storedFamilyName))
} }
@ -258,7 +254,7 @@ func TestSaveYoB(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -269,8 +265,8 @@ func TestSaveYoB(t *testing.T) {
// Define test data // Define test data
yob := "1980" yob := "1980"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -289,7 +285,7 @@ func TestSaveYoB(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_YOB) storedYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_YOB)
assert.Equal(t, yob, string(storedYob)) assert.Equal(t, yob, string(storedYob))
} }
@ -297,7 +293,7 @@ func TestSaveLocation(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -308,8 +304,8 @@ func TestSaveLocation(t *testing.T) {
// Define test data // Define test data
location := "Kilifi" location := "Kilifi"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(location)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -328,7 +324,7 @@ func TestSaveLocation(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_LOCATION) storedLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
assert.Equal(t, location, string(storedLocation)) assert.Equal(t, location, string(storedLocation))
} }
@ -336,7 +332,7 @@ func TestSaveOfferings(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -348,7 +344,7 @@ func TestSaveOfferings(t *testing.T) {
// Define test data // Define test data
offerings := "Bananas" offerings := "Bananas"
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -367,7 +363,7 @@ func TestSaveOfferings(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_OFFERINGS) storedOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
assert.Equal(t, offerings, string(storedOfferings)) assert.Equal(t, offerings, string(storedOfferings))
} }
@ -375,7 +371,7 @@ func TestSaveGender(t *testing.T) {
sessionId := "session123" sessionId := "session123"
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
fm, _ := NewFlagManager(flagsPath) fm, _ := NewFlagManager(flagsPath)
flag_allow_update, _ := fm.GetFlag("flag_allow_update") flag_allow_update, _ := fm.GetFlag("flag_allow_update")
@ -413,7 +409,7 @@ func TestSaveGender(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(tt.expectedGender)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -433,7 +429,7 @@ func TestSaveGender(t *testing.T) {
assert.Equal(t, resource.Result{}, 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, utils.DATA_GENDER) storedGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
assert.Equal(t, tt.expectedGender, string(storedGender)) assert.Equal(t, tt.expectedGender, string(storedGender))
}) })
} }
@ -487,7 +483,6 @@ func TestSaveTemporaryPin(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
// Assert that the Result FlagSet has the required flags after language switch // Assert that the Result FlagSet has the required flags after language switch
assert.Equal(t, res, tt.expectedResult, "Result should match expected result") assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
}) })
@ -518,7 +513,7 @@ func TestCheckIdentifier(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(tt.publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(tt.publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -562,12 +557,12 @@ func TestGetAmount(t *testing.T) {
amount := "0.03" amount := "0.03"
activeSym := "SRF" activeSym := "SRF"
err := store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(amount)) err := store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(amount))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(activeSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(activeSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -593,7 +588,7 @@ func TestGetRecipient(t *testing.T) {
recepient := "0xcasgatweksalw1018221" recepient := "0xcasgatweksalw1018221"
err := store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(recepient)) err := store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recepient))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -722,12 +717,12 @@ func TestResetAllowUpdate(t *testing.T) {
func TestResetAccountAuthorized(t *testing.T) { func TestResetAccountAuthorized(t *testing.T) {
fm, err := NewFlagManager(flagsPath) fm, err := NewFlagManager(flagsPath)
flag_account_authorized, _ := fm.parser.GetFlag("flag_account_authorized")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
flag_account_authorized, _ := fm.parser.GetFlag("flag_account_authorized")
// Define test cases // Define test cases
tests := []struct { tests := []struct {
name string name string
@ -745,7 +740,6 @@ func TestResetAccountAuthorized(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Create the Handlers instance with the mock flag manager // Create the Handlers instance with the mock flag manager
h := &Handlers{ h := &Handlers{
flagManager: fm.parser, flagManager: fm.parser,
@ -904,10 +898,7 @@ func TestAuthorize(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Create context with session ID err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(accountPIN))
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_PIN, []byte(accountPIN))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1034,7 +1025,7 @@ func TestVerifyCreatePin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err = store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte("1234")) err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte("1234"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1067,18 +1058,14 @@ func TestCheckAccountStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
publicKey []byte publicKey []byte
serverResponse *api.OKResponse response *models.TrackStatusResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test when account is on the Sarafu network", name: "Test when account is on the Sarafu network",
publicKey: []byte("TrackingId1234"), publicKey: []byte("TrackingId1234"),
serverResponse: &api.OKResponse{ response: &models.TrackStatusResult{
Ok: true, Active: true,
Description: "Account creation succeeded",
Result: map[string]any{
"active": true,
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_success}, FlagSet: []uint32{flag_account_success},
@ -1088,12 +1075,8 @@ func TestCheckAccountStatus(t *testing.T) {
{ {
name: "Test when the account is not yet on the sarafu network", name: "Test when the account is not yet on the sarafu network",
publicKey: []byte("TrackingId1234"), publicKey: []byte("TrackingId1234"),
serverResponse: &api.OKResponse{ response: &models.TrackStatusResult{
Ok: true, Active: false,
Description: "Account creation succeeded",
Result: map[string]any{
"active": false,
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagSet: []uint32{flag_account_pending}, FlagSet: []uint32{flag_account_pending},
@ -1111,12 +1094,12 @@ func TestCheckAccountStatus(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(tt.publicKey)) err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(tt.publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.serverResponse, nil) mockAccountService.On("TrackAccountStatus", string(tt.publicKey)).Return(tt.response, nil)
// Call the method under test // Call the method under test
res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte("")) res, _ := h.CheckAccountStatus(ctx, "check_account_status", []byte(""))
@ -1264,15 +1247,15 @@ func TestInitiateTransaction(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_AMOUNT, []byte(tt.Amount)) err := store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(tt.Amount))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_RECIPIENT, []byte(tt.Recipient)) err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(tt.Recipient))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_SYM, []byte(tt.ActiveSym)) err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(tt.ActiveSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1446,7 +1429,7 @@ func TestValidateAmount(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
err := store.WriteEntry(ctx, sessionId, utils.DATA_ACTIVE_BAL, []byte(tt.activeBal)) err := store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(tt.activeBal))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1550,11 +1533,11 @@ func TestCheckBalance(t *testing.T) {
accountService: mockAccountService, accountService: mockAccountService,
} }
err := store.WriteEntry(ctx, tt.sessionId, utils.DATA_ACTIVE_SYM, []byte(tt.activeSym)) err := store.WriteEntry(ctx, tt.sessionId, common.DATA_ACTIVE_SYM, []byte(tt.activeSym))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = store.WriteEntry(ctx, tt.sessionId, utils.DATA_ACTIVE_BAL, []byte(tt.activeBal)) err = store.WriteEntry(ctx, tt.sessionId, common.DATA_ACTIVE_BAL, []byte(tt.activeBal))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1589,13 +1572,13 @@ func TestGetProfile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
languageCode string languageCode string
keys []utils.DataTyp keys []common.DataTyp
profileInfo []string profileInfo []string
result resource.Result result resource.Result
}{ }{
{ {
name: "Test with full profile information in eng", name: "Test with full profile information in eng",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "eng", languageCode: "eng",
result: resource.Result{ result: resource.Result{
@ -1607,7 +1590,7 @@ func TestGetProfile(t *testing.T) {
}, },
{ {
name: "Test with with profile information in swa", name: "Test with with profile information in swa",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "swa", languageCode: "swa",
result: resource.Result{ result: resource.Result{
@ -1619,7 +1602,7 @@ func TestGetProfile(t *testing.T) {
}, },
{ {
name: "Test with with profile information with language that is not yet supported", name: "Test with with profile information with language that is not yet supported",
keys: []utils.DataTyp{utils.DATA_FAMILY_NAME, utils.DATA_FIRST_NAME, utils.DATA_GENDER, utils.DATA_OFFERINGS, utils.DATA_LOCATION, utils.DATA_YOB}, keys: []common.DataTyp{common.DATA_FAMILY_NAME, common.DATA_FIRST_NAME, common.DATA_GENDER, common.DATA_OFFERINGS, common.DATA_LOCATION, common.DATA_YOB},
profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"}, profileInfo: []string{"Doee", "John", "Male", "Bananas", "Kilifi", "1976"},
languageCode: "nor", languageCode: "nor",
result: resource.Result{ result: resource.Result{
@ -1728,7 +1711,7 @@ func TestConfirmPin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(tt.temporarypin)) err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(tt.temporarypin))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1757,43 +1740,21 @@ func TestFetchCustodialBalances(t *testing.T) {
ctx, store := InitializeTestStore(t) ctx, store := InitializeTestStore(t)
ctx = context.WithValue(ctx, "SessionId", sessionId) ctx = context.WithValue(ctx, "SessionId", sessionId)
err = store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tests := []struct { tests := []struct {
name string name string
balanceResonse *models.BalanceResponse balanceResponse *models.BalanceResult
expectedResult resource.Result expectedResult resource.Result
}{ }{
{ {
name: "Test when fetch custodial balances is not a success", name: "Test when fetch custodial balances is not a success",
balanceResonse: &models.BalanceResponse{ balanceResponse: &models.BalanceResult{
Ok: false, Balance: "0.003 CELO",
Result: struct { Nonce: json.Number("0"),
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
},
expectedResult: resource.Result{
FlagSet: []uint32{flag_api_error},
},
},
{
name: "Test when fetch custodial balances is a success",
balanceResonse: &models.BalanceResponse{
Ok: true,
Result: struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
}, },
expectedResult: resource.Result{ expectedResult: resource.Result{
FlagReset: []uint32{flag_api_error}, FlagReset: []uint32{flag_api_error},
@ -1813,7 +1774,7 @@ func TestFetchCustodialBalances(t *testing.T) {
} }
// Set up the expected behavior of the mock // Set up the expected behavior of the mock
mockAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResonse, nil) mockAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResponse, nil)
// Call the method // Call the method
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte("")) res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
@ -1833,28 +1794,33 @@ func TestSetDefaultVoucher(t *testing.T) {
if err != nil { if err != nil {
t.Logf(err.Error()) t.Logf(err.Error())
} }
flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher")
if err != nil {
t.Logf(err.Error())
}
publicKey := "0X13242618721" publicKey := "0X13242618721"
tests := []struct { tests := []struct {
name string name string
vouchersResp *models.VoucherHoldingResponse vouchersResp []dataserviceapi.TokenHoldings
expectedResult resource.Result expectedResult resource.Result
}{ }{
{
name: "Test no vouchers available",
vouchersResp: []dataserviceapi.TokenHoldings{},
expectedResult: resource.Result{
FlagSet: []uint32{flag_no_active_voucher},
},
},
{ {
name: "Test set default voucher when no active voucher is set", name: "Test set default voucher when no active voucher is set",
vouchersResp: &models.VoucherHoldingResponse{ vouchersResp: []dataserviceapi.TokenHoldings{
Ok: true, dataserviceapi.TokenHoldings{
Description: "Vouchers fetched successfully", ContractAddress: "0x123",
Result: models.VoucherResult{ TokenSymbol: "TOKEN1",
Holdings: []dataserviceapi.TokenHoldings{ TokenDecimals: "18",
{ Balance: "100",
ContractAddress: "0x123",
TokenSymbol: "TOKEN1",
TokenDecimals: "18",
Balance: "100",
},
},
}, },
}, },
expectedResult: resource.Result{}, expectedResult: resource.Result{},
@ -1871,14 +1837,14 @@ func TestSetDefaultVoucher(t *testing.T) {
flagManager: fm.parser, flagManager: fm.parser,
} }
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil)
res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("")) res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input"))
assert.NoError(t, err) assert.NoError(t, err)
@ -1904,13 +1870,12 @@ func TestCheckVouchers(t *testing.T) {
prefixDb: spdb, prefixDb: spdb,
} }
err := store.WriteEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY, []byte(publicKey)) err := store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mockVouchersResponse := &models.VoucherHoldingResponse{} mockVouchersResponse := []dataserviceapi.TokenHoldings{
mockVouchersResponse.Result.Holdings = []dataserviceapi.TokenHoldings{
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"},
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"},
} }
@ -2018,7 +1983,7 @@ func TestSetVoucher(t *testing.T) {
expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.ContractAddress) expectedData := fmt.Sprintf("%s,%s,%s,%s", tempData.TokenSymbol, tempData.Balance, tempData.TokenDecimals, tempData.ContractAddress)
// store the expectedData // store the expectedData
if err := store.WriteEntry(ctx, sessionId, utils.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil { if err := store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(expectedData)); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -1,10 +0,0 @@
package models
type AccountResponse struct {
Ok bool `json:"ok"`
Description string `json:"description"` // Include the description field
Result struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
} `json:"result"`
}

View File

@ -1,12 +0,0 @@
package models
import "encoding/json"
type BalanceResponse struct {
Ok bool `json:"ok"`
Result struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
} `json:"result"`
}

View File

@ -1,26 +0,0 @@
package models
import (
"encoding/json"
"time"
)
type Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
type TrackStatusResponse struct {
Ok bool `json:"ok"`
Result struct {
Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
} `json:"result"`
}

View File

@ -1,14 +0,0 @@
package models
import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
type VoucherHoldingResponse struct {
Ok bool `json:"ok"`
Description string `json:"description"`
Result VoucherResult `json:"result"`
}
// VoucherResult holds the list of token holdings
type VoucherResult struct {
Holdings []dataserviceapi.TokenHoldings `json:"holdings"`
}

View File

@ -11,11 +11,11 @@ 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/internal/handlers" "git.grassecon.net/urdt/ussd/internal/handlers"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/storage"
"git.grassecon.net/urdt/ussd/internal/testutil/testservice" "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
"git.grassecon.net/urdt/ussd/internal/testutil/testtag" "git.grassecon.net/urdt/ussd/internal/testutil/testtag"
testdataloader "github.com/peteole/testdata-loader" testdataloader "github.com/peteole/testdata-loader"
"git.grassecon.net/urdt/ussd/remote"
) )
var ( var (
@ -73,7 +73,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
os.Exit(1) os.Exit(1)
} }
lhs, err := handlers.NewLocalHandlerService(pfp, true, dbResource, cfg, rs) lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
lhs.SetDataStore(&userDataStore) lhs.SetDataStore(&userDataStore)
lhs.SetPersister(pe) lhs.SetPersister(pe)
@ -83,7 +83,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
} }
if testtag.AccountService == nil { if testtag.AccountService == nil {
testtag.AccountService = &server.AccountService{} testtag.AccountService = &remote.AccountService{}
} }
switch testtag.AccountService.(type) { switch testtag.AccountService.(type) {
@ -91,7 +91,7 @@ func TestEngine(sessionId string) (engine.Engine, func(), chan bool) {
go func() { go func() {
eventChannel <- false eventChannel <- false
}() }()
case *server.AccountService: case *remote.AccountService:
go func() { go func() {
time.Sleep(5 * time.Second) // Wait for 5 seconds time.Sleep(5 * time.Second) // Wait for 5 seconds
eventChannel <- true eventChannel <- true

View File

@ -3,8 +3,8 @@ package mocks
import ( import (
"context" "context"
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@ -13,28 +13,33 @@ type MockAccountService struct {
mock.Mock mock.Mock
} }
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) { func (m *MockAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
args := m.Called() args := m.Called()
return args.Get(0).(*api.OKResponse), args.Error(1) return args.Get(0).(*models.AccountResult), args.Error(1)
} }
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) { func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*models.BalanceResponse), args.Error(1) return args.Get(0).(*models.BalanceResult), args.Error(1)
} }
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) { func (m *MockAccountService) TrackAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResult, error) {
args := m.Called(trackingId) args := m.Called(trackingId)
return args.Get(0).(*models.TrackStatusResponse), args.Error(1) return args.Get(0).(*models.TrackStatusResult), args.Error(1)
} }
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*api.OKResponse), args.Error(1) return args.Get(0).([]dataserviceapi.TokenHoldings), args.Error(1)
} }
func (m *MockAccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
func (m *MockAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) {
args := m.Called(publicKey) args := m.Called(publicKey)
return args.Get(0).(*models.VoucherHoldingResponse), args.Error(1) return args.Get(0).([]dataserviceapi.Last10TxResponse), args.Error(1)
}
func(m MockAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
args := m.Called(address)
return args.Get(0).(*models.VoucherDataResult), args.Error(1)
} }

View File

@ -3,88 +3,50 @@ package testservice
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"time"
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/models"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
) )
type TestAccountService struct { type TestAccountService struct {
} }
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) { func (tas *TestAccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
return &api.OKResponse{ return &models.AccountResult {
Ok: true, TrackingId: "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
Description: "Account creation succeeded", PublicKey: "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
Result: map[string]any{
"trackingId": "075ccc86-f6ef-4d33-97d5-e91cfb37aa0d",
"publicKey": "0x623EFAFa8868df4B934dd12a8B26CB3Dd75A7AdD",
},
}, nil }, nil
} }
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) { func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
balanceResponse := &models.BalanceResponse{ balanceResponse := &models.BalanceResult {
Ok: true, Balance: "0.003 CELO",
Result: struct { Nonce: json.Number("0"),
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}{
Balance: "0.003 CELO",
Nonce: json.Number("0"),
},
} }
return balanceResponse, nil return balanceResponse, nil
} }
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) { func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) {
trackResponse := &models.TrackStatusResponse{ return &models.TrackStatusResult {
Ok: true, Active: true,
Result: struct {
Transaction struct {
CreatedAt time.Time "json:\"createdAt\""
Status string "json:\"status\""
TransferValue json.Number "json:\"transferValue\""
TxHash string "json:\"txHash\""
TxType string "json:\"txType\""
}
}{
Transaction: models.Transaction{
CreatedAt: time.Now(),
Status: "SUCCESS",
TransferValue: json.Number("0.5"),
TxHash: "0x123abc456def",
TxType: "transfer",
},
},
}
return trackResponse, nil
}
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
return &api.OKResponse{
Ok: true,
Description: "Account creation succeeded",
Result: map[string]any{
"active": true,
},
}, nil }, nil
} }
func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) (*models.VoucherHoldingResponse, error) { func (tas *TestAccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
return &models.VoucherHoldingResponse{ return []dataserviceapi.TokenHoldings {
Ok: true, dataserviceapi.TokenHoldings {
Result: models.VoucherResult{ ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee",
Holdings: []dataserviceapi.TokenHoldings{ TokenSymbol: "SRF",
{ TokenDecimals: "6",
ContractAddress: "0x6CC75A06ac72eB4Db2eE22F781F5D100d8ec03ee", Balance: "2745987",
TokenSymbol: "SRF",
TokenDecimals: "6",
Balance: "2745987",
},
},
}, },
}, nil }, nil
}
func (tas *TestAccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
return []dataserviceapi.Last10TxResponse{}, nil
}
func(m TestAccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
return &models.VoucherDataResult{}, nil
} }

View File

@ -3,10 +3,10 @@
package testtag package testtag
import ( import (
"git.grassecon.net/urdt/ussd/internal/handlers/server" "git.grassecon.net/urdt/ussd/remote"
accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice" accountservice "git.grassecon.net/urdt/ussd/internal/testutil/testservice"
) )
var ( var (
AccountService server.AccountServiceInterface = &accountservice.TestAccountService{} AccountService remote.AccountServiceInterface = &accountservice.TestAccountService{}
) )

View File

@ -0,0 +1,51 @@
package utils
import (
"context"
"git.defalsify.org/vise.git/db"
fsdb "git.defalsify.org/vise.git/db/fs"
"git.defalsify.org/vise.git/logging"
)
var (
logg = logging.NewVanilla().WithDomain("adminstore")
)
type AdminStore struct {
ctx context.Context
FsStore db.Db
}
func NewAdminStore(ctx context.Context, fileName string) (*AdminStore, error) {
fsStore, err := getFsStore(ctx, fileName)
if err != nil {
return nil, err
}
return &AdminStore{ctx: ctx, FsStore: fsStore}, nil
}
func getFsStore(ctx context.Context, connectStr string) (db.Db, error) {
fsStore := fsdb.NewFsDb()
err := fsStore.Connect(ctx, connectStr)
fsStore.SetPrefix(db.DATATYPE_USERDATA)
if err != nil {
return nil, err
}
return fsStore, nil
}
// Checks if the given sessionId is listed as an admin.
func (as *AdminStore) IsAdmin(sessionId string) (bool, error) {
_, err := as.FsStore.Get(as.ctx, []byte(sessionId))
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Returning false because session id was not found")
return false, nil
} else {
return false, err
}
}
return true, nil
}

View File

@ -13,7 +13,7 @@
}, },
{ {
"input": "5", "input": "5",
"expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n3:Guard my PIN\n0:Back" "expectedContent": "PIN Management\n1:Change PIN\n2:Reset other's PIN\n0:Back"
}, },
{ {
"input": "1", "input": "1",

View File

@ -0,0 +1,6 @@
package models
type AccountResult struct {
PublicKey string `json:"publicKey"`
TrackingId string `json:"trackingId"`
}

View File

@ -0,0 +1,9 @@
package models
import "encoding/json"
type BalanceResult struct {
Balance string `json:"balance"`
Nonce json.Number `json:"nonce"`
}

View File

@ -0,0 +1,18 @@
package models
import (
"encoding/json"
"time"
)
type Transaction struct {
CreatedAt time.Time `json:"createdAt"`
Status string `json:"status"`
TransferValue json.Number `json:"transferValue"`
TxHash string `json:"txHash"`
TxType string `json:"txType"`
}
type TrackStatusResult struct {
Active bool `json:"active"`
}

View File

@ -0,0 +1,21 @@
package models
import dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
//type VoucherHoldingResponse struct {
// Ok bool `json:"ok"`
// Description string `json:"description"`
// Result VoucherResult `json:"result"`
//}
// VoucherResult holds the list of token holdings
type VoucherResult struct {
Holdings []dataserviceapi.TokenHoldings `json:"holdings"`
}
type VoucherDataResult struct {
TokenName string `json:"tokenName"`
TokenSymbol string `json:"tokenSymbol"`
TokenDecimals string `json:"tokenDecimals"`
SinkAddress string `json:"sinkAddress"`
}

224
remote/accountservice.go Normal file
View File

@ -0,0 +1,224 @@
package remote
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/models"
)
var (
)
type AccountServiceInterface interface {
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error)
CreateAccount(ctx context.Context) (*models.AccountResult, error)
TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error)
FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error)
FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error)
VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error)
}
type AccountService struct {
}
// Parameters:
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
// AccountResponse struct can be used here to check the account status during a transaction.
//
// Returns:
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil
func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*models.TrackStatusResult, error) {
var r models.TrackStatusResult
ep, err := url.JoinPath(config.TrackURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
}
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked.
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResult, error) {
var balanceResult models.BalanceResult
ep, err := url.JoinPath(config.BalanceURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &balanceResult)
return &balanceResult, err
}
// CreateAccount creates a new account in the custodial system.
// Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
// If there is an error during the request or processing, this will be nil.
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
// If no error occurs, this will be nil.
func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountResult, error) {
var r models.AccountResult
// Create a new request
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
}
// FetchVouchers retrieves the token holdings for a given public key from the data indexer API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) ([]dataserviceapi.TokenHoldings, error) {
var r []dataserviceapi.TokenHoldings
ep, err := url.JoinPath(config.VoucherHoldingsURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// FetchTransactions retrieves the last 10 transactions for a given public key from the data indexer API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
func (as *AccountService) FetchTransactions(ctx context.Context, publicKey string) ([]dataserviceapi.Last10TxResponse, error) {
var r []dataserviceapi.Last10TxResponse
ep, err := url.JoinPath(config.VoucherTransfersURL, publicKey)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
return r, nil
}
// VoucherData retrieves voucher metadata from the data indexer API endpoint.
// Parameters:
// - address: The voucher address.
func (as *AccountService) VoucherData(ctx context.Context, address string) (*models.VoucherDataResult, error) {
var voucherDataResult models.VoucherDataResult
ep, err := url.JoinPath(config.VoucherDataURL, address)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", ep, nil)
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &voucherDataResult)
return &voucherDataResult, err
}
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse
var errResponse api.ErrResponse
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
errResponse.Description = err.Error()
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= http.StatusBadRequest {
err := json.Unmarshal([]byte(body), &errResponse)
if err != nil {
return nil, err
}
return nil, errors.New(errResponse.Description)
}
err = json.Unmarshal([]byte(body), &okResponse)
if err != nil {
return nil, err
}
if len(okResponse.Result) == 0 {
return nil, errors.New("Empty api result")
}
return &okResponse, nil
v, err := json.Marshal(okResponse.Result)
if err != nil {
return nil, err
}
err = json.Unmarshal(v, &rcpt)
return &okResponse, err
}
func doCustodialRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.CustodialAPIKey)
return doRequest(ctx, req, rcpt)
}
func doDataRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.DataAPIKey)
return doRequest(ctx, req, rcpt)
}

View File

@ -0,0 +1 @@
Please confirm new PIN for:{{.retrieve_blocked_number}}

View File

@ -0,0 +1,14 @@
CATCH pin_entry flag_incorrect_pin 1
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
CATCH invalid_others_pin flag_valid_pin 0
CATCH pin_reset_result flag_account_authorized 1
LOAD save_others_temporary_pin 6
RELOAD save_others_temporary_pin
MOUT back 0
HALT
INCMP _ 0
LOAD check_pin_mismatch 0
RELOAD check_pin_mismatch
CATCH others_pin_mismatch flag_pin_mismatch 1
INCMP pin_entry *

View File

@ -3,5 +3,3 @@ MOUT back 0
HALT HALT
INCMP _ 0 INCMP _ 0
INCMP * pin_reset_success INCMP * pin_reset_success

View File

@ -0,0 +1 @@
Enter other's phone number:

View File

@ -0,0 +1,7 @@
CATCH no_admin_privilege flag_admin_privilege 0
LOAD reset_account_authorized 0
RELOAD reset_account_authorized
MOUT back 0
HALT
INCMP _ 0
INCMP enter_others_new_pin *

View File

@ -0,0 +1 @@
Please enter new PIN for: {{.retrieve_blocked_number}}

View File

@ -0,0 +1,12 @@
LOAD validate_blocked_number 6
RELOAD validate_blocked_number
CATCH unregistered_number flag_unregistered_number 1
LOAD retrieve_blocked_number 0
RELOAD retrieve_blocked_number
MAP retrieve_blocked_number
MOUT back 0
HALT
LOAD verify_new_pin 6
RELOAD verify_new_pin
INCMP _ 0
INCMP * confirm_others_new_pin

View File

@ -1 +0,0 @@
Guard my PIN

View File

@ -1 +0,0 @@
Linda PIN yangu

View File

@ -0,0 +1 @@
The PIN you have entered is invalid.Please try a 4 digit number instead.

View File

@ -0,0 +1,5 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP enter_others_new_pin 1
INCMP quit 9

View File

@ -0,0 +1 @@
You do not have privileges to perform this action

View File

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

View File

@ -0,0 +1 @@
The PIN you have entered is not a match

View File

@ -0,0 +1,5 @@
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 1
INCMP quit 9

View File

@ -1,8 +1,8 @@
MOUT change_pin 1 MOUT change_pin 1
MOUT reset_pin 2 MOUT reset_pin 2
MOUT guard_pin 3
MOUT back 0 MOUT back 0
HALT HALT
INCMP _ 0 INCMP my_account 0
INCMP old_pin 1 INCMP old_pin 1
INCMP enter_other_number 2
INCMP . *

View File

@ -0,0 +1 @@
PIN reset request for {{.retrieve_blocked_number}} was successful

View File

@ -0,0 +1,8 @@
LOAD retrieve_blocked_number 0
MAP retrieve_blocked_number
LOAD reset_others_pin 6
MOUT back 0
MOUT quit 9
HALT
INCMP pin_management 0
INCMP quit 9

View File

@ -6,5 +6,3 @@ MOUT quit 9
HALT HALT
INCMP main 0 INCMP main 0
INCMP quit 9 INCMP quit 9

View File

@ -17,3 +17,5 @@ flag,flag_incorrect_date_format,23,this is set when the given year of birth is i
flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid flag,flag_incorrect_voucher,24,this is set when the selected voucher is invalid
flag,flag_api_call_error,25,this is set when communication to an external service fails flag,flag_api_call_error,25,this is set when communication to an external service fails
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_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action

1 flag flag_language_set 8 checks whether the user has set their prefered language
17 flag flag_incorrect_voucher 24 this is set when the selected voucher is invalid
18 flag flag_api_call_error 25 this is set when communication to an external service fails
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

View File

@ -0,0 +1 @@
The number you have entered is either not registered with Sarafu or is invalid.

View File

@ -0,0 +1,7 @@
LOAD reset_unregistered_number 0
RELOAD reset_unregistered_number
MOUT back 0
MOUT quit 9
HALT
INCMP ^ 0
INCMP quit 9