Compare commits

...

19 Commits

Author SHA1 Message Date
34957e5b6f Merge pull request 'sms-upsell-api' (#10) from sms-api into master
Reviewed-on: #10
2025-04-11 10:06:08 +02:00
6597d6b6a2
added the SendUpsellSMS to DevAccountService and MockApi 2025-04-11 11:02:23 +03:00
441e289854
implement SendUpsellSMs 2025-04-01 15:25:10 +03:00
5b41c8dc64
chore: rename SendSMS to SendUpsellSMS 2025-04-01 14:55:03 +03:00
417e7f0179
fix: correct sendSms response 2025-04-01 14:53:30 +03:00
2eed990921
Merge branch 'master' into sms-api 2025-04-01 14:18:04 +03:00
8145b4bd00
fix: remove formatting to fqdn 2025-03-10 12:39:12 +03:00
dda02852bf
add alias resolve logs 2025-03-10 11:49:49 +03:00
6e437cb8e0
feat: sanitize alias hint before resolving 2025-03-07 16:10:40 +03:00
4af0e9709d
fix: format alias to fqdn before request 2025-03-07 16:04:15 +03:00
9c8a3df971
add logs 2025-03-07 09:45:09 +03:00
62c33e2587 Merge pull request 'ens-names' (#8) from ens-names into master
Reviewed-on: #8
2025-03-07 07:27:29 +01:00
4a5de68a8c
added the SendSMS func to the httpAccount service 2025-03-06 09:06:45 +03:00
6c4702e2ba
added the send sms response model 2025-03-06 09:05:39 +03:00
7280784ee1
added the send sms endpoint configs 2025-03-06 09:05:01 +03:00
1f3ac220d1
feat: request and resolve aliases. 2025-02-24 14:49:58 +03:00
e64eb265a5
add alias responses 2025-02-24 14:48:00 +03:00
d5dc792dce
add alias ens endpoint 2025-02-24 14:45:45 +03:00
31eb30de0f
add dev api logs 2025-02-06 14:29:44 +03:00
9 changed files with 158 additions and 9 deletions

View File

@ -16,12 +16,15 @@ const (
voucherTransfersPathPrefix = "/api/v1/transfers/last10" voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token" voucherDataPathPrefix = "/api/v1/token"
AliasPrefix = "api/v1/alias" AliasPrefix = "api/v1/alias"
SendSMSPrefix = "api/v1/external/upsell"
AliasEnsPrefix = "/api/v1/bypass"
) )
var ( var (
custodialURLBase string custodialURLBase string
dataURLBase string dataURLBase string
BearerToken string BearerToken string
aliasEnsURLBase string
) )
var ( var (
@ -34,6 +37,8 @@ var (
VoucherTransfersURL string VoucherTransfersURL string
VoucherDataURL string VoucherDataURL string
CheckAliasURL string CheckAliasURL string
SendSMSURL string
AliasEnsURL string
) )
func setBase() error { func setBase() error {
@ -41,6 +46,7 @@ func setBase() error {
custodialURLBase = env.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003") custodialURLBase = env.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
dataURLBase = env.GetEnv("DATA_URL_BASE", "http://localhost:5006") dataURLBase = env.GetEnv("DATA_URL_BASE", "http://localhost:5006")
aliasEnsURLBase = env.GetEnv("ALIAS_ENS_BASE", "http://localhost:5015")
BearerToken = env.GetEnv("BEARER_TOKEN", "") BearerToken = env.GetEnv("BEARER_TOKEN", "")
_, err = url.Parse(custodialURLBase) _, err = url.Parse(custodialURLBase)
@ -69,5 +75,7 @@ func LoadConfig() error {
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix) VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix) VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
CheckAliasURL, _ = url.JoinPath(dataURLBase, AliasPrefix) CheckAliasURL, _ = url.JoinPath(dataURLBase, AliasPrefix)
SendSMSURL, _ = url.JoinPath(dataURLBase, SendSMSPrefix)
AliasEnsURL, _ = url.JoinPath(aliasEnsURLBase, AliasEnsPrefix)
return nil return nil
} }

View File

@ -202,10 +202,13 @@ func (das *DevAccountService) loadItem(ctx context.Context, k []byte, v []byte)
} }
if ss[0] == "account" { if ss[0] == "account" {
err = das.loadAccount(ctx, ss[1], v) err = das.loadAccount(ctx, ss[1], v)
logg.ErrorCtxf(ctx, "loading saved account failed", "error_load_account", err)
} else if ss[0] == "tx" { } else if ss[0] == "tx" {
err = das.loadTx(ctx, ss[1], v) err = das.loadTx(ctx, ss[1], v)
logg.ErrorCtxf(ctx, "loading transactions failed", "error_load_txs", err)
} else if ss[0] == "alias" { } else if ss[0] == "alias" {
err = das.loadAlias(ctx, ss[1], k) err = das.loadAlias(ctx, ss[1], k)
logg.ErrorCtxf(ctx, "loading aliases failed", "error_load_aliases", err)
} else { } else {
logg.ErrorCtxf(ctx, "unknown double underscore key", "key", ss[0]) logg.ErrorCtxf(ctx, "unknown double underscore key", "key", ss[0])
} }
@ -574,10 +577,12 @@ func (das *DevAccountService) TokenTransfer(ctx context.Context, amount, from, t
func (das *DevAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { func (das *DevAccountService) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) {
addr, ok := das.accountsAlias[alias] addr, ok := das.accountsAlias[alias]
if !ok { if !ok {
logg.ErrorCtxf(ctx, "alias check failed", "alias", alias)
return nil, fmt.Errorf("alias %s not found", alias) return nil, fmt.Errorf("alias %s not found", alias)
} }
acc, ok := das.accounts[addr] acc, ok := das.accounts[addr]
if !ok { if !ok {
logg.ErrorCtxf(ctx, "failed to resolve alias", "alias", alias)
return nil, fmt.Errorf("alias %s found but does not resolve", alias) return nil, fmt.Errorf("alias %s found but does not resolve", alias)
} }
return &models.AliasAddress{ return &models.AliasAddress{
@ -600,6 +605,7 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
var alias string var alias string
uid, err := uuid.NewV4() uid, err := uuid.NewV4()
if !aliasRegex.MatchString(hint) { if !aliasRegex.MatchString(hint) {
logg.ErrorCtxf(ctx, "alias hint does not match", "key", publicKey, "hint", hint)
return nil, fmt.Errorf("alias hint does not match: %s", publicKey) return nil, fmt.Errorf("alias hint does not match: %s", publicKey)
} }
acc, ok := das.accounts[publicKey] acc, ok := das.accounts[publicKey]
@ -611,6 +617,7 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
} }
err = das.saveAccount(ctx, acc) err = das.saveAccount(ctx, acc)
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "account save failed with", "account", acc, "account_save_error", err)
return nil, err return nil, err
} }
das.accounts[publicKey] = acc das.accounts[publicKey] = acc
@ -618,6 +625,7 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
alias = hint alias = hint
isPhone, err := das.applyPhoneAlias(ctx, publicKey, alias) isPhone, err := das.applyPhoneAlias(ctx, publicKey, alias)
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "failed to apply phone alias", "public key", publicKey, "alias", alias, "error", err)
return nil, fmt.Errorf("phone parser error: %v", err) return nil, fmt.Errorf("phone parser error: %v", err)
} }
if !isPhone { if !isPhone {
@ -636,6 +644,7 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
das.accountsAlias[alias] = publicKey das.accountsAlias[alias] = publicKey
err := das.saveAlias(ctx, map[string]string{alias: publicKey}) err := das.saveAlias(ctx, map[string]string{alias: publicKey})
if err != nil { if err != nil {
logg.ErrorCtxf(ctx, "account save error", "public key", publicKey, "alias", alias, "alias_save_error", err)
return nil, fmt.Errorf("Failed to save the account alias with error: %s", err.Error()) return nil, fmt.Errorf("Failed to save the account alias with error: %s", err.Error())
} }
} }
@ -644,3 +653,10 @@ func (das *DevAccountService) RequestAlias(ctx context.Context, publicKey string
Alias: alias, Alias: alias,
}, nil }, nil
} }
func (das *DevAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
logg.DebugCtxf(ctx, "sent an SMS", "inviterPhone", inviterPhone, "inviteePhone", inviteePhone)
return &models.SendSMSResponse{
Invitee: inviteePhone,
}, nil
}

View File

@ -7,3 +7,13 @@ type RequestAliasResult struct {
type AliasAddress struct { type AliasAddress struct {
Address string Address string
} }
type AliasEnsResult struct {
Address string `json:"address"`
AutoChoose bool `json:"autoChoose"`
Name string `json:"name"`
}
type AliasEnsAddressResult struct {
Address string `json:"address"`
}

View File

@ -0,0 +1,5 @@
package models
type SendSMSResponse struct {
Invitee string `json:"invitee"`
}

View File

@ -17,4 +17,5 @@ type AccountService interface {
TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error)
CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) CheckAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error)
RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error) RequestAlias(ctx context.Context, hint string, publicKey string) (*models.RequestAliasResult, error)
SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error)
} }

View File

@ -11,7 +11,9 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings"
"git.defalsify.org/vise.git/logging"
"git.grassecon.net/grassrootseconomics/sarafu-api/config" "git.grassecon.net/grassrootseconomics/sarafu-api/config"
"git.grassecon.net/grassrootseconomics/sarafu-api/dev" "git.grassecon.net/grassrootseconomics/sarafu-api/dev"
"git.grassecon.net/grassrootseconomics/sarafu-api/models" "git.grassecon.net/grassrootseconomics/sarafu-api/models"
@ -22,6 +24,7 @@ import (
var ( var (
aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$") aliasRegex = regexp.MustCompile("^\\+?[a-zA-Z0-9\\-_]+$")
logg = logging.NewVanilla().WithDomain("sarafu-api.devapi")
) )
type HTTPAccountService struct { type HTTPAccountService struct {
@ -59,6 +62,10 @@ func (as *HTTPAccountService) TrackAccountStatus(ctx context.Context, publicKey
return &r, nil return &r, nil
} }
func (as *HTTPAccountService) ToFqdn(alias string) string {
return alias + ".sarafu.eth"
}
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. // CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
// Parameters: // Parameters:
// - publicKey: The public key associated with the account whose balance needs to be checked. // - publicKey: The public key associated with the account whose balance needs to be checked.
@ -216,8 +223,10 @@ func (as *HTTPAccountService) CheckAliasAddress(ctx context.Context, alias strin
if as.SS == nil { if as.SS == nil {
return nil, fmt.Errorf("The storage service cannot be nil") return nil, fmt.Errorf("The storage service cannot be nil")
} }
logg.InfoCtxf(ctx, "resolving alias before formatting", "alias", alias)
svc := dev.NewDevAccountService(ctx, as.SS) svc := dev.NewDevAccountService(ctx, as.SS)
if as.UseApi { if as.UseApi {
logg.InfoCtxf(ctx, "resolving alias to address", "alias", alias)
return resolveAliasAddress(ctx, alias) return resolveAliasAddress(ctx, alias)
} else { } else {
return svc.CheckAliasAddress(ctx, alias) return svc.CheckAliasAddress(ctx, alias)
@ -225,27 +234,114 @@ func (as *HTTPAccountService) CheckAliasAddress(ctx context.Context, alias strin
} }
func resolveAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) { func resolveAliasAddress(ctx context.Context, alias string) (*models.AliasAddress, error) {
var r models.AliasAddress var (
aliasEnsResult models.AliasEnsAddressResult
)
ep, err := url.JoinPath(config.CheckAliasURL, alias) ep, err := url.JoinPath(config.AliasEnsURL, "/resolve")
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest("GET", ep, nil)
u, err := url.Parse(ep)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = doRequest(ctx, req, &r)
return &r, err query := u.Query()
query.Set("name", alias)
u.RawQuery = query.Encode()
req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &aliasEnsResult)
if err != nil {
return nil, err
}
return &models.AliasAddress{Address: aliasEnsResult.Address}, err
} }
// TODO: Use actual custodial api to request available alias
func (as *HTTPAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (as *HTTPAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
if as.SS == nil { if as.SS == nil {
return nil, fmt.Errorf("The storage service cannot be nil") return nil, fmt.Errorf("The storage service cannot be nil")
} }
svc := dev.NewDevAccountService(ctx, as.SS) if as.UseApi {
return svc.RequestAlias(ctx, publicKey, hint) if !strings.Contains(hint, ".") {
hint = as.ToFqdn(hint)
}
enr, err := requestEnsAlias(ctx, publicKey, hint)
if err != nil {
return nil, err
}
return &models.RequestAliasResult{Alias: enr.Name}, nil
} else {
svc := dev.NewDevAccountService(ctx, as.SS)
return svc.RequestAlias(ctx, publicKey, hint)
}
}
func requestEnsAlias(ctx context.Context, publicKey string, hint string) (*models.AliasEnsResult, error) {
var r models.AliasEnsResult
ep, err := url.JoinPath(config.AliasEnsURL, "/register")
if err != nil {
return nil, err
}
logg.InfoCtxf(ctx, "requesting alias", "endpoint", ep, "hint", hint)
//Payload with the address and hint to derive an ENS name
payload := map[string]string{
"address": publicKey,
"hint": hint,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", ep, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, err
}
// Log the request body
logg.InfoCtxf(ctx, "request body", "payload", string(payloadBytes))
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
logg.InfoCtxf(ctx, "alias successfully assigned", "alias", r.Name)
return &r, nil
}
// SendSMS calls the API to send out an SMS.
// Parameters:
// - inviterPhone: The user initiating the SMS.
// - inviteePhone: The number being invited to Sarafu.
func (as *HTTPAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
var r models.SendSMSResponse
// Create request payload
payload := map[string]string{
"inviterPhone": inviterPhone,
"inviteePhone": inviteePhone,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}
// Create a new request
req, err := http.NewRequest("POST", config.SendSMSURL, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, err
}
_, err = doRequest(ctx, req, &r)
if err != nil {
return nil, err
}
return &r, nil
} }
// TODO: remove eth-custodial api dependency // TODO: remove eth-custodial api dependency

View File

@ -61,3 +61,7 @@ func (m MockApi) RequestAlias(ctx context.Context, publicKey string, hint string
func (m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) { func (m MockApi) TokenTransfer(ctx context.Context, amount, from, to, tokenAddress string) (*models.TokenTransferResponse, error) {
return nil, nil return nil, nil
} }
func (m MockApi) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
return nil, nil
}

View File

@ -53,7 +53,12 @@ func (m *MockAccountService) CheckAliasAddress(ctx context.Context, alias string
return args.Get(0).(*models.AliasAddress), args.Error(1) return args.Get(0).(*models.AliasAddress), args.Error(1)
} }
func (m MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (m *MockAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
args := m.Called(publicKey, hint) args := m.Called(publicKey, hint)
return args.Get(0).(*models.RequestAliasResult), args.Error(1) return args.Get(0).(*models.RequestAliasResult), args.Error(1)
} }
func (m *MockAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
args := m.Called(inviterPhone, inviteePhone)
return args.Get(0).(*models.SendSMSResponse), args.Error(1)
}

View File

@ -64,3 +64,7 @@ func (m TestAccountService) CheckAliasAddress(ctx context.Context, alias string)
func (m TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) { func (m TestAccountService) RequestAlias(ctx context.Context, publicKey string, hint string) (*models.RequestAliasResult, error) {
return &models.RequestAliasResult{}, nil return &models.RequestAliasResult{}, nil
} }
func (m TestAccountService) SendUpsellSMS(ctx context.Context, inviterPhone, inviteePhone string) (*models.SendSMSResponse, error) {
return &models.SendSMSResponse{}, nil
}