diff --git a/go.mod b/go.mod index e6bd876..25c8c1e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.grassecon.net/grassrootseconomics/sarafu-vise go 1.23.4 require ( - git.defalsify.org/vise.git v0.3.2-0.20250326034808-b9c2294cbf1a + git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2 diff --git a/go.sum b/go.sum index f6c0ec5..301a3ee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -git.defalsify.org/vise.git v0.3.2-0.20250326034808-b9c2294cbf1a h1:5uLBUZC6armYgBPkuNEsQPtqT3qZxRfNP4HUdkhjm4I= -git.defalsify.org/vise.git v0.3.2-0.20250326034808-b9c2294cbf1a/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.3.1 h1:A6FhMcur09ft/JzUPGXR+KpA17fltfeBnasyvLMZmq4= +git.defalsify.org/vise.git v0.3.1/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805 h1:FnT39aqXcP5YWhwPDBABopSjCu2SlbPFoOVitSpAVxU= +git.defalsify.org/vise.git v0.3.2-0.20250401123711-d481b04a6805/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d h1:5mzLas+jxTUtusOKx4XvU+n2QvrV/mH17MnJRy46siQ= git.grassecon.net/grassrootseconomics/common v0.0.0-20250121134736-ba8cbbccea7d/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250310093912-8145b4bd004b h1:xiTpaqWWoF5qcnarY/9ZkT6aVdnKwqztb2gzIahJn4w= diff --git a/handlers/application/menuhandler.go b/handlers/application/menuhandler.go index 5cbea34..3077ef2 100644 --- a/handlers/application/menuhandler.go +++ b/handlers/application/menuhandler.go @@ -1946,9 +1946,11 @@ func (h *MenuHandlers) InitiateTransaction(ctx context.Context, sym string, inpu return res, nil } -// SetDefaultVoucher retrieves the current vouchers -// and sets the first as the default voucher, if no active voucher is set. -func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input []byte) (resource.Result, error) { +// ManageVouchers retrieves the token holdings from the API using the "PublicKey" and +// 1. sets the first as the default voucher if no active voucher is set. +// 2. Stores list of vouchers +// 3. updates the balance of the active voucher +func (h *MenuHandlers) ManageVouchers(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result userStore := h.userdataStore @@ -1959,30 +1961,31 @@ func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input flag_no_active_voucher, _ := h.flagManager.GetFlag("flag_no_active_voucher") - // check if the user has an active sym - _, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) + publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) + if err != nil { + logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err) + return res, err + } + + // Fetch vouchers from API + vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_no_active_voucher) + return res, nil + } + + if len(vouchersResp) == 0 { + res.FlagSet = append(res.FlagSet, flag_no_active_voucher) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_no_active_voucher) + + // Check if user has an active voucher with proper error handling + activeSym, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) if err != nil { if db.IsNotFound(err) { - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Fetch vouchers from the API using the public key - vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) - if err != nil { - res.FlagSet = append(res.FlagSet, flag_no_active_voucher) - return res, nil - } - - // Return if there is no voucher - if len(vouchersResp) == 0 { - res.FlagSet = append(res.FlagSet, flag_no_active_voucher) - return res, nil - } - - // Use only the first voucher + // No active voucher, set the first one as default firstVoucher := vouchersResp[0] defaultSym := firstVoucher.TokenSymbol defaultBal := firstVoucher.Balance @@ -1992,71 +1995,27 @@ func (h *MenuHandlers) SetDefaultVoucher(ctx context.Context, sym string, input // Scale down the balance scaledBalance := store.ScaleDownBalance(defaultBal, defaultDec) - // TODO: implement atomic transaction - // set the active symbol - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(defaultSym)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write defaultSym entry with", "key", storedb.DATA_ACTIVE_SYM, "value", defaultSym, "error", err) - return res, err - } - // set the active balance - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL, []byte(scaledBalance)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", storedb.DATA_ACTIVE_BAL, "value", scaledBalance, "error", err) - return res, err - } - // set the active decimals - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_DECIMAL, []byte(defaultDec)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write defaultDec entry with", "key", storedb.DATA_ACTIVE_DECIMAL, "value", defaultDec, "error", err) - return res, err - } - // set the active contract address - err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte(defaultAddr)) - if err != nil { - logg.ErrorCtxf(ctx, "failed to write defaultAddr entry with", "key", storedb.DATA_ACTIVE_ADDRESS, "value", defaultAddr, "error", err) - return res, err + firstVoucherMap := map[storedb.DataTyp]string{ + storedb.DATA_ACTIVE_SYM: defaultSym, + storedb.DATA_ACTIVE_BAL: scaledBalance, + storedb.DATA_ACTIVE_DECIMAL: defaultDec, + storedb.DATA_ACTIVE_ADDRESS: defaultAddr, } - return res, nil + for key, value := range firstVoucherMap { + if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { + logg.ErrorCtxf(ctx, "Failed to write active voucher data", "key", key, "error", err) + return res, err + } + } + + logg.InfoCtxf(ctx, "Default voucher set", "symbol", defaultSym, "balance", defaultBal, "decimals", defaultDec, "address", defaultAddr) + } else { + logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) + return res, err } - - logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", storedb.DATA_ACTIVE_SYM, "error", err) - return res, err - } - - res.FlagReset = append(res.FlagReset, flag_no_active_voucher) - - return res, nil -} - -// CheckVouchers retrieves the token holdings from the API using the "PublicKey" and stores -// them to gdbm. -func (h *MenuHandlers) CheckVouchers(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") - } - - userStore := h.userdataStore - publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY) - if err != nil { - logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err) - return res, err - } - - // Fetch vouchers from the API using the public key - vouchersResp, err := h.accountService.FetchVouchers(ctx, string(publicKey)) - if err != nil { - return res, nil - } - - logg.InfoCtxf(ctx, "fetched user vouchers", "public_key", string(publicKey), "vouchers", vouchersResp) - - // check the current active sym and update the data - activeSym, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM) - if activeSym != nil { + } else { + // Update active voucher balance activeSymStr := string(activeSym) // Find the matching voucher data @@ -2086,14 +2045,8 @@ func (h *MenuHandlers) CheckVouchers(ctx context.Context, sym string, input []by } } - activeBal, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_BAL) - activeAddr, _ := userStore.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - - logg.InfoCtxf(ctx, "The active data in CheckVouchers:", "activeSym", string(activeSym), string(activeBal), string(activeAddr)) - - data := store.ProcessVouchers(vouchersResp) - // Store all voucher data + data := store.ProcessVouchers(vouchersResp) dataMap := map[storedb.DataTyp]string{ storedb.DATA_VOUCHER_SYMBOLS: data.Symbols, storedb.DATA_VOUCHER_BALANCES: data.Balances, @@ -2103,7 +2056,6 @@ func (h *MenuHandlers) CheckVouchers(ctx context.Context, sym string, input []by // Write data entries for key, value := range dataMap { - logg.InfoCtxf(ctx, "Writing data entry for sessionId: %s", sessionId, "key", key, "value", value) if err := userStore.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil { logg.ErrorCtxf(ctx, "Failed to write data entry for sessionId: %s", sessionId, "key", key, "error", err) continue @@ -2528,9 +2480,10 @@ func (h *MenuHandlers) RequestCustomAlias(ctx context.Context, sym string, input return res, fmt.Errorf("Failed to retrieve alias: %s", err.Error()) } alias := aliasResult.Alias + logg.InfoCtxf(ctx, "Suggested alias ", "alias", alias) //Store the returned alias,wait for user to confirm it as new account alias - err = store.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(alias)) + err = store.WriteEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS, []byte(alias)) if err != nil { logg.ErrorCtxf(ctx, "failed to write account alias", "key", storedb.DATA_TEMPORARY_VALUE, "value", alias, "error", err) return res, err @@ -2559,7 +2512,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input if !ok { return res, fmt.Errorf("missing session") } - suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + suggestedAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) if err != nil { return res, nil } @@ -2567,7 +2520,7 @@ func (h *MenuHandlers) GetSuggestedAlias(ctx context.Context, sym string, input return res, nil } -// ConfirmNewAlias reads the suggested alias from the temporary value and confirms it as the new account alias. +// ConfirmNewAlias reads the suggested alias from the [DATA_SUGGECTED_ALIAS] key and confirms it as the new account alias. func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result store := h.userdataStore @@ -2578,10 +2531,11 @@ func (h *MenuHandlers) ConfirmNewAlias(ctx context.Context, sym string, input [] if !ok { return res, fmt.Errorf("missing session") } - newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE) + newAlias, err := store.ReadEntry(ctx, sessionId, storedb.DATA_SUGGESTED_ALIAS) if err != nil { return res, nil } + logg.InfoCtxf(ctx, "Confirming new alias", "alias", string(newAlias)) err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACCOUNT_ALIAS, []byte(string(newAlias))) if err != nil { logg.ErrorCtxf(ctx, "failed to clear DATA_ACCOUNT_ALIAS_VALUE entry with", "key", storedb.DATA_ACCOUNT_ALIAS, "value", "empty", "error", err) diff --git a/handlers/application/menuhandler_test.go b/handlers/application/menuhandler_test.go index 6c5f5a2..1a3789b 100644 --- a/handlers/application/menuhandler_test.go +++ b/handlers/application/menuhandler_test.go @@ -1990,36 +1990,46 @@ func TestFetchCommunityBalance(t *testing.T) { } } -func TestSetDefaultVoucher(t *testing.T) { +func TestManageVouchers(t *testing.T) { sessionId := "session123" + publicKey := "0X13242618721" + ctx, store := InitializeTestStore(t) ctx = context.WithValue(ctx, "SessionId", sessionId) fm, err := NewFlagManager(flagsPath) if err != nil { - t.Logf(err.Error()) + t.Fatal(err) } flag_no_active_voucher, err := fm.GetFlag("flag_no_active_voucher") if err != nil { - t.Logf(err.Error()) + t.Fatal(err) } - publicKey := "0X13242618721" + err = store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) + if err != nil { + t.Fatal(err) + } tests := []struct { - name string - vouchersResp []dataserviceapi.TokenHoldings - expectedResult resource.Result + name string + vouchersResp []dataserviceapi.TokenHoldings + storedActiveVoucher string + expectedVoucherSymbols []byte + expectedUpdatedAddress []byte + expectedResult resource.Result }{ { - name: "Test no vouchers available", - vouchersResp: []dataserviceapi.TokenHoldings{}, + name: "No vouchers available", + vouchersResp: []dataserviceapi.TokenHoldings{}, + expectedVoucherSymbols: []byte(""), + expectedUpdatedAddress: []byte(""), expectedResult: resource.Result{ FlagSet: []uint32{flag_no_active_voucher}, }, }, { - name: "Test set default voucher when no active voucher is set", + name: "Set default voucher when no active voucher is set", vouchersResp: []dataserviceapi.TokenHoldings{ { ContractAddress: "0x123", @@ -2028,7 +2038,24 @@ func TestSetDefaultVoucher(t *testing.T) { Balance: "100", }, }, - expectedResult: resource.Result{}, + expectedVoucherSymbols: []byte("1:TOKEN1"), + expectedUpdatedAddress: []byte("0x123"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_no_active_voucher}, + }, + }, + { + name: "Check and update active voucher balance", + vouchersResp: []dataserviceapi.TokenHoldings{ + {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, + {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, + }, + storedActiveVoucher: "SRF", + expectedVoucherSymbols: []byte("1:SRF\n2:MILO"), + expectedUpdatedAddress: []byte("0xd4c288865Ce"), + expectedResult: resource.Result{ + FlagReset: []uint32{flag_no_active_voucher}, + }, }, } @@ -2042,85 +2069,41 @@ func TestSetDefaultVoucher(t *testing.T) { flagManager: fm, } - err := store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - mockAccountService.On("FetchVouchers", string(publicKey)).Return(tt.vouchersResp, nil) - res, err := h.SetDefaultVoucher(ctx, "set_default_voucher", []byte("some-input")) + // Store active voucher if needed + if tt.storedActiveVoucher != "" { + err := store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte(tt.storedActiveVoucher)) + if err != nil { + t.Fatal(err) + } + err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) + if err != nil { + t.Fatal(err) + } + } + res, err := h.ManageVouchers(ctx, "manage_vouchers", []byte("")) assert.NoError(t, err) + assert.Equal(t, tt.expectedResult, res) - assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result") + if tt.storedActiveVoucher != "" { + // Validate stored voucher symbols + voucherData, err := store.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) + assert.NoError(t, err) + assert.Equal(t, tt.expectedVoucherSymbols, voucherData) - mockAccountService.AssertExpectations(t) + // Validate stored active contract address + updatedAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) + assert.NoError(t, err) + assert.Equal(t, tt.expectedUpdatedAddress, updatedAddress) + + mockAccountService.AssertExpectations(t) + } }) } } -func TestCheckVouchers(t *testing.T) { - mockAccountService := new(mocks.MockAccountService) - sessionId := "session123" - publicKey := "0X13242618721" - - ctx, store := InitializeTestStore(t) - ctx = context.WithValue(ctx, "SessionId", sessionId) - - h := &MenuHandlers{ - userdataStore: store, - accountService: mockAccountService, - } - - err := store.WriteEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY, []byte(publicKey)) - if err != nil { - t.Fatal(err) - } - - mockVouchersResponse := []dataserviceapi.TokenHoldings{ - {ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100"}, - {ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200"}, - } - - // store the default voucher data - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_SYM, []byte("SRF")) - if err != nil { - t.Fatal(err) - } - err = store.WriteEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS, []byte("0x41c188D45rfg6ds")) - if err != nil { - t.Fatal(err) - } - - expectedSym := []byte("1:SRF\n2:MILO") - expectedUpdatedAddress := []byte("0xd4c288865Ce") - - mockAccountService.On("FetchVouchers", string(publicKey)).Return(mockVouchersResponse, nil) - - _, err = h.CheckVouchers(ctx, "check_vouchers", []byte("")) - assert.NoError(t, err) - - // Read voucher sym data from the store - voucherData, err := store.ReadEntry(ctx, sessionId, storedb.DATA_VOUCHER_SYMBOLS) - if err != nil { - t.Fatal(err) - } - - // Read active contract address from the store - updatedAddress, err := store.ReadEntry(ctx, sessionId, storedb.DATA_ACTIVE_ADDRESS) - if err != nil { - t.Fatal(err) - } - - // assert that the data is stored correctly - assert.Equal(t, expectedSym, voucherData) - // assert that the address is updated - assert.Equal(t, expectedUpdatedAddress, updatedAddress) - - mockAccountService.AssertExpectations(t) -} - func TestGetVoucherList(t *testing.T) { sessionId := "session123" diff --git a/handlers/local.go b/handlers/local.go index 43b0bb5..3116b66 100644 --- a/handlers/local.go +++ b/handlers/local.go @@ -103,8 +103,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService) ls.DbRs.AddLocalFunc("confirm_pin_change", appHandlers.ConfirmPinChange) ls.DbRs.AddLocalFunc("quit_with_help", appHandlers.QuitWithHelp) ls.DbRs.AddLocalFunc("fetch_community_balance", appHandlers.FetchCommunityBalance) - ls.DbRs.AddLocalFunc("set_default_voucher", appHandlers.SetDefaultVoucher) - ls.DbRs.AddLocalFunc("check_vouchers", appHandlers.CheckVouchers) + ls.DbRs.AddLocalFunc("manage_vouchers", appHandlers.ManageVouchers) ls.DbRs.AddLocalFunc("get_vouchers", appHandlers.GetVoucherList) ls.DbRs.AddLocalFunc("view_voucher", appHandlers.ViewVoucher) ls.DbRs.AddLocalFunc("set_voucher", appHandlers.SetVoucher) diff --git a/services/registration/main.vis b/services/registration/main.vis index 5996c97..cda6844 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -1,9 +1,7 @@ LOAD clear_temporary_value 2 RELOAD clear_temporary_value -LOAD set_default_voucher 8 -RELOAD set_default_voucher -LOAD check_vouchers 10 -RELOAD check_vouchers +LOAD manage_vouchers 160 +RELOAD manage_vouchers LOAD check_balance 128 RELOAD check_balance CATCH api_failure flag_api_call_error 1 diff --git a/services/registration/my_account_alias b/services/registration/my_account_alias index 726f06b..4ab2e04 100644 --- a/services/registration/my_account_alias +++ b/services/registration/my_account_alias @@ -1,2 +1,2 @@ Current alias: {{.get_current_profile_info}} -Enter your preferred alias:: \ No newline at end of file +Enter your preferred alias: \ No newline at end of file diff --git a/services/registration/my_account_alias_swa b/services/registration/my_account_alias_swa index 0ea3166..3bd52a3 100644 --- a/services/registration/my_account_alias_swa +++ b/services/registration/my_account_alias_swa @@ -1,2 +1,2 @@ Lakabu ya sasa: {{.get_current_profile_info}} -Weka lakabu unalopendelea:: \ No newline at end of file +Weka lakabu unalopendelea: \ No newline at end of file diff --git a/store/db/db.go b/store/db/db.go index 73c9c5d..220ac56 100644 --- a/store/db/db.go +++ b/store/db/db.go @@ -63,6 +63,8 @@ const ( DATA_INITIAL_LANGUAGE_CODE //Fully qualified account alias string DATA_ACCOUNT_ALIAS + //currently suggested alias by the api awaiting user's confirmation as accepted account alias + DATA_SUGGESTED_ALIAS ) const (