Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(SPV-1231): admin endpoints group - Paymails API. #28

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion admin_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/accesskeys"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/contacts"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/invitations"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/paymails"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/transactions"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/webhooks"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/admin/xpubs"
Expand All @@ -30,6 +31,7 @@ import (
// ErrUnrecognizedAPIResponse, depending on the behavior of the SPV Wallet API.
type AdminAPI struct {
xpubsAPI *xpubs.API
paymailsAPI *paymails.API
accessKeyAPI *accesskeys.API
transactionsAPI *transactions.API
contactsAPI *contacts.API
Expand Down Expand Up @@ -225,6 +227,64 @@ func NewAdminAPIWithXPriv(cfg config.Config, xPriv string) (*AdminAPI, error) {
return initAdminAPI(cfg, authenticator)
}

// Paymails retrieves a paginated list of paymail addresses via the Admin Paymails API.
// The response includes user paymails along with pagination metadata, such as
// the current page number, sort order, and the field used for sorting (sortBy).
//
// Query parameters can be configured using optional query options. These options allow
// filtering based on metadata, pagination settings, or specific paymail attributes.
//
// The API response is unmarshaled into a *queries.PaymailAddressPage struct.
// Returns an error if the API request fails or the response cannot be decoded.
func (a *AdminAPI) Paymails(ctx context.Context, opts ...queries.PaymailQueryOption) (*queries.PaymailAddressPage, error) {
res, err := a.paymailsAPI.Paymails(ctx, opts...)
if err != nil {
return nil, paymails.HTTPErrorFormatter("failed to retrieve paymail addresses page", err).FormatGetErr()
}

return res, nil
}

// Paymail retrieves the paymail address associated with the specified ID via the Admin Paymails API.
// The response is expected to be unmarshaled into a *response.PaymailAddress struct.
// Returns an error if the request fails or the response cannot be decoded.
func (a *AdminAPI) Paymail(ctx context.Context, ID string) (*response.PaymailAddress, error) {
res, err := a.paymailsAPI.Paymail(ctx, ID)
if err != nil {
msg := fmt.Sprintf("failed retrieve paymail address with ID: %s", ID)
return nil, paymails.HTTPErrorFormatter(msg, err).FormatGetErr()
}

return res, nil
}

// CreatePaymail creates a new paymail address record via the Admin Paymails API.
// The provided command contains the necessary parameters to define the paymail address record.
//
// The API response is unmarshaled into a *response.Xpub PaymailAddress.
mgosek-4chain marked this conversation as resolved.
Show resolved Hide resolved
// Returns an error if the API request fails or the response cannot be decoded.
func (a *AdminAPI) CreatePaymail(ctx context.Context, cmd *commands.CreatePaymail) (*response.PaymailAddress, error) {
res, err := a.paymailsAPI.CreatePaymail(ctx, cmd)
if err != nil {
return nil, paymails.HTTPErrorFormatter("failed to create paymail address", err).FormatPostErr()
}

return res, nil
}

// DeletePaymail deletes a paymail address with via the Admin Paymails API.
// It returns an error if the API request fails. A nil error indicates that the paymail
// was successfully deleted.
func (a *AdminAPI) DeletePaymail(ctx context.Context, address string) error {
err := a.paymailsAPI.DeletePaymail(ctx, address)
if err != nil {
msg := fmt.Sprintf("failed to remove paymail address: %s", address)
return paymails.HTTPErrorFormatter(msg, err).FormatGetErr()
}

return nil
}

// NewAdminWithXPub initializes a new AdminAPI instance using an extended public key (xPub).
// This function configures the API client with the provided configuration and uses the xPub key for authentication.
// If any configuration or initialization step fails, an appropriate error is returned.
Expand Down Expand Up @@ -252,10 +312,11 @@ func initAdminAPI(cfg config.Config, auth authenticator) (*AdminAPI, error) {
}

return &AdminAPI{
paymailsAPI: paymails.NewAPI(url, httpClient),
transactionsAPI: transactions.NewAPI(url, httpClient),
xpubsAPI: xpubs.NewAPI(url, httpClient),
accessKeyAPI: accesskeys.NewAPI(url, httpClient),
webhooksAPI: webhooks.NewAPI(url, httpClient),
transactionsAPI: transactions.NewAPI(url, httpClient),
contactsAPI: contacts.NewAPI(url, httpClient),
invitationsAPI: invitations.NewAPI(url, httpClient),
}, nil
Expand Down
13 changes: 13 additions & 0 deletions commands/paymails.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package commands

import "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"

// CreatePaymail defines the parameters required to create a new paymail address,
// including associated metadata such as the public name and avatar.
type CreatePaymail struct {
Metadata querybuilders.Metadata `json:"metadata"` // Metadata associated with the paymail as key-value pairs.
Key string `json:"key"` // The xpub key linked to the paymail.
Address string `json:"address"` // The paymail address to be created.
PublicName string `json:"public_name"` // The public display name associated with the paymail.
Avatar string `json:"avatar"` // The URL of the paymail's avatar image.
}
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 4 additions & 2 deletions commands/users.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package commands

import "github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"

// UpdateXPubMetadata contains the parameters needed to update the metadata
// associated with the current user's xpub.
type UpdateXPubMetadata struct {
Metadata map[string]any `json:"metadata"` // Key-value pairs representing the xpub metadata
Metadata querybuilders.Metadata `json:"metadata"` // Key-value pairs representing the xpub metadata
}

// GenerateAccessKey contains the parameters needed to generate a new access key
// for the current user, including any associated metadata.
type GenerateAccessKey struct {
Metadata map[string]any `json:"metadata"` // Key-value pairs representing the access key metadata
Metadata querybuilders.Metadata `json:"metadata"` // Key-value pairs representing the access key metadata
}
5 changes: 5 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ the wallet client package during interaction wit the SPV Wallet API.
```
task: [default] task --list
task: Available tasks for this project:
* create-paymail-as-admin: Create a paymail address.
* default: Display all available tasks.
* delete-paymail-as-admin: Delete paymail address.
* fetch-paymail-as-admin: Fetch paymail with a given address.
* fetch-paymails-as-admin: Fetch paymails page.
* accept-invitation-as-admin: Accept invitation with a given ID as Admin.
* create-xpub-as-admin: Create xPub as Admin.
* default: Display all available tasks.
Expand Down
23 changes: 23 additions & 0 deletions examples/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ tasks:
- go run ../walletkeys/cmd/main.go
- echo "=================================================================="

fetch-paymails-as-admin:
desc: "Fetch paymails page."
silent: true
cmds:
- go run ./fetch_paymails_as_admin/fetch_paymails_as_admin.go

fetch-paymail-as-admin:
desc: "Fetch paymail with a given address."
silent: true
cmds:
- go run ./fetch_paymail_as_admin/fetch_paymail_as_admin.go

create-paymail-as-admin:
desc: "Create a paymail address."
silent: true
cmds:
- go run ./create_paymail_as_admin/create_paymail_as_admin.go

delete-paymail-as-admin:
desc: "Delete paymail address."
silent: true
cmds:
- go run ./delete_paymail_as_admin/delete_paymail_as_admin.go
accept-invitation-as-admin:
desc: "Accept invitation with a given ID as Admin."
silent: true
Expand Down
42 changes: 42 additions & 0 deletions examples/create_paymail_as_admin/create_paymail_as_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"context"
"log"

wallet "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/commands"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"
)

func main() {
ctx := context.Background()

adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv)
if err != nil {
log.Fatal(err)
}

xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{
Metadata: map[string]any{"xpub_key": "xpub_val"},
XPub: examples.XPub,
})
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub)

addr := exampleutil.RandomPaymail()
paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{
Key: examples.XPub,
Address: addr,
Metadata: querybuilders.Metadata{"key": "value"},
})
if err != nil {
log.Fatal(err)
}

exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail)
}
49 changes: 49 additions & 0 deletions examples/delete_paymail_as_admin/delete_paymail_as_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"context"
"fmt"
"log"

wallet "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/commands"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"
)

func main() {
ctx := context.Background()

adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv)
if err != nil {
log.Fatal(err)
}

xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{
Metadata: map[string]any{"xpub_key": "xpub_val"},
XPub: examples.XPub,
})
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub)

addr := exampleutil.RandomPaymail()
paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{
Key: examples.XPub,
Address: addr,
Metadata: querybuilders.Metadata{"key": "value"},
})
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail)

err = adminAPI.DeletePaymail(ctx, addr)
if err != nil {
log.Fatal(err)
}

fmt.Printf("[HTTP DELETE][Step 3] Delete Paymail: %s - api/v1/admin/paymails", addr)
}
10 changes: 10 additions & 0 deletions examples/exampleutil/exampleutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"fmt"
"log"
"strings"
"time"

"math/rand"

"github.com/bitcoin-sv/spv-wallet-go-client/config"
)
Expand All @@ -21,3 +24,10 @@ func Print(s string, a any) {
}
fmt.Println(string(res))
}

func RandomPaymail() string {
seed := time.Now().UnixNano()
n := rand.New(rand.NewSource(seed)).Intn(500)
addr := fmt.Sprintf("john.doe.%dtest@4chain.test.com", n)
chris-4chain marked this conversation as resolved.
Show resolved Hide resolved
return addr
}
45 changes: 45 additions & 0 deletions examples/fetch_paymail_as_admin/fetch_paymail_as_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"context"
"log"

wallet "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/commands"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil"
"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"
)

func main() {
ctx := context.Background()
adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv)
if err != nil {
log.Fatal(err)
}

xPub, err := adminAPI.CreateXPub(ctx, &commands.CreateUserXpub{
Metadata: map[string]any{"xpub_key": "xpub_val"},
XPub: examples.XPub,
})
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP POST][Step 1] Create xPub - api/v1/admin/users", xPub)

paymail, err := adminAPI.CreatePaymail(ctx, &commands.CreatePaymail{
Key: examples.XPub,
Address: exampleutil.RandomPaymail(),
Metadata: querybuilders.Metadata{"key": "value"},
})
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP POST][Step 2] Create Paymail - api/v1/admin/paymails", paymail)

paymail, err = adminAPI.Paymail(ctx, paymail.ID)
if err != nil {
log.Fatal(err)
}
exampleutil.Print("[HTTP GET][Step 3] Fetch Paymail - api/v1/admin/paymails", paymail)
}
28 changes: 28 additions & 0 deletions examples/fetch_paymails_as_admin/fetch_paymails_as_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"context"
"log"

wallet "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
"github.com/bitcoin-sv/spv-wallet-go-client/examples/exampleutil"
"github.com/bitcoin-sv/spv-wallet-go-client/queries"
"github.com/bitcoin-sv/spv-wallet/models/filter"
)

func main() {
adminAPI, err := wallet.NewAdminAPIWithXPriv(exampleutil.ExampleConfig, examples.XPriv)
if err != nil {
log.Fatal(err)
}

page, err := adminAPI.Paymails(context.Background(), queries.PaymailQueryWithPageFilter(filter.Page{
Size: 3,
}))
if err != nil {
log.Fatal(err)
}

exampleutil.Print("[HTTP GET] Paymails page - api/v1/admin/paymails", page)
}
33 changes: 33 additions & 0 deletions internal/api/v1/admin/paymails/paymail_filter_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package paymails

import (
"fmt"
"net/url"

"github.com/bitcoin-sv/spv-wallet-go-client/internal/api/v1/querybuilders"
"github.com/bitcoin-sv/spv-wallet/models/filter"
)

type paymailFilterBuilder struct {
paymailFilter filter.AdminPaymailFilter
modelFilterBuilder querybuilders.ModelFilterBuilder
}

func (p *paymailFilterBuilder) Build() (url.Values, error) {
modelFilterBuilder, err := p.modelFilterBuilder.Build()
if err != nil {
return nil, fmt.Errorf("failed to build model filter query params: %w", err)
}

params := querybuilders.NewExtendedURLValues()
if len(modelFilterBuilder) > 0 {
params.Append(modelFilterBuilder)
}

params.AddPair("id", p.paymailFilter.ID)
params.AddPair("xpubId", p.paymailFilter.XpubID)
params.AddPair("alias", p.paymailFilter.Alias)
params.AddPair("domain", p.paymailFilter.Domain)
params.AddPair("publicName", p.paymailFilter.PublicName)
return params.Values, nil
}
Loading
Loading