account-statement #126
115
common/transfer_statements.go
Normal file
115
common/transfer_statements.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||||
|
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransferMetadata helps organize data fields
|
||||||
|
type TransferMetadata struct {
|
||||||
|
Senders string
|
||||||
|
Recipients string
|
||||||
|
TransferValues string
|
||||||
|
Addresses string
|
||||||
|
TxHashes string
|
||||||
|
Dates string
|
||||||
|
Symbols string
|
||||||
|
Decimals string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessTransfers converts transfers into formatted strings
|
||||||
|
func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetadata {
|
||||||
|
var data TransferMetadata
|
||||||
|
var senders, recipients, transferValues, addresses, txHashes, dates, symbols, decimals []string
|
||||||
|
|
||||||
|
for _, t := range transfers {
|
||||||
|
senders = append(senders, t.Sender)
|
||||||
|
recipients = append(recipients, t.Recipient)
|
||||||
|
|
||||||
|
// Scale down the amount
|
||||||
|
scaledBalance := ScaleDownBalance(t.TransferValue, t.TokenDecimals)
|
||||||
|
transferValues = append(transferValues, scaledBalance)
|
||||||
|
|
||||||
|
addresses = append(addresses, t.ContractAddress)
|
||||||
|
txHashes = append(txHashes, t.TxHash)
|
||||||
|
dates = append(dates, fmt.Sprintf("%s", t.DateBlock))
|
||||||
|
symbols = append(symbols, t.TokenSymbol)
|
||||||
|
decimals = append(decimals, t.TokenDecimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Senders = strings.Join(senders, "\n")
|
||||||
|
data.Recipients = strings.Join(recipients, "\n")
|
||||||
|
data.TransferValues = strings.Join(transferValues, "\n")
|
||||||
|
data.Addresses = strings.Join(addresses, "\n")
|
||||||
|
data.TxHashes = strings.Join(txHashes, "\n")
|
||||||
|
data.Dates = strings.Join(dates, "\n")
|
||||||
|
data.Symbols = strings.Join(symbols, "\n")
|
||||||
|
data.Decimals = strings.Join(decimals, "\n")
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransferData retrieves and matches transfer data
|
||||||
|
// returns a formatted string of the full transaction/statement
|
||||||
|
func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) {
|
||||||
|
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"}
|
||||||
|
|||||||
|
data := make(map[string]string)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
value, err := db.Get(ctx, []byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get %s: %v", key, err)
|
||||||
|
}
|
||||||
|
data[key] = string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the data
|
||||||
|
senders := strings.Split(string(data["txfrom"]), "\n")
|
||||||
|
recipients := strings.Split(string(data["txto"]), "\n")
|
||||||
|
values := strings.Split(string(data["txval"]), "\n")
|
||||||
|
addresses := strings.Split(string(data["txaddr"]), "\n")
|
||||||
|
hashes := strings.Split(string(data["txhash"]), "\n")
|
||||||
|
dates := strings.Split(string(data["txdate"]), "\n")
|
||||||
|
syms := strings.Split(string(data["txsym"]), "\n")
|
||||||
|
|
||||||
|
// Adjust for 0-based indexing
|
||||||
|
i := index - 1
|
||||||
|
transactionType := "received"
|
||||||
|
party := fmt.Sprintf("from: %s", strings.TrimSpace(senders[i]))
|
||||||
|
if strings.TrimSpace(senders[i]) == publicKey {
|
||||||
|
transactionType = "sent"
|
||||||
|
party = fmt.Sprintf("to: %s", strings.TrimSpace(recipients[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedDate := formatDate(strings.TrimSpace(dates[i]))
|
||||||
|
|
||||||
|
// Build the full transaction detail
|
||||||
|
detail := fmt.Sprintf(
|
||||||
|
"%s %s %s\n%s\ncontract address: %s\ntxhash: %s\ndate: %s",
|
||||||
|
transactionType,
|
||||||
|
strings.TrimSpace(values[i]),
|
||||||
|
strings.TrimSpace(syms[i]),
|
||||||
|
party,
|
||||||
|
strings.TrimSpace(addresses[i]),
|
||||||
|
strings.TrimSpace(hashes[i]),
|
||||||
|
formattedDate,
|
||||||
|
)
|
||||||
|
|
||||||
|
return detail, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format date in desired output
|
||||||
|
func formatDate(dateStr string) string {
|
||||||
|
fmt.Println(dateStr)
|
||||||
|
parsedDate, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dateStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error parsing date:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
lash
commented
this is hardcoded..? this is hardcoded..?
Alfred-mk
commented
Yes it is. This defines the layout that will be used to parse the dateStr func time.Parse(layout string, value string) Yes it is. This defines the layout that will be used to parse the dateStr
func time.Parse(layout string, value string)
|
|||||||
|
return parsedDate.Format("2006-01-02 03:04:05 PM")
|
||||||
|
}
|
@ -118,7 +118,9 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
|
|||||||
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
|
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
|
||||||
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
|
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
|
||||||
ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo)
|
ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo)
|
||||||
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactions)
|
ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions)
|
||||||
|
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList)
|
||||||
|
ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement)
|
||||||
|
|
||||||
return ussdHandlers, nil
|
return ussdHandlers, nil
|
||||||
}
|
}
|
||||||
|
@ -1634,11 +1634,171 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactions retrieves the transactions from the API using the "PublicKey"
|
// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb
|
||||||
func (h *Handlers) GetTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) CheckTransactions(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
|
||||||
res.Content = "Transaction list"
|
flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers")
|
||||||
|
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||||
|
|
||||||
|
store := h.userdataStore
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch transactions from the API using the public key
|
||||||
|
transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey))
|
||||||
|
if err != nil {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
|
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if there are no transactions
|
||||||
|
if len(transactionsResp) == 0 {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_no_transfers)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := common.ProcessTransfers(transactionsResp)
|
||||||
|
|
||||||
|
// Store all transaction data
|
||||||
|
dataMap := map[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range dataMap {
|
||||||
|
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_no_transfers)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionsList fetches the list of transactions and formats them
|
||||||
|
func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read transactions from the store and format them
|
||||||
|
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom"))
|
||||||
|
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"))
|
||||||
|
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"))
|
||||||
|
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"))
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the data
|
||||||
|
senders := strings.Split(string(TransactionSenders), "\n")
|
||||||
|
syms := strings.Split(string(TransactionSyms), "\n")
|
||||||
|
values := strings.Split(string(TransactionValues), "\n")
|
||||||
|
dates := strings.Split(string(TransactionDates), "\n")
|
||||||
|
|
||||||
|
var formattedTransactions []string
|
||||||
|
for i := 0; i < len(senders); i++ {
|
||||||
|
sender := strings.TrimSpace(senders[i])
|
||||||
|
sym := strings.TrimSpace(syms[i])
|
||||||
|
value := strings.TrimSpace(values[i])
|
||||||
|
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
|
||||||
|
|
||||||
|
status := "received"
|
||||||
|
if sender == string(publicKey) {
|
||||||
|
status = "sent"
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date))
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Content = strings.Join(formattedTransactions, "\n")
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewTransactionStatement retrieves the token holding and balance from the subprefixDB
|
||||||
|
// and displays it to the user for them to select it
|
||||||
|
func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
|
var res resource.Result
|
||||||
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
|
if !ok {
|
||||||
|
return res, fmt.Errorf("missing session")
|
||||||
|
}
|
||||||
|
store := h.userdataStore
|
||||||
|
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||||
|
if err != nil {
|
||||||
|
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement")
|
||||||
|
|
||||||
|
inputStr := string(input)
|
||||||
|
if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" {
|
||||||
lash
commented
Here we are back to checking menu selectors in the handler code. What functionality is missing to be able to avoid this? Here we are back to checking menu selectors in the handler code. What functionality is missing to be able to avoid this?
Alfred-mk
commented
We can discuss this in the meeting, on how to use the lateral navigation indicator as this just prevents the navigation input from being processed We can discuss this in the meeting, on how to use the lateral navigation indicator as this just prevents the navigation input from being processed
|
|||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert input string to integer
|
||||||
|
index, err := strconv.Atoi(strings.TrimSpace(inputStr))
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("invalid input: must be a number between 1 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < 1 || index > 10 {
|
||||||
|
return res, fmt.Errorf("invalid input: index must be between 1 and 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
statement, err := common.GetTransferData(ctx, h.prefixDb, string(publicKey), index)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("failed to retrieve transfer data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if statement == "" {
|
||||||
|
res.FlagSet = append(res.FlagSet, flag_incorrect_statement)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||||
|
res.Content = statement
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user
made an issue to be addressed later, regarding key lengths.
#180
This is well noted and will be addressed