Compare commits

...

56 Commits

Author SHA1 Message Date
7a9de79aae
return nil with error 2024-12-04 21:07:11 +03:00
862830e9de
renamed internal/storage/db.go -> internal/storage/sub_prefix_db.go for clarity 2024-12-04 20:59:46 +03:00
bc0e536d3d
updated failing tests 2024-12-04 20:55:03 +03:00
82884a75a3
Merge branch 'master' into data-items-cleanup 2024-12-04 20:45:38 +03:00
93c44861e0
Use numeric prefixes 2024-12-04 20:42:47 +03:00
4ecfc9de38
removed DATATYPE_USERSUB and replaced with DATATYPE_USERDATA 2024-12-04 20:38:52 +03:00
22c9c3e0f0 Merge pull request 'minor-bug-fixes' (#177) from minor-bug-fixes into master
Reviewed-on: urdt/ussd#177
2024-12-03 18:18:23 +01:00
a709d24520 Merge branch 'master' into minor-bug-fixes 2024-12-03 18:16:19 +01:00
e56138e416 Merge pull request 'Clear persister from handler in outer code aswell' (#200) from lash/persister-freakout into master
Reviewed-on: urdt/ussd#200
2024-12-03 17:18:11 +01:00
lash
d516584d90
Clear persister from handler in outer code aswell 2024-12-03 16:16:53 +00:00
d40a4a171f
formatted code 2024-12-03 14:12:47 +03:00
ba430a5849
add a separate function to handle ConstructName 2024-12-03 14:10:05 +03:00
0f21b01813
resolved error in the TestViewVoucher 2024-12-03 13:37:00 +03:00
10586baf0d
resolved error in the TestCheckBalance 2024-12-03 13:35:14 +03:00
e979742424
resolved error in the TestValidateRecipient 2024-12-03 13:32:18 +03:00
ff3f049226
updated the CheckAliasAddress mock 2024-12-03 13:31:30 +03:00
13b45c49da Merge branch 'master' into minor-bug-fixes 2024-12-03 12:58:26 +03:00
b615c27cf6 Merge pull request 'trigger-balance-reload' (#193) from trigger-balance-reload into master
Some checks failed
release / docker (push) Has been cancelled
Reviewed-on: urdt/ussd#193
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-12-02 16:36:26 +01:00
22e870b3e5 Merge branch 'master' into trigger-balance-reload 2024-12-02 16:36:08 +01:00
da462346f9 Merge pull request 'Always reset persister in handler' (#194) from lash/no-persister-deadlock into master
Reviewed-on: urdt/ussd#194
2024-12-02 16:17:36 +01:00
lash
406bd84875
Always reset persister in handler 2024-12-02 14:53:18 +00:00
419cd185fc Merge branch 'master' into minor-bug-fixes 2024-12-02 15:25:34 +03:00
7976e237ca Merge pull request 'voucher-details' (#179) from voucher-details into master
Reviewed-on: urdt/ussd#179
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-12-02 11:43:30 +01:00
19ec8f0817 Merge branch 'master' into voucher-details 2024-12-02 13:32:32 +03:00
ef3a3d6717
updated the TestGetVoucherDetails 2024-12-02 13:30:33 +03:00
c2019267d1
capitalize the voucher descriptions 2024-12-02 13:26:46 +03:00
0091fbcabb
fix: looped navigation 2024-12-02 09:03:04 +03:00
aa7497573e
removed unused code 2024-11-30 15:29:28 +03:00
54c1fe51ef
update the active voucher data when checking the current vouchers 2024-11-30 15:28:21 +03:00
7a86b2ad3b
updated the UpdateVoucherData description 2024-11-30 15:26:13 +03:00
6b23c284e5
check vouchers before checking the balance 2024-11-30 15:24:14 +03:00
aab6660edd
Capitalize menu items 2024-11-29 15:39:27 +03:00
c46f41e25f
Format the balance to 2 decimal places 2024-11-29 14:47:22 +03:00
00c0445eed
show name without depending on family name being set 2024-11-28 11:42:47 +03:00
c8c6b05b8a Merge branch 'master' into minor-bug-fixes 2024-11-26 15:31:45 +03:00
08ff1056d7 Validate aliases, addresses and phone numbers in the send menu (#176)
Some checks failed
release / docker (push) Has been cancelled
- Update the phone number regex
- Check whether the recipient is a valid phone number, alias or address

Reviewed-on: urdt/ussd#176
Co-authored-by: alfred-mk <alfredmwaik@gmail.com>
Co-committed-by: alfred-mk <alfredmwaik@gmail.com>
2024-11-26 07:24:57 +01:00
0f4a7e900f Merge pull request 'feat: upgrade go-vise dep, minor cleanups to go.mod' (#182) from sohail/upgrade-deps into master
Reviewed-on: urdt/ussd#182
Reviewed-by: lash <accounts-grassrootseconomics@holbrook.no>
2024-11-26 07:02:07 +01:00
2d6c434bde
feat: upgrade go-vise dep, minor cleanups to go.mod
* added stub for Dump()
2024-11-25 09:56:24 +03:00
f5d2644031 Merge pull request 'account-statement' (#126) from account-statement into master
Reviewed-on: urdt/ussd#126
2024-11-22 19:24:08 +01:00
09b4fa2860 Merge branch 'master' into account-statement 2024-11-22 19:23:59 +01:00
a748c1b6b2
chore: fix old reference 2024-11-22 11:53:13 +03:00
a17cf78d29
Merge remote-tracking branch 'refs/remotes/origin/minor-bug-fixes' into minor-bug-fixes 2024-11-22 11:35:57 +03:00
9847433e0a
use _ for back navigation 2024-11-22 11:30:13 +03:00
7ce50398d1
use the language translation instead of hardcoded eng 2024-11-21 15:54:00 +03:00
e30bc177e9
fixed typo and added a new translation 2024-11-21 15:52:07 +03:00
b9ff467c0c
use the correct balance 2024-11-21 15:51:04 +03:00
1174500e3f
add test for voucher details 2024-11-21 15:19:36 +03:00
07df450b3c
include labels to define the symbol and balance while selecting a voucher 2024-11-21 15:15:15 +03:00
b8d938d3aa
add voucher details 2024-11-21 13:04:19 +03:00
d1e9340ea9
add voucher details 2024-11-21 13:03:43 +03:00
8925e26c4c
refactor check for valid yob 2024-11-21 11:25:47 +03:00
9b89462797
add function to check validity of provided yob 2024-11-21 11:21:16 +03:00
7880294c6f
set eng as default language 2024-11-20 17:14:25 +03:00
451b15fb6b
explicit set_language reload 2024-11-20 17:13:14 +03:00
d20700ca74
fix size limit error 2024-11-20 16:39:50 +03:00
9cf1cbe425
add swahili translation for catch node 2024-11-20 16:36:18 +03:00
43 changed files with 507 additions and 194 deletions

View File

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

View File

@ -32,7 +32,18 @@ const (
DATA_PUBLIC_KEY_REVERSE
DATA_ACTIVE_DECIMAL
DATA_ACTIVE_ADDRESS
DATA_TRANSACTIONS
DATA_PREFIX_SYMBOLS
DATA_PREFIX_BALANCES
DATA_PREFIX_DECIMALS
DATA_PREFIX_ADDRESSES
DATA_PREFIX_TX_SENDERS
DATA_PREFIX_TX_RECIPIENTS
DATA_PREFIX_TX_VALUES
DATA_PREFIX_TX_ADDRESSES
DATA_PREFIX_TX_HASHES
DATA_PREFIX_TX_DATES
DATA_PREFIX_TX_SYMBOLS
DATA_PREFIX_TX_DECIMALS
)
var (
@ -69,3 +80,10 @@ func StringToDataTyp(str string) (DataTyp, error) {
return 0, errors.New("invalid DataTyp string")
}
}
// Convert DataTyp to []byte
func (d DataTyp) ToBytes() []byte {
bytes := make([]byte, 2)
binary.BigEndian.PutUint16(bytes, uint16(d))
return bytes
}

73
common/recipient.go Normal file
View File

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

View File

@ -57,25 +57,25 @@ func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetad
// GetTransferData retrieves and matches transfer data
// returns a formatted string of the full transaction/statement
func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) {
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"}
data := make(map[string]string)
keys := []DataTyp{DATA_PREFIX_TX_SENDERS, DATA_PREFIX_TX_RECIPIENTS, DATA_PREFIX_TX_VALUES, DATA_PREFIX_TX_ADDRESSES, DATA_PREFIX_TX_HASHES, DATA_PREFIX_TX_DATES, DATA_PREFIX_TX_SYMBOLS}
data := make(map[DataTyp]string)
for _, key := range keys {
value, err := db.Get(ctx, []byte(key))
value, err := db.Get(ctx, key.ToBytes())
if err != nil {
return "", fmt.Errorf("failed to get %s: %v", key, err)
return "", fmt.Errorf("failed to get %s: %v", key.ToBytes(), err)
}
data[key] = string(value)
}
// Split the data
senders := strings.Split(string(data["txfrom"]), "\n")
recipients := strings.Split(string(data["txto"]), "\n")
values := strings.Split(string(data["txval"]), "\n")
addresses := strings.Split(string(data["txaddr"]), "\n")
hashes := strings.Split(string(data["txhash"]), "\n")
dates := strings.Split(string(data["txdate"]), "\n")
syms := strings.Split(string(data["txsym"]), "\n")
senders := strings.Split(string(data[DATA_PREFIX_TX_SENDERS]), "\n")
recipients := strings.Split(string(data[DATA_PREFIX_TX_RECIPIENTS]), "\n")
values := strings.Split(string(data[DATA_PREFIX_TX_VALUES]), "\n")
addresses := strings.Split(string(data[DATA_PREFIX_TX_ADDRESSES]), "\n")
hashes := strings.Split(string(data[DATA_PREFIX_TX_HASHES]), "\n")
dates := strings.Split(string(data[DATA_PREFIX_TX_DATES]), "\n")
syms := strings.Split(string(data[DATA_PREFIX_TX_SYMBOLS]), "\n")
// Check if index is within range
if index < 1 || index > len(senders) {

View File

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

View File

@ -86,16 +86,16 @@ func TestGetVoucherData(t *testing.T) {
spdb := storage.NewSubPrefixDb(db, []byte("vouchers"))
// Test voucher data
mockData := map[string][]byte{
"sym": []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
mockData := map[DataTyp][]byte{
DATA_PREFIX_SYMBOLS: []byte("1:SRF\n2:MILO"),
DATA_PREFIX_BALANCES: []byte("1:100\n2:200"),
DATA_PREFIX_DECIMALS: []byte("1:6\n2:4"),
DATA_PREFIX_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
}
// Put the data
for key, value := range mockData {
err = spdb.Put(ctx, []byte(key), []byte(value))
err = spdb.Put(ctx, []byte(key.ToBytes()), []byte(value))
if err != nil {
t.Fatal(err)
}

View File

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

36
go.mod
View File

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

8
go.sum
View File

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

View File

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

View File

@ -10,7 +10,6 @@ import (
"strings"
"git.defalsify.org/vise.git/asm"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/db"
@ -25,22 +24,26 @@ import (
"gopkg.in/leonelquinteros/gotext.v1"
"git.grassecon.net/urdt/ussd/internal/storage"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
var (
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
scriptDir = path.Join("services", "registration")
translationDir = path.Join(scriptDir, "locale")
okResponse *api.OKResponse
errResponse *api.ErrResponse
)
// Define the regex patterns as constants
// Define the regex patterns as constants
const (
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
pinPattern = `^\d{4}$`
)
// isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin)
return match
}
// FlagManager handles centralized flag management
type FlagManager struct {
parser *asm.FlagParser
@ -95,17 +98,6 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
return h, nil
}
// isValidPIN checks whether the given input is a 4 digit number
func isValidPIN(pin string) bool {
match, _ := regexp.MatchString(pinPattern, pin)
return match
}
func isValidPhoneNumber(phonenumber string) bool {
match, _ := regexp.MatchString(phoneRegex, phonenumber)
return match
}
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
if h.pe != nil {
panic("persister already set")
@ -120,6 +112,9 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
return r, nil
}
defer func() {
h.Exit()
}()
h.st = h.pe.GetState()
h.ca = h.pe.GetMemory()
@ -139,13 +134,16 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource
logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
return r, fmt.Errorf("cannot get state and memory for handler")
}
h.pe = nil
logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca)
return r, nil
}
func (h *Handlers) Exit() {
h.pe = nil
}
// SetLanguage sets the language across the menu
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
@ -154,7 +152,8 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
code := strings.Split(symbol, "_")[1]
if !utils.IsValidISO639(code) {
return res, nil
//Fallback to english instead?
code = "eng"
}
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
res.Content = code
@ -763,12 +762,11 @@ func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (res
return res, nil
}
if len(date) == 4 {
if utils.IsValidYOb(date) {
res.FlagReset = append(res.FlagReset, flag_incorrect_date_format)
} else {
res.FlagSet = append(res.FlagSet, flag_incorrect_date_format)
}
return res, nil
}
@ -817,7 +815,17 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, err
}
res.Content = l.Get("Balance: %s\n", fmt.Sprintf("%s %s", activeBal, activeSym))
// Convert activeBal from []byte to float64
balFloat, err := strconv.ParseFloat(string(activeBal), 64)
if err != nil {
logg.ErrorCtxf(ctx, "failed to parse activeBal as float", "value", string(activeBal), "error", err)
return res, err
}
// Format to 2 decimal places
balStr := fmt.Sprintf("%.2f %s", balFloat, activeSym)
res.Content = l.Get("Balance: %s\n", balStr)
return res, nil
}
@ -877,7 +885,7 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
}
blockedNumber := string(input)
_, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY)
if !isValidPhoneNumber(blockedNumber) {
if !common.IsValidPhoneNumber(blockedNumber) {
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
}
@ -898,10 +906,9 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
return res, nil
}
// ValidateRecipient validates that the given input is a valid phone number.
// ValidateRecipient validates that the given input is valid.
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var err error
store := h.userdataStore
sessionId, ok := ctx.Value("SessionId").(string)
@ -909,13 +916,16 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, fmt.Errorf("missing session")
}
recipient := string(input)
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
recipient := string(input)
if recipient != "0" {
if !isValidPhoneNumber(recipient) {
recipientType, err := common.CheckRecipient(recipient)
if err != nil {
// Invalid recipient format (not a phone number, address, or valid alias format)
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
res.Content = recipient
@ -929,25 +939,61 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
return res, err
}
publicKey, err := store.ReadEntry(ctx, recipient, common.DATA_PUBLIC_KEY)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Unregistered number")
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
res.Content = recipient
return res, nil
switch recipientType {
case "phone number":
// format the phone number
formattedNumber, err := common.FormatPhoneNumber(recipient)
if err != nil {
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err)
return res, err
}
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Check if the phone number is registered
publicKey, err := store.ReadEntry(ctx, formattedNumber, common.DATA_PUBLIC_KEY)
if err != nil {
if db.IsNotFound(err) {
logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient)
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
res.Content = recipient
return res, nil
}
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
return res, nil
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Save the publicKey as the recipient
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
return res, err
}
case "address":
// Save the valid Ethereum address as the recipient
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", recipient, "error", err)
return res, err
}
case "alias":
// Call the API to validate and retrieve the address for the alias
r, aliasErr := h.accountService.CheckAliasAddress(ctx, recipient)
if aliasErr != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
res.Content = recipient
logg.ErrorCtxf(ctx, "failed on CheckAliasAddress", "error", aliasErr)
return res, err
}
// Alias validation succeeded, save the Ethereum address
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(r.Address))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", r.Address, "error", err)
return res, err
}
}
}
@ -1356,14 +1402,7 @@ func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte)
offerings := getEntryOrDefault(store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS))
// Construct the full name
name := defaultValue
if familyName != defaultValue {
if firstName == defaultValue {
name = familyName
} else {
name = firstName + " " + familyName
}
}
name := utils.ConstructName(firstName, familyName, defaultValue)
// Calculate age from year of birth
age := defaultValue
@ -1502,18 +1541,50 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
return res, nil
}
// check the current active sym and update the data
activeSym, _ := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if activeSym != nil {
activeSymStr := string(activeSym)
// Find the matching voucher data
var activeData *dataserviceapi.TokenHoldings
for _, voucher := range vouchersResp {
if voucher.TokenSymbol == activeSymStr {
activeData = &voucher
break
}
}
if activeData == nil {
logg.ErrorCtxf(ctx, "activeSym not found in vouchers", "activeSym", activeSymStr)
return res, fmt.Errorf("activeSym %s not found in vouchers", activeSymStr)
}
// Scale down the balance
scaledBalance := common.ScaleDownBalance(activeData.Balance, activeData.TokenDecimals)
// Update the balance field with the scaled value
activeData.Balance = scaledBalance
// Pass the matching voucher data to UpdateVoucherData
if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, activeData); err != nil {
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
return res, err
}
}
data := common.ProcessVouchers(vouchersResp)
// Store all voucher data
dataMap := map[string]string{
"sym": data.Symbols,
"bal": data.Balances,
"deci": data.Decimals,
"addr": data.Addresses,
dataMap := map[common.DataTyp]string{
common.DATA_PREFIX_SYMBOLS: data.Symbols,
common.DATA_PREFIX_BALANCES: data.Balances,
common.DATA_PREFIX_DECIMALS: data.Decimals,
common.DATA_PREFIX_ADDRESSES: data.Addresses,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
if err := h.prefixDb.Put(ctx, []byte(key.ToBytes()), []byte(value)); err != nil {
return res, nil
}
}
@ -1526,7 +1597,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
var res resource.Result
// Read vouchers from the store
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
voucherData, err := h.prefixDb.Get(ctx, common.DATA_PREFIX_SYMBOLS.ToBytes())
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
return res, err
@ -1546,6 +1617,10 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
return res, fmt.Errorf("missing session")
}
code := codeFromCtx(ctx)
l := gotext.NewLocale(translationDir, code)
l.AddDomain("default")
flag_incorrect_voucher, _ := h.flagManager.GetFlag("flag_incorrect_voucher")
inputStr := string(input)
@ -1570,7 +1645,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
}
res.FlagReset = append(res.FlagReset, flag_incorrect_voucher)
res.Content = fmt.Sprintf("%s\n%s", metadata.TokenSymbol, metadata.Balance)
res.Content = l.Get("Symbol: %s\nBalance: %s", metadata.TokenSymbol, metadata.Balance)
return res, nil
}
@ -1626,10 +1701,9 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by
return res, nil
}
tokenSymbol := voucherData.TokenSymbol
tokenName := voucherData.TokenName
res.Content = fmt.Sprintf("%s %s", tokenSymbol, tokenName)
res.Content = fmt.Sprintf(
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", voucherData.TokenName, voucherData.TokenSymbol, voucherData.TokenCommodity, voucherData.TokenLocation,
)
return res, nil
}
@ -1669,19 +1743,19 @@ func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []by
data := common.ProcessTransfers(transactionsResp)
// Store all transaction data
dataMap := map[string]string{
"txfrom": data.Senders,
"txto": data.Recipients,
"txval": data.TransferValues,
"txaddr": data.Addresses,
"txhash": data.TxHashes,
"txdate": data.Dates,
"txsym": data.Symbols,
"txdeci": data.Decimals,
dataMap := map[common.DataTyp]string{
common.DATA_PREFIX_TX_SENDERS: data.Senders,
common.DATA_PREFIX_TX_RECIPIENTS: data.Recipients,
common.DATA_PREFIX_TX_VALUES: data.TransferValues,
common.DATA_PREFIX_TX_ADDRESSES: data.Addresses,
common.DATA_PREFIX_TX_HASHES: data.TxHashes,
common.DATA_PREFIX_TX_DATES: data.Dates,
common.DATA_PREFIX_TX_SYMBOLS: data.Symbols,
common.DATA_PREFIX_TX_DECIMALS: data.Decimals,
}
for key, value := range dataMap {
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
if err := h.prefixDb.Put(ctx, []byte(key.ToBytes()), []byte(value)); err != nil {
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
return res, err
}
@ -1707,22 +1781,22 @@ func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []
}
// Read transactions from the store and format them
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom"))
TransactionSenders, err := h.prefixDb.Get(ctx, common.DATA_PREFIX_TX_SENDERS.ToBytes())
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
return res, err
}
TransactionSyms, err := h.prefixDb.Get(ctx, []byte("txsym"))
TransactionSyms, err := h.prefixDb.Get(ctx, common.DATA_PREFIX_TX_SYMBOLS.ToBytes())
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
return res, err
}
TransactionValues, err := h.prefixDb.Get(ctx, []byte("txval"))
TransactionValues, err := h.prefixDb.Get(ctx, common.DATA_PREFIX_TX_VALUES.ToBytes())
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
return res, err
}
TransactionDates, err := h.prefixDb.Get(ctx, []byte("txdate"))
TransactionDates, err := h.prefixDb.Get(ctx, common.DATA_PREFIX_TX_DATES.ToBytes())
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
return res, err

View File

@ -1498,10 +1498,10 @@ func TestValidateRecipient(t *testing.T) {
}{
{
name: "Test with invalid recepient",
input: []byte("9234adf5"),
input: []byte("7?1234"),
expectedResult: resource.Result{
FlagSet: []uint32{flag_invalid_recipient},
Content: "9234adf5",
Content: "7?1234",
},
},
{
@ -1517,22 +1517,40 @@ func TestValidateRecipient(t *testing.T) {
input: []byte("0711223344"),
expectedResult: resource.Result{},
},
{
name: "Test with address",
input: []byte("0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9"),
expectedResult: resource.Result{},
},
{
name: "Test with alias recepient",
input: []byte("alias123"),
expectedResult: resource.Result{},
},
}
// store a public key for the valid recipient
err = store.WriteEntry(ctx, "0711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
err = store.WriteEntry(ctx, "+254711223344", common.DATA_PUBLIC_KEY, []byte(publicKey))
if err != nil {
t.Fatal(err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockAccountService := new(mocks.MockAccountService)
// Create the Handlers instance
h := &Handlers{
flagManager: fm.parser,
userdataStore: store,
flagManager: fm.parser,
userdataStore: store,
accountService: mockAccountService,
}
aliasResponse := &dataserviceapi.AliasAddress{
Address: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
}
mockAccountService.On("CheckAliasAddress", string(tt.input)).Return(aliasResponse, nil)
// Call the method
res, err := h.ValidateRecipient(ctx, "validate_recepient", tt.input)
@ -1564,7 +1582,7 @@ func TestCheckBalance(t *testing.T) {
publicKey: "0X98765432109",
activeSym: "ETH",
activeBal: "1.5",
expectedResult: resource.Result{Content: "Balance: 1.5 ETH\n"},
expectedResult: resource.Result{Content: "Balance: 1.50 ETH\n"},
expectError: false,
},
}
@ -1919,7 +1937,7 @@ func TestCheckVouchers(t *testing.T) {
assert.NoError(t, err)
// Read voucher sym data from the store
voucherData, err := spdb.Get(ctx, []byte("sym"))
voucherData, err := spdb.Get(ctx, common.DATA_PREFIX_SYMBOLS.ToBytes())
if err != nil {
t.Fatal(err)
}
@ -1943,7 +1961,7 @@ func TestGetVoucherList(t *testing.T) {
expectedSym := []byte("1:SRF\n2:MILO")
// Put voucher sym data from the store
err := spdb.Put(ctx, []byte("sym"), expectedSym)
err := spdb.Put(ctx, common.DATA_PREFIX_SYMBOLS.ToBytes(), expectedSym)
if err != nil {
t.Fatal(err)
}
@ -1973,16 +1991,16 @@ func TestViewVoucher(t *testing.T) {
}
// Define mock voucher data
mockData := map[string][]byte{
"sym": []byte("1:SRF\n2:MILO"),
"bal": []byte("1:100\n2:200"),
"deci": []byte("1:6\n2:4"),
"addr": []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
mockData := map[common.DataTyp][]byte{
common.DATA_PREFIX_SYMBOLS: []byte("1:SRF\n2:MILO"),
common.DATA_PREFIX_BALANCES: []byte("1:100\n2:200"),
common.DATA_PREFIX_DECIMALS: []byte("1:6\n2:4"),
common.DATA_PREFIX_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
}
// Put the data
for key, value := range mockData {
err = spdb.Put(ctx, []byte(key), []byte(value))
err = spdb.Put(ctx, []byte(key.ToBytes()), []byte(value))
if err != nil {
t.Fatal(err)
}
@ -1990,7 +2008,7 @@ func TestViewVoucher(t *testing.T) {
res, err := h.ViewVoucher(ctx, "view_voucher", []byte("1"))
assert.NoError(t, err)
assert.Equal(t, res.Content, "SRF\n100")
assert.Equal(t, res.Content, "Symbol: SRF\nBalance: 100")
}
func TestSetVoucher(t *testing.T) {
@ -2024,3 +2042,42 @@ func TestSetVoucher(t *testing.T) {
assert.Equal(t, string(tempData.TokenSymbol), res.Content)
}
func TestGetVoucherDetails(t *testing.T) {
ctx, store := InitializeTestStore(t)
fm, err := NewFlagManager(flagsPath)
if err != nil {
t.Logf(err.Error())
}
mockAccountService := new(mocks.MockAccountService)
sessionId := "session123"
ctx = context.WithValue(ctx, "SessionId", sessionId)
expectedResult := resource.Result{}
tokA_AAddress := "0x0000000000000000000000000000000000000000"
h := &Handlers{
userdataStore: store,
flagManager: fm.parser,
accountService: mockAccountService,
}
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_ADDRESS, []byte(tokA_AAddress))
if err != nil {
t.Fatal(err)
}
tokenDetails := &models.VoucherDataResult{
TokenName: "Token A",
TokenSymbol: "TOKA",
TokenLocation: "Kilifi,Kenya",
TokenCommodity: "Farming",
}
expectedResult.Content = fmt.Sprintf(
"Name: %s\nSymbol: %s\nCommodity: %s\nLocation: %s", tokenDetails.TokenName, tokenDetails.TokenSymbol, tokenDetails.TokenCommodity, tokenDetails.TokenLocation,
)
mockAccountService.On("VoucherData", string(tokA_AAddress)).Return(tokenDetails, nil)
res, err := h.GetVoucherDetails(ctx, "SessionId", []byte(""))
assert.NoError(t, err)
assert.Equal(t, expectedResult, res)
}

View File

@ -4,8 +4,8 @@ import (
"context"
"git.defalsify.org/vise.git/db"
"git.defalsify.org/vise.git/lang"
gdbmdb "git.defalsify.org/vise.git/db/gdbm"
"git.defalsify.org/vise.git/lang"
)
var (
@ -114,3 +114,8 @@ func(tdb *ThreadGdbmDb) Close() error {
tdb.db = nil
return err
}
func(tdb *ThreadGdbmDb) Dump(_ context.Context, _ []byte) (*db.Dumper, error) {
logg.Warnf("method not implemented for thread gdbm db")
return nil, nil
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
package utils
import "time"
import (
"strconv"
"time"
)
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
// It adjusts for cases where the current date is before the birthday in the current year.
@ -25,11 +28,29 @@ func CalculateAge(birthdate, today time.Time) int {
// It subtracts the YOB from the current year to determine the age.
//
// Parameters:
// yob: The year of birth as an integer.
//
// yob: The year of birth as an integer.
//
// Returns:
// The calculated age as an integer.
//
// The calculated age as an integer.
func CalculateAgeWithYOB(yob int) int {
currentYear := time.Now().Year()
return currentYear - yob
}
currentYear := time.Now().Year()
return currentYear - yob
}
//IsValidYob checks if the provided yob can be considered valid
func IsValidYOb(yob string) bool {
currentYear := time.Now().Year()
yearOfBirth, err := strconv.ParseInt(yob, 10, 64)
if err != nil {
return false
}
if yearOfBirth >= 1900 && int(yearOfBirth) <= currentYear {
return true
} else {
return false
}
}

17
internal/utils/name.go Normal file
View File

@ -0,0 +1,17 @@
package utils
func ConstructName(firstName, familyName, defaultValue string) string {
name := defaultValue
if familyName != defaultValue {
if firstName != defaultValue {
name = firstName + " " + familyName
} else {
name = familyName
}
} else {
if firstName != defaultValue {
name = firstName
}
}
return name
}

View File

@ -54,7 +54,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
},
{
"input": "1",
@ -62,7 +62,7 @@
},
{
"input": "1234",
"expectedContent": "Select language:\n0:english\n1:kiswahili"
"expectedContent": "Select language:\n0:English\n1:Kiswahili"
},
{
"input": "0",
@ -95,7 +95,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
},
{
"input": "1",
@ -141,7 +141,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
"expectedContent": "Incorrect PIN\n1:Retry\n9:Quit"
},
{
"input": "1",

View File

@ -7,11 +7,11 @@
"steps": [
{
"input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
},
{
"input": "0",
@ -40,11 +40,11 @@
"steps": [
{
"input": "",
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:english\n1:kiswahili"
"expectedContent": "Welcome to Sarafu Network\nPlease select a language\n0:English\n1:Kiswahili"
},
{
"input": "0",
"expectedContent": "Do you agree to terms and conditions?\n0:yes\n1:no"
"expectedContent": "Do you agree to terms and conditions?\n0:Yes\n1:No"
},
{
"input": "1",
@ -61,7 +61,7 @@
},
{
"input": "1",
"expectedContent": "Enter recipient's phone number:\n0:Back"
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back"
},
{
"input": "000",
@ -69,7 +69,7 @@
},
{
"input": "1",
"expectedContent": "Enter recipient's phone number:\n0:Back"
"expectedContent": "Enter recipient's phone number/address/alias:\n0:Back"
},
{
"input": "0712345678",

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
English

View File

@ -1 +1 @@
Incorrect pin
Incorrect PIN

View File

@ -1 +1 @@
PIN mpya na udhibitisho wa pin mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.
PIN mpya na udhibitisho wa PIN mpya hazilingani.Tafadhali jaribu tena.Kwa usaidizi piga simu +254757628885.

View File

@ -0,0 +1 @@
Kiswahili

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
Next

View File

@ -0,0 +1 @@
Mbele

View File

@ -1 +1 @@
no
No

View File

@ -1 +1 @@
la
La

View File

@ -0,0 +1 @@
Prev

View File

@ -0,0 +1 @@
Nyuma

View File

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

View File

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

View File

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

View File

@ -1 +1 @@
yes
Yes

View File

@ -1 +1 @@
ndio
Ndio