From 18a30a00659d2d2795496ff1a4b9f8af461e6718 Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 14 Jan 2025 13:53:29 +0000 Subject: [PATCH] Add dev api for alias handling' --- dev/api.go | 86 +++++++++++++++++++++++++++++++------- dev/api_test.go | 72 +++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + models/alias_response.go | 9 ++++ remote/account_service.go | 1 + testutil/mocks/api_mock.go | 6 +++ 7 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 dev/api_test.go create mode 100644 models/alias_response.go diff --git a/dev/api.go b/dev/api.go index 8047c34..bdf0886 100644 --- a/dev/api.go +++ b/dev/api.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "os" + "regexp" "strconv" "strings" "time" @@ -17,11 +18,13 @@ import ( fsdb "git.defalsify.org/vise.git/db/fs" "git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/event" + "git.grassecon.net/grassrootseconomics/common/phone" dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api" ) var ( logg = logging.NewVanilla().WithDomain("sarafu-api.devapi") + aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$") ) const ( @@ -104,10 +107,8 @@ type DevAccountService struct { // accountsSession map[string]string } -func NewDevAccountService(ctx context.Context, d string) *DevAccountService { +func NewDevAccountService() *DevAccountService { svc := &DevAccountService{ - dir: d, - db: fsdb.NewFsDb(), accounts: make(map[string]Account), accountsTrack: make(map[string]string), accountsAlias: make(map[string]string), @@ -122,18 +123,27 @@ func NewDevAccountService(ctx context.Context, d string) *DevAccountService { Address: zeroAddress, } svc.accounts[acc.Address] = acc - err := svc.db.Connect(ctx, d) - if err != nil { - panic(err) - } - svc.db.SetPrefix(db.DATATYPE_USERDATA) - err = svc.loadAll(ctx) - if err != nil { - panic(err) - } return svc } +func (das *DevAccountService) WithFs(ctx context.Context, dir string) *DevAccountService { + if das.db != nil { + return das + } + das.dir = dir + das.db = fsdb.NewFsDb() + err := das.db.Connect(ctx, dir) + if err != nil { + panic(err) + } + das.db.SetPrefix(db.DATATYPE_USERDATA) + err = das.loadAll(ctx) + if err != nil { + panic(err) + } + return das +} + func (das *DevAccountService) WithEmitter(fn event.EmitterFunc) *DevAccountService { das.emitterFunc = fn return das @@ -295,6 +305,9 @@ func (das *DevAccountService) balanceAuto(ctx context.Context, pubKey string) er } func (das *DevAccountService) saveAccount(ctx context.Context, acc Account) error { + if das.db == nil { + return nil + } k := "account_" + acc.Address v, err := json.Marshal(acc) if err != nil { @@ -516,7 +529,7 @@ func (das *DevAccountService) TokenTransfer(ctx context.Context, amount, from, t }, nil } -func (das *DevAccountService) CheckAliasAddress(ctx context.Context, alias string) (*dataserviceapi.AliasAddress, error) { +func (das *DevAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { addr, ok := das.accountsAlias[alias] if !ok { return nil, fmt.Errorf("alias %s not found", alias) @@ -525,7 +538,52 @@ func (das *DevAccountService) CheckAliasAddress(ctx context.Context, alias strin if !ok { return nil, fmt.Errorf("alias %s found but does not resolve", alias) } - return &dataserviceapi.AliasAddress{ + return &models.AliasAddress{ Address: acc.Address, }, nil } + +func (das *DevAccountService) applyPhoneAlias(ctx context.Context, publicKey string, phoneNumber string) (bool, error) { + if phoneNumber[0] == '+' { + if !phone.IsValidPhoneNumber(phoneNumber) { + return false, fmt.Errorf("Invalid phoneNumber number: %v", phoneNumber) + } + logg.DebugCtxf(ctx, "matched phoneNumber alias", "phoneNumber", phoneNumber, "address", publicKey) + return true, nil + } + return false, nil +} + +func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { + var alias string + if !aliasRegex.MatchString(hint) { + return nil, fmt.Errorf("alias hint does not match: %s", publicKey) + } + acc, ok := das.accounts[publicKey] + if !ok { + return nil, fmt.Errorf("address %s not found", publicKey) + } + alias = hint + isPhone, err := das.applyPhoneAlias(ctx, publicKey, alias) + if err != nil { + return nil, fmt.Errorf("phone parser error: %v", err) + } + if !isPhone { + for true { + addr, ok := das.accountsAlias[alias] + if !ok { + break + } + if addr == publicKey { + break + } + alias += "x" + } + acc.Alias = alias + das.accountsAlias[alias] = publicKey + } + logg.DebugCtxf(ctx, "set alias", "addr", publicKey, "alias", alias) + return &models.RequestAliasResult{ + Alias: alias, + }, nil +} diff --git a/dev/api_test.go b/dev/api_test.go new file mode 100644 index 0000000..3b3efcd --- /dev/null +++ b/dev/api_test.go @@ -0,0 +1,72 @@ +package dev + +import ( + "context" + "testing" +) + +func TestApiRequestAlias(t *testing.T) { + ctx := context.Background() + svc := NewDevAccountService() + ra, err := svc.CreateAccount(ctx) + if err != nil { + t.Fatal(err) + } + addr := ra.PublicKey + + _, err = svc.RequestAlias(ctx, addr, "+254f00") + if err == nil { + t.Fatalf("expected error") + } + alias := "+254712345678" + rb, err := svc.RequestAlias(ctx, addr, alias) + if err != nil { + t.Fatal(err) + } + if rb.Alias != alias { + t.Fatalf("expected '%s', got '%s'", alias, rb.Alias) + } + _, err = svc.CheckAliasAddress(ctx, alias) + if err == nil { + t.Fatalf("expected error") + } + + alias = "foo" + rb, err = svc.RequestAlias(ctx, addr, alias) + if err != nil { + t.Fatal(err) + } + if rb.Alias != alias { + t.Fatalf("expected '%s', got '%s'", alias, rb.Alias) + } + rc, err := svc.CheckAliasAddress(ctx, alias) + if err != nil { + t.Fatal(err) + } + if rc.Address != addr { + t.Fatalf("expected '%s', got '%s'", addr, rc.Address) + } + + // create a second account + ra, err = svc.CreateAccount(ctx) + if err != nil { + t.Fatal(err) + } + addr = ra.PublicKey + + rb, err = svc.RequestAlias(ctx, addr, alias) + if err != nil { + t.Fatal(err) + } + alias = "foox" + if rb.Alias != alias { + t.Fatalf("expected '%s', got '%s'", alias, rb.Alias) + } + rc, err = svc.CheckAliasAddress(ctx, alias) + if err != nil { + t.Fatal(err) + } + if rc.Address != addr { + t.Fatalf("expected '%s', got '%s'", addr, rc.Address) + } +} diff --git a/go.mod b/go.mod index e132cbe..688ab66 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.4 require ( git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d + git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05 git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250113213325-5228aef0889b github.com/gofrs/uuid v4.4.0+incompatible github.com/grassrootseconomics/eth-custodial v1.3.0-beta diff --git a/go.sum b/go.sum index 23eded9..bfe97ac 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d h1:bPAOVZOX4frSGhfOdcj7kc555f8dc9DmMd2YAyC2AMw= git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck= +git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05 h1:BenzGx6aDHKDwE23/mWIFD2InYIXyzHroZWV3MF5WUk= +git.grassecon.net/grassrootseconomics/common v0.0.0-20250113174703-6afccefd1f05/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60= git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250113213325-5228aef0889b h1:6SieNUSEKbkjzquuwazs/lVG56zdEWF10zQQEMRJfMs= git.grassecon.net/grassrootseconomics/visedriver v0.8.0-beta.10.0.20250113213325-5228aef0889b/go.mod h1:E6W7ZOa7ZvVr0Bc5ot0LNSwpSPYq4hXlAIvEPy3AJ7U= github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE= diff --git a/models/alias_response.go b/models/alias_response.go new file mode 100644 index 0000000..295c9ef --- /dev/null +++ b/models/alias_response.go @@ -0,0 +1,9 @@ +package models + +type RequestAliasResult struct { + Alias string +} + +type AliasAddress struct { + Address string +} diff --git a/remote/account_service.go b/remote/account_service.go index c6dd56b..ee86653 100644 --- a/remote/account_service.go +++ b/remote/account_service.go @@ -16,4 +16,5 @@ type AccountService interface { 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) + RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error) } diff --git a/testutil/mocks/api_mock.go b/testutil/mocks/api_mock.go index 35ce6e2..6794b64 100644 --- a/testutil/mocks/api_mock.go +++ b/testutil/mocks/api_mock.go @@ -54,6 +54,12 @@ func(m MockApi) CheckAliasAddress(ctx context.Context, alias string) (*dataservi return nil, nil } +func(m MockApi) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { + return nil, nil +} + func(m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { return nil, nil } + +