Skip to content

Commit

Permalink
[SDP-1170] Add PATCH /organization/circle-config (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
marwen-abid authored Jun 14, 2024
1 parent 77ea816 commit 901f457
Show file tree
Hide file tree
Showing 34 changed files with 1,072 additions and 89 deletions.
1 change: 1 addition & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ func (c *ServeCommand) Command(serverService ServerServiceInterface, monitorServ
serveOpts.MonitorService = monitorService
serveOpts.BaseURL = globalOptions.BaseURL
serveOpts.NetworkPassphrase = globalOptions.NetworkPassphrase
serveOpts.DistAccEncryptionPassphrase = txSubmitterOpts.SignatureServiceOptions.DistAccEncryptionPassphrase

// Inject metrics server dependencies
metricsServeOpts.MonitorService = monitorService
Expand Down
1 change: 1 addition & 0 deletions cmd/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func Test_serve(t *testing.T) {
SubmitterEngine: submitterEngine,
DistributionAccountService: mDistAccService,
MaxInvitationSMSResendAttempts: 3,
DistAccEncryptionPassphrase: distributionAccPrivKey,
}
serveOpts.AnchorPlatformAPIService, err = anchorplatform.NewAnchorPlatformAPIService(httpclient.DefaultClient(), serveOpts.AnchorPlatformBasePlatformURL, serveOpts.AnchorPlatformOutgoingJWTSecret)
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
"github.com/stellar/go/support/log"

"github.com/stellar/stellar-disbursement-platform-backend/internal/monitor"
)

Expand Down
10 changes: 10 additions & 0 deletions db/dbtest/dbtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ import (
)

func OpenWithoutMigrations(t *testing.T) *dbtest.DB {
t.Helper()

db := dbtest.Postgres(t)
return db
}

func openWithMigrations(t *testing.T, configs ...migrations.MigrationRouter) *dbtest.DB {
t.Helper()

db := OpenWithoutMigrations(t)

conn := db.Open()
Expand All @@ -34,6 +38,8 @@ func openWithMigrations(t *testing.T, configs ...migrations.MigrationRouter) *db
}

func Open(t *testing.T) *dbtest.DB {
t.Helper()

return openWithMigrations(t,
migrations.AdminMigrationRouter,
migrations.SDPMigrationRouter,
Expand All @@ -43,9 +49,13 @@ func Open(t *testing.T) *dbtest.DB {
}

func OpenWithAdminMigrationsOnly(t *testing.T) *dbtest.DB {
t.Helper()

return openWithMigrations(t, migrations.AdminMigrationRouter)
}

func OpenWithTSSMigrationsOnly(t *testing.T) *dbtest.DB {
t.Helper()

return openWithMigrations(t, migrations.TSSMigrationRouter)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- +migrate Up
CREATE TABLE circle_client_config (
wallet_id VARCHAR(64) NOT NULL,
encrypted_api_key VARCHAR(256) NOT NULL,
encrypter_public_key VARCHAR(256) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- +migrate StatementBegin
CREATE OR REPLACE FUNCTION enforce_single_row_for_circle_client_config()
RETURNS TRIGGER AS $$
BEGIN
IF (SELECT COUNT(*) FROM circle_client_config) != 0 THEN
RAISE EXCEPTION 'circle_client_config must contain exactly one row';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- +migrate StatementEnd

CREATE TRIGGER enforce_single_row_for_circle_client_config_insert_trigger
BEFORE INSERT ON circle_client_config
FOR EACH ROW
EXECUTE FUNCTION enforce_single_row_for_circle_client_config();

CREATE TRIGGER enforce_single_row_for_circle_client_config_delete_trigger
BEFORE DELETE ON circle_client_config
FOR EACH ROW
EXECUTE FUNCTION enforce_single_row_for_circle_client_config();


-- TRIGGER: updated_at
CREATE TRIGGER refresh_circle_client_config_updated_at BEFORE UPDATE ON circle_client_config FOR EACH ROW EXECUTE PROCEDURE update_at_refresh();


-- +migrate Down

DROP TRIGGER enforce_single_row_for_circle_client_config_delete_trigger ON circle_client_config;

DROP TRIGGER enforce_single_row_for_circle_client_config_insert_trigger ON circle_client_config;

DROP FUNCTION enforce_single_row_for_circle_client_config;

DROP TRIGGER refresh_circle_client_config_updated_at ON circle_client_config;

DROP TABLE circle_client_config;
152 changes: 152 additions & 0 deletions internal/circle/client_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package circle

import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"time"

"github.com/stellar/stellar-disbursement-platform-backend/db"
)

type ClientConfig struct {
EncryptedAPIKey *string `db:"encrypted_api_key"`
WalletID *string `db:"wallet_id"`
EncrypterPublicKey *string `db:"encrypter_public_key"`
UpdatedAt time.Time `db:"updated_at"`
CreatedAt time.Time `db:"created_at"`
}

type ClientConfigModel struct {
DBConnectionPool db.DBConnectionPool
}

func NewClientConfigModel(dbConnectionPool db.DBConnectionPool) *ClientConfigModel {
return &ClientConfigModel{DBConnectionPool: dbConnectionPool}
}

// Upsert insert or update the client configuration for Circle into the database.
func (m *ClientConfigModel) Upsert(ctx context.Context, configUpdate ClientConfigUpdate) error {
err := db.RunInTransaction(ctx, m.DBConnectionPool, nil, func(tx db.DBTransaction) error {
existingConfig, err := m.get(ctx, tx)
if err != nil {
return fmt.Errorf("getting existing circle config: %w", err)
}

if existingConfig == nil {
err = m.insert(ctx, tx, configUpdate)
if err != nil {
return fmt.Errorf("inserting new circle config: %w", err)
}
} else {
err = m.update(ctx, tx, configUpdate)
if err != nil {
return fmt.Errorf("updating existing circle config: %w", err)
}
}

return nil
})
if err != nil {
return fmt.Errorf("running transaction: %w", err)
}

return nil
}

// Get retrieves the circle client config from the database if it exists.
func (m *ClientConfigModel) Get(ctx context.Context) (*ClientConfig, error) {
return m.get(ctx, m.DBConnectionPool)
}

// get retrieves the circle client config from the database if it exists.
func (m *ClientConfigModel) get(ctx context.Context, sqlExec db.SQLExecuter) (*ClientConfig, error) {
const q = `SELECT * FROM circle_client_config`
var config ClientConfig
err := sqlExec.GetContext(ctx, &config, q)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, fmt.Errorf("getting circle config: %w", err)
}
return &config, nil
}

// insert inserts the circle client config into the database.
func (m *ClientConfigModel) insert(ctx context.Context, sqlExec db.SQLExecuter, config ClientConfigUpdate) error {
if err := config.validateForInsert(); err != nil {
return fmt.Errorf("invalid circle config for insert: %w", err)
}
const q = `
INSERT INTO circle_client_config (encrypted_api_key, wallet_id, encrypter_public_key)
VALUES ($1, $2, $3)
`
_, err := sqlExec.ExecContext(ctx, q, config.EncryptedAPIKey, config.WalletID, config.EncrypterPublicKey)
if err != nil {
return fmt.Errorf("inserting circle config: %w", err)
}
return nil
}

// update updates the circle client config in the database.
func (m *ClientConfigModel) update(ctx context.Context, sqlExec db.SQLExecuter, config ClientConfigUpdate) error {
if err := config.validate(); err != nil {
return fmt.Errorf("invalid circle config for update: %w", err)
}

query := `
UPDATE
circle_client_config
SET
%s
`

args := []interface{}{}
fields := []string{}
if config.WalletID != nil {
fields = append(fields, "wallet_id = ?")
args = append(args, config.WalletID)
}

if config.EncryptedAPIKey != nil {
fields = append(fields, "encrypted_api_key = ?", "encrypter_public_key = ?")
args = append(args, config.EncryptedAPIKey, config.EncrypterPublicKey)
}

query = m.DBConnectionPool.Rebind(fmt.Sprintf(query, strings.Join(fields, ", ")))

_, err := sqlExec.ExecContext(ctx, query, args...)
if err != nil {
return fmt.Errorf("error updating client config: %w", err)
}

return nil
}

type ClientConfigUpdate struct {
EncryptedAPIKey *string `db:"encrypted_api_key"`
WalletID *string `db:"wallet_id"`
EncrypterPublicKey *string `db:"encrypter_public_key"`
}

func (c ClientConfigUpdate) validate() error {
if c.WalletID == nil && c.EncryptedAPIKey == nil {
return fmt.Errorf("wallet_id or encrypted_api_key must be provided")
}

if c.EncryptedAPIKey != nil && c.EncrypterPublicKey == nil {
return fmt.Errorf("encrypter_public_key must be provided if encrypted_api_key is provided")
}

return nil
}

func (c ClientConfigUpdate) validateForInsert() error {
if c.WalletID == nil || c.EncryptedAPIKey == nil || c.EncrypterPublicKey == nil {
return fmt.Errorf("wallet_id, encrypted_api_key, and encrypter_public_key must be provided")
}
return nil
}
Loading

0 comments on commit 901f457

Please sign in to comment.