-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SDP-1170] Add
PATCH /organization/circle-config
(#326)
- Loading branch information
1 parent
77ea816
commit 901f457
Showing
34 changed files
with
1,072 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
db/migrations/sdp-migrations/2024-06-12.0-add-circle-client-config-table.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.