Skip to content

Commit

Permalink
Repair Transaction Endpoints (#195)
Browse files Browse the repository at this point in the history
Co-authored-by: Danielle <[email protected]>
  • Loading branch information
bbengfort and daniellemaxwell authored Aug 9, 2024
1 parent 669246b commit 527edec
Show file tree
Hide file tree
Showing 11 changed files with 561 additions and 83 deletions.
12 changes: 8 additions & 4 deletions pkg/store/mock/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ func (s *Store) PrepareTransaction(context.Context, uuid.UUID) (models.PreparedT
return nil, nil
}

func (s *Store) LatestSecureEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error) {
return nil, nil
}

func (s *Store) ListSecureEnvelopes(ctx context.Context, txID uuid.UUID, page *models.PageInfo) (*models.SecureEnvelopePage, error) {
return nil, nil
}
Expand All @@ -71,6 +67,14 @@ func (s *Store) DeleteSecureEnvelope(ctx context.Context, txID uuid.UUID, envID
return nil
}

func (s *Store) LatestSecureEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error) {
return nil, nil
}

func (s *Store) LatestPayloadEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error) {
return nil, nil
}

func (s *Store) ListAccounts(context.Context, *models.PageInfo) (*models.AccountsPage, error) {
return nil, nil
}
Expand Down
103 changes: 70 additions & 33 deletions pkg/store/sqlite/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,39 +168,6 @@ func (s *Store) DeleteTransaction(ctx context.Context, id uuid.UUID) (err error)
return tx.Commit()
}

const (
latestSecEnvSQL = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID ORDER BY timestamp DESC LIMIT 1"
latestSecEnvByDirectionSQL = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID AND direction=:direction ORDER BY timestamp DESC LIMIT 1"
)

func (s *Store) LatestSecureEnvelope(ctx context.Context, envelopeID uuid.UUID, direction string) (env *models.SecureEnvelope, err error) {
var tx *sql.Tx
if tx, err = s.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}); err != nil {
return nil, err
}
defer tx.Rollback()

var result *sql.Row
if direction == "" || direction == models.DirectionAny {
// Get the latest secure envelope regardless of the direction
result = tx.QueryRow(latestSecEnvSQL, sql.Named("envelopeID", envelopeID))
} else {
// Specify the direction to get the latest envelope for
result = tx.QueryRow(latestSecEnvByDirectionSQL, sql.Named("envelopeID", envelopeID), sql.Named("direction", direction))
}

env = &models.SecureEnvelope{}
if err = env.Scan(result); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, dberr.ErrNotFound
}
return nil, err
}

tx.Commit()
return env, nil
}

//===========================================================================
// Secure Envelopes CRUD Interface
//===========================================================================
Expand Down Expand Up @@ -387,6 +354,76 @@ func (s *Store) DeleteSecureEnvelope(ctx context.Context, txID uuid.UUID, envID
return tx.Commit()
}

//===========================================================================
// Secure Envelope Fetching
//===========================================================================

const (
latestSecEnvSQL = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID ORDER BY timestamp DESC LIMIT 1"
latestSecEnvByDirectionSQL = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID AND direction=:direction ORDER BY timestamp DESC LIMIT 1"
)

func (s *Store) LatestSecureEnvelope(ctx context.Context, envelopeID uuid.UUID, direction string) (env *models.SecureEnvelope, err error) {
var tx *sql.Tx
if tx, err = s.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}); err != nil {
return nil, err
}
defer tx.Rollback()

var result *sql.Row
if direction == "" || direction == models.DirectionAny {
// Get the latest secure envelope regardless of the direction
result = tx.QueryRow(latestSecEnvSQL, sql.Named("envelopeID", envelopeID))
} else {
// Specify the direction to get the latest envelope for
result = tx.QueryRow(latestSecEnvByDirectionSQL, sql.Named("envelopeID", envelopeID), sql.Named("direction", direction))
}

env = &models.SecureEnvelope{}
if err = env.Scan(result); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, dberr.ErrNotFound
}
return nil, err
}

tx.Commit()
return env, nil
}

const (
latestPayload = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID AND is_error=false ORDER BY timestamp DESC LIMIT 1"
latestPayloadByDirectionSQL = "SELECT * FROM secure_envelopes WHERE envelope_id=:envelopeID AND is_error=false AND direction=:direction ORDER BY timestamp DESC LIMIT 1"
)

func (s *Store) LatestPayloadEnvelope(ctx context.Context, envelopeID uuid.UUID, direction string) (env *models.SecureEnvelope, err error) {
var tx *sql.Tx
if tx, err = s.BeginTx(ctx, &sql.TxOptions{ReadOnly: true}); err != nil {
return nil, err
}
defer tx.Rollback()

var result *sql.Row
if direction == "" || direction == models.DirectionAny {
// Get the latest secure envelope regardless of the direction
result = tx.QueryRow(latestPayload, sql.Named("envelopeID", envelopeID))
} else {
// Specify the direction to get the latest envelope for
result = tx.QueryRow(latestPayloadByDirectionSQL, sql.Named("envelopeID", envelopeID), sql.Named("direction", direction))
}

env = &models.SecureEnvelope{}
if err = env.Scan(result); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, dberr.ErrNotFound
}
return nil, err
}

tx.Commit()
return env, nil
}

//===========================================================================
// Prepared Transactions
//===========================================================================
Expand Down
3 changes: 2 additions & 1 deletion pkg/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ type TransactionStore interface {
UpdateTransaction(context.Context, *models.Transaction) error
DeleteTransaction(context.Context, uuid.UUID) error
PrepareTransaction(context.Context, uuid.UUID) (models.PreparedTransaction, error)
LatestSecureEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error)
}

// SecureEnvelopes are associated with individual transactions.
Expand All @@ -87,6 +86,8 @@ type SecureEnvelopeStore interface {
RetrieveSecureEnvelope(ctx context.Context, txID uuid.UUID, envID ulid.ULID) (*models.SecureEnvelope, error)
UpdateSecureEnvelope(context.Context, *models.SecureEnvelope) error
DeleteSecureEnvelope(ctx context.Context, txID uuid.UUID, envID ulid.ULID) error
LatestSecureEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error)
LatestPayloadEnvelope(ctx context.Context, txID uuid.UUID, direction string) (*models.SecureEnvelope, error)
}

// AccountStore provides CRUD interactions with Account models.
Expand Down
7 changes: 5 additions & 2 deletions pkg/web/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ type Client interface {
Export(context.Context, io.Writer) error

// Transaction Detail Actions
Preview(ctx context.Context, transactionID uuid.UUID) (*Envelope, error)
SendEnvelope(ctx context.Context, transactionID uuid.UUID, in *Envelope) (*Envelope, error)
Accept(ctx context.Context, transactionID uuid.UUID) (*Envelope, error)
LatestPayloadEnvelope(ctx context.Context, transactionID uuid.UUID) (*Envelope, error)
AcceptPreview(ctx context.Context, transactionID uuid.UUID) (*Envelope, error)
Accept(ctx context.Context, transactionID uuid.UUID, in *Envelope) (*Envelope, error)
Reject(ctx context.Context, transactionID uuid.UUID, in *Rejection) (*Envelope, error)
RepairPreview(ctx context.Context, transactionID uuid.UUID) (*Repair, error)
Repair(ctx context.Context, transactionID uuid.UUID, in *Envelope) (*Envelope, error)

// SecureEnvelopes Resource
ListSecureEnvelopes(ctx context.Context, transactionID uuid.UUID, in *EnvelopeListQuery) (*EnvelopesList, error)
Expand Down
46 changes: 36 additions & 10 deletions pkg/web/api/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,31 +235,39 @@ func (s *APIv1) Export(ctx context.Context, w io.Writer) (err error) {
// Transaction Detail Actions
//===========================================================================

const previewEP = "preview"
const sendEP = "send"

func (s *APIv1) Preview(ctx context.Context, transactionID uuid.UUID) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), previewEP)
if err = s.Detail(ctx, endpoint, &out); err != nil {
func (s *APIv1) SendEnvelope(ctx context.Context, transactionID uuid.UUID, in *Envelope) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), sendEP)
if err = s.Create(ctx, endpoint, in, &out); err != nil {
return nil, err
}
return out, nil
}

const sendEP = "send"
const payloadEP = "payload"

func (s *APIv1) SendEnvelope(ctx context.Context, transactionID uuid.UUID, in *Envelope) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), sendEP)
if err = s.Create(ctx, endpoint, in, &out); err != nil {
func (s *APIv1) LatestPayloadEnvelope(ctx context.Context, transactionID uuid.UUID) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), payloadEP)
if err = s.Detail(ctx, endpoint, &out); err != nil {
return nil, err
}
return out, nil
}

const acceptEP = "accept"

func (s *APIv1) Accept(ctx context.Context, transactionID uuid.UUID) (out *Envelope, err error) {
func (s *APIv1) AcceptPreview(ctx context.Context, transactionID uuid.UUID) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), acceptEP)
if err = s.Create(ctx, endpoint, nil, &out); err != nil {
if err = s.Detail(ctx, endpoint, &out); err != nil {
return nil, err
}
return out, nil
}

func (s *APIv1) Accept(ctx context.Context, transactionID uuid.UUID, in *Envelope) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), acceptEP)
if err = s.Create(ctx, endpoint, in, &out); err != nil {
return nil, err
}
return out, nil
Expand All @@ -275,6 +283,24 @@ func (s *APIv1) Reject(ctx context.Context, transactionID uuid.UUID, in *Rejecti
return out, nil
}

const repairEP = "repair"

func (s *APIv1) RepairPreview(ctx context.Context, transactionID uuid.UUID) (out *Repair, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), repairEP)
if err = s.Detail(ctx, endpoint, &out); err != nil {
return nil, err
}
return out, nil
}

func (s *APIv1) Repair(ctx context.Context, transactionID uuid.UUID, in *Envelope) (out *Envelope, err error) {
endpoint, _ := url.JoinPath(transactionsEP, transactionID.String(), repairEP)
if err = s.Create(ctx, endpoint, in, &out); err != nil {
return nil, err
}
return out, nil
}

//===========================================================================
// Secure and Decrypted Envelopes Resource
//===========================================================================
Expand Down
1 change: 1 addition & 0 deletions pkg/web/api/v1/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var (

var (
ErrInvalidTimestamp = errors.New("payload timestamp has invalid string format")
ErrInvalidRejection = errors.New("envelope does not contain a rejection/repair error")
)

// Construct a new response for an error or simply return unsuccessful.
Expand Down
19 changes: 19 additions & 0 deletions pkg/web/api/v1/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ type Rejection struct {
RequestRetry bool `json:"request_retry"`
}

type Repair struct {
Error *Rejection
Envelope *Envelope
}

type TransactionQuery struct {
Detail string `json:"detail" url:"detail,omitempty" form:"detail"`
}
Expand Down Expand Up @@ -521,6 +526,20 @@ func (q *TransactionQuery) Validate() (err error) {
// Rejection
//===========================================================================

func NewRejection(env *models.SecureEnvelope) (out *Rejection, err error) {
if !env.IsError {
return nil, ErrInvalidRejection
}

out = &Rejection{
Code: env.Envelope.Error.Code.String(),
Message: env.Envelope.Error.Message,
RequestRetry: env.Envelope.Error.Retry,
}

return out, nil
}

func (r *Rejection) Validate() (err error) {
// Check that the error code is valid
r.Code = strings.ToUpper(strings.TrimSpace(r.Code))
Expand Down
7 changes: 6 additions & 1 deletion pkg/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,15 @@ func (s *Server) setupRoutes() (err error) {
transactions.GET("/export", authorize(permiss.TravelRuleManage), s.ExportTransactions)

// Transaction specific actions
// TODO: deprecate /:id/preview
transactions.POST("/:id/send", authorize(permiss.TravelRuleManage), s.SendEnvelopeForTransaction)
transactions.GET("/:id/preview", authorize(permiss.TravelRuleManage), s.AcceptTransactionPreview)
transactions.GET("/:id/payload", authorize(permiss.TravelRuleView), s.LatestPayloadEnvelope)
transactions.GET("/:id/preview", authorize(permiss.TravelRuleView), s.AcceptTransactionPreview)
transactions.GET("/:id/accept", authorize(permiss.TravelRuleView), s.AcceptTransactionPreview)
transactions.POST("/:id/accept", authorize(permiss.TravelRuleManage), s.AcceptTransaction)
transactions.POST("/:id/reject", authorize(permiss.TravelRuleManage), s.RejectTransaction)
transactions.GET("/:id/repair", authorize(permiss.TravelRuleView), s.RepairTransactionPreview)
transactions.POST("/:id/repair", authorize(permiss.TravelRuleManage), s.RepairTransaction)

// SecureEnvelope Resource (nested on Transactions)
se := transactions.Group("/:id/secure-envelopes")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div></div>
Loading

0 comments on commit 527edec

Please sign in to comment.