Skip to content

Commit

Permalink
feat(cli): Adding CLI tool to trigger transaction refresh.
Browse files Browse the repository at this point in the history
This will make it easier to force a specific connection to refresh
transactions via the CLI. This is something I eventually want to surface
in the UI but I'm not sure how to do that well yet.
  • Loading branch information
elliotcourant committed Apr 13, 2024
1 parent 34aca84 commit 8d6d757
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
99 changes: 99 additions & 0 deletions server/cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"encoding/hex"
"fmt"

"github.com/benbjohnson/clock"
"github.com/monetr/monetr/server/config"
"github.com/monetr/monetr/server/logging"
"github.com/monetr/monetr/server/models"
"github.com/monetr/monetr/server/platypus"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
Expand All @@ -27,6 +29,103 @@ func newAdminCommand(parent *cobra.Command) {
}

newSecretsCommand(command)
newPlaidCommand(command)

parent.AddCommand(command)
}

func newPlaidCommand(parent *cobra.Command) {
command := &cobra.Command{
Use: "plaid",
Short: "Manage Plaid links in monetr",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}

newPlaidRefreshTransactionsCommand(command)

parent.AddCommand(command)
}

func newPlaidRefreshTransactionsCommand(parent *cobra.Command) {
var linkId uint64
command := &cobra.Command{
Use: "refresh-transactions",
Short: "Trigger a transaction refresh for a Plaid link",
RunE: func(cmd *cobra.Command, args []string) error {
clock := clock.New()
configuration := config.LoadConfiguration()

log := logging.NewLoggerWithConfig(configuration.Logging)
if configFileName := configuration.GetConfigFileName(); configFileName != "" {
log.WithField("config", configFileName).Info("config file loaded")
}

if linkId == 0 {
log.Fatal("link ID must be specified via --link")
return cmd.Help()
}

db, err := getDatabase(log, configuration, nil)
if err != nil {
log.WithError(err).Fatal("failed to setup database")
return err
}

kms, err := getKMS(log, configuration)
if err != nil {
log.WithError(err).Fatal("failed to initialize KMS")
return err
}

log.Info("retrieving link from database")
var link models.Link
if err := db.Model(&link).
Relation("PlaidLink").
Where(`"link"."link_id" = ?`, linkId).
Limit(1).
Select(&link); err != nil {
log.WithError(err).Fatal("failed to retrieve link specified")
return err
}

if link.PlaidLink == nil {
log.Fatal("link does not have a plaid link!")
return errors.New("link is not a valid plaid link")
}

plaid := platypus.NewPlaid(
log,
clock,
kms,
db,
configuration.Plaid,
)

client, err := plaid.NewClientFromLink(
context.Background(),
link.AccountId,
link.LinkId,
)
if err != nil {
log.WithError(err).Warn("failed to create Plaid client")
return err
}

log.Info("triggering transaction refresh")

if err := client.RefeshTransactions(context.Background()); err != nil {
log.WithError(err).Fatal("failed to refresh transactions")
return err
}

log.Info("transaction refresh triggered successfully!")

return nil
},
}
command.PersistentFlags().Uint64Var(&linkId, "link", 0, "Link Id to trigger the Plaid refresh on")

parent.AddCommand(command)
}
Expand Down
36 changes: 36 additions & 0 deletions server/platypus/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ type (
RemoveItem(ctx context.Context) error
// Sync takes a cursor (or lack of one) and retrieves transaction data from Plaid that is newer than that cursor.
Sync(ctx context.Context, cursor *string) (*SyncResult, error)

RefeshTransactions(ctx context.Context) error
}
)

Expand Down Expand Up @@ -319,3 +321,37 @@ func (p *PlaidClient) RemoveItem(ctx context.Context) error {

return nil
}

func (p *PlaidClient) RefeshTransactions(ctx context.Context) error {
span := sentry.StartSpan(ctx, "http.client")
defer span.Finish()
span.Description = "Plaid - ForceRefresh"

span.SetTag("itemId", p.itemId)

log := p.getLog(span)

log.Trace("force refreshing transactions for item")

request := p.client.PlaidApi.
TransactionsRefresh(span.Context()).
TransactionsRefreshRequest(plaid.TransactionsRefreshRequest{
AccessToken: p.accessToken,
})

// Send the request.
_, response, err := request.Execute()
// And handle the response.
if err = after(
span,
response,
err,
"Triggering transaction refresh for Plaid item",
"failed to trigger transaction refresh for Plaid item",
); err != nil {
log.WithError(err).Errorf("failed to trigger transaction refresh for Plaid item")
return err
}

return nil
}

0 comments on commit 8d6d757

Please sign in to comment.