github.com/gradientzero/comby-store-postgres
Advanced tools
@@ -7,3 +7,3 @@ package store_test | ||
| "github.com/gradientzero/comby-store-postgres" | ||
| store "github.com/gradientzero/comby-store-postgres" | ||
| "github.com/gradientzero/comby/v2" | ||
@@ -116,1 +116,121 @@ ) | ||
| } | ||
| func TestCommandStoreEncrypted(t *testing.T) { | ||
| var err error | ||
| ctx := context.Background() | ||
| // create crypto service | ||
| key := []byte("12345678901234567890123456789012") | ||
| cryptoService, _ := comby.NewCryptoService(key) | ||
| // setup and init store | ||
| commandStore := store.NewCommandStorePostgres("localhost", 5432, "postgres", "mysecretpassword", "postgres") | ||
| if err = commandStore.Init(ctx, | ||
| comby.CommandStoreOptionWithCryptoService(cryptoService), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // create domain data to encrypt/decrypt | ||
| type MyDomainCommand struct { | ||
| String string | ||
| Int int64 | ||
| Boolean bool | ||
| } | ||
| domainData := &MyDomainCommand{ | ||
| String: "string", | ||
| Int: 123, | ||
| Boolean: true, | ||
| } | ||
| // Create values | ||
| cmd1 := &comby.BaseCommand{ | ||
| CommandUuid: comby.NewUuid(), | ||
| TenantUuid: "TenantUuid_1", | ||
| Domain: "Domain_1", | ||
| CreatedAt: 1000, | ||
| DomainCmd: domainData, | ||
| } | ||
| if err := commandStore.Create(ctx, | ||
| comby.CommandStoreCreateOptionWithCommand(cmd1), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // Get a value | ||
| if _cmd1, err := commandStore.Get(ctx, | ||
| comby.CommandStoreGetOptionWithCommandUuid(cmd1.CommandUuid), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } else { | ||
| _domainData := &MyDomainCommand{} | ||
| _domainData, _ = comby.Deserialize(_cmd1.GetDomainCmdBytes(), _domainData) | ||
| if _domainData.String != "string" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 123 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != true { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // List all commands | ||
| if cmds, _, err := commandStore.List(ctx); err == nil { | ||
| _cmd1 := cmds[0] | ||
| _domainData := &MyDomainCommand{} | ||
| _domainData, _ = comby.Deserialize(_cmd1.GetDomainCmdBytes(), _domainData) | ||
| if _domainData.String != "string" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 123 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != true { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // Update event | ||
| domainData.String = "string2" | ||
| domainData.Int = 456 | ||
| domainData.Boolean = false | ||
| cmd1.DomainCmd = domainData | ||
| // Delete an event | ||
| if err := commandStore.Update(ctx, | ||
| comby.CommandStoreUpdateOptionWithCommand(cmd1), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // Get a value | ||
| if _cmd1, err := commandStore.Get(ctx, | ||
| comby.CommandStoreGetOptionWithCommandUuid(cmd1.CommandUuid), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } else { | ||
| _domainData := &MyDomainCommand{} | ||
| _domainData, _ = comby.Deserialize(_cmd1.GetDomainCmdBytes(), _domainData) | ||
| if _domainData.String != "string2" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 456 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != false { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // reset database | ||
| if err := commandStore.Reset(ctx); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // close connection | ||
| if err := commandStore.Close(ctx); err != nil { | ||
| t.Fatalf("failed to close connection: %v", err) | ||
| } | ||
| } |
@@ -6,2 +6,3 @@ package store | ||
| "database/sql" | ||
| "encoding/hex" | ||
| "fmt" | ||
@@ -29,4 +30,4 @@ | ||
| func NewCommandStorePostgres(host string, port int, user, password, dbName string) comby.CommandStore { | ||
| return &commandStorePostgres{ | ||
| func NewCommandStorePostgres(host string, port int, user, password, dbName string, opts ...comby.CommandStoreOption) comby.CommandStore { | ||
| cs := &commandStorePostgres{ | ||
| host: host, | ||
@@ -38,2 +39,8 @@ port: port, | ||
| } | ||
| for _, opt := range opts { | ||
| if _, err := opt(&cs.options); err != nil { | ||
| return nil | ||
| } | ||
| } | ||
| return cs | ||
| } | ||
@@ -143,2 +150,9 @@ | ||
| // encrypt domain data if crypto service is provided | ||
| if cs.options.CryptoService != nil { | ||
| if err := cs.encryptDomainData(dbRecord); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // sql begin transaction | ||
@@ -234,2 +248,9 @@ tx, err := cs.db.Begin() | ||
| // decrypt domain data if crypto service is provided | ||
| if cs.options.CryptoService != nil { | ||
| if err := cs.decryptDomainData(&dbRecord); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| // db record to command | ||
@@ -362,2 +383,11 @@ cmd, err := internal.DbCommandToBaseCommand(&dbRecord) | ||
| // decrypt domain data if crypto service is provided | ||
| if cs.options.CryptoService != nil { | ||
| for _, dbRecord := range dbRecords { | ||
| if err := cs.decryptDomainData(dbRecord); err != nil { | ||
| return nil, 0, err | ||
| } | ||
| } | ||
| } | ||
| // convert | ||
@@ -397,2 +427,9 @@ cmds, err := internal.DbCommandsToBaseCommands(dbRecords) | ||
| // encrypt domain data if crypto service is provided | ||
| if cs.options.CryptoService != nil { | ||
| if err := cs.encryptDomainData(dbRecord); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // sql begin transaction | ||
@@ -413,3 +450,3 @@ tx, err := cs.db.Begin() | ||
| req_ctx=$7 | ||
| ) WHERE uuid=$8 LIMIT 1;` | ||
| WHERE uuid=$8;` | ||
| stmt, err := tx.Prepare(query) | ||
@@ -534,1 +571,36 @@ if err != nil { | ||
| } | ||
| func (cs *commandStorePostgres) encryptDomainData(dbRecord *internal.Command) error { | ||
| if cs.options.CryptoService == nil { | ||
| return fmt.Errorf("'%s' failed - crypto service is nil", cs.String()) | ||
| } | ||
| domainData := []byte(dbRecord.DataBytes) | ||
| if len(domainData) < 1 { | ||
| return fmt.Errorf("'%s' failed - domain data is empty", cs.String()) | ||
| } | ||
| if encryptedData, err := cs.options.CryptoService.Encrypt(domainData); err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to encrypt domain data: %w", cs.String(), err) | ||
| } else { | ||
| dbRecord.DataBytes = hex.EncodeToString(encryptedData) | ||
| } | ||
| return nil | ||
| } | ||
| func (cs *commandStorePostgres) decryptDomainData(dbRecord *internal.Command) error { | ||
| if cs.options.CryptoService == nil { | ||
| return fmt.Errorf("'%s' failed - crypto service is nil", cs.String()) | ||
| } | ||
| encryptedData, err := hex.DecodeString(dbRecord.DataBytes) | ||
| if err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to decode hex domain data: %w", cs.String(), err) | ||
| } | ||
| if len(encryptedData) < 1 { | ||
| return fmt.Errorf("'%s' failed - encrypted domain data is empty", cs.String()) | ||
| } | ||
| if decryptedData, err := cs.options.CryptoService.Decrypt(encryptedData); err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to decrypt domain data: %w", cs.String(), err) | ||
| } else { | ||
| dbRecord.DataBytes = string(decryptedData) | ||
| } | ||
| return nil | ||
| } |
@@ -117,1 +117,121 @@ package store_test | ||
| } | ||
| func TestEventStoreWithEncrypted(t *testing.T) { | ||
| var err error | ||
| ctx := context.Background() | ||
| // create crypto service | ||
| key := []byte("12345678901234567890123456789012") | ||
| cryptoService, _ := comby.NewCryptoService(key) | ||
| // setup and init store | ||
| eventStore := store.NewEventStorePostgres("localhost", 5432, "postgres", "mysecretpassword", "postgres") | ||
| if err = eventStore.Init(ctx, | ||
| comby.EventStoreOptionWithCryptoService(cryptoService), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // create domain data to encrypt/decrypt | ||
| type MyDomainEvent struct { | ||
| String string | ||
| Int int64 | ||
| Boolean bool | ||
| } | ||
| domainData := &MyDomainEvent{ | ||
| String: "string", | ||
| Int: 123, | ||
| Boolean: true, | ||
| } | ||
| // Create values | ||
| evt1 := &comby.BaseEvent{ | ||
| EventUuid: comby.NewUuid(), | ||
| AggregateUuid: "AggregateUuid_1", | ||
| Domain: "Domain_1", | ||
| CreatedAt: 1000, | ||
| Version: 1, | ||
| DomainEvt: domainData, | ||
| } | ||
| if err := eventStore.Create(ctx, | ||
| comby.EventStoreCreateOptionWithEvent(evt1), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // Get a value | ||
| if _evt1, err := eventStore.Get(ctx, | ||
| comby.EventStoreGetOptionWithEventUuid(evt1.EventUuid), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } else { | ||
| _domainData := &MyDomainEvent{} | ||
| _domainData, _ = comby.Deserialize(_evt1.GetDomainEvtBytes(), _domainData) | ||
| if _domainData.String != "string" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 123 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != true { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // List all events | ||
| if evts, _, err := eventStore.List(ctx); err == nil { | ||
| _evt1 := evts[0] | ||
| _domainData := &MyDomainEvent{} | ||
| _domainData, _ = comby.Deserialize(_evt1.GetDomainEvtBytes(), _domainData) | ||
| if _domainData.String != "string" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 123 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != true { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // Update event | ||
| domainData.String = "string2" | ||
| domainData.Int = 456 | ||
| domainData.Boolean = false | ||
| evt1.DomainEvt = domainData | ||
| if err := eventStore.Update(ctx, | ||
| comby.EventStoreUpdateOptionWithEvent(evt1), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // Get a value | ||
| if _evt1, err := eventStore.Get(ctx, | ||
| comby.EventStoreGetOptionWithEventUuid(evt1.EventUuid), | ||
| ); err != nil { | ||
| t.Fatal(err) | ||
| } else { | ||
| _domainData := &MyDomainEvent{} | ||
| _domainData, _ = comby.Deserialize(_evt1.GetDomainEvtBytes(), _domainData) | ||
| if _domainData.String != "string2" { | ||
| t.Fatalf("wrong value: %q", _domainData.String) | ||
| } | ||
| if _domainData.Int != 456 { | ||
| t.Fatalf("wrong value: %q", _domainData.Int) | ||
| } | ||
| if _domainData.Boolean != false { | ||
| t.Fatalf("wrong value") | ||
| } | ||
| } | ||
| // reset database | ||
| if err := eventStore.Reset(ctx); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| // close connection | ||
| if err := eventStore.Close(ctx); err != nil { | ||
| t.Fatalf("failed to close connection: %v", err) | ||
| } | ||
| } |
@@ -6,2 +6,3 @@ package store | ||
| "database/sql" | ||
| "encoding/hex" | ||
| "fmt" | ||
@@ -29,4 +30,4 @@ | ||
| func NewEventStorePostgres(host string, port int, user, password, dbName string) comby.EventStore { | ||
| return &eventStorePostgres{ | ||
| func NewEventStorePostgres(host string, port int, user, password, dbName string, opts ...comby.EventStoreOption) comby.EventStore { | ||
| es := &eventStorePostgres{ | ||
| host: host, | ||
@@ -38,2 +39,8 @@ port: port, | ||
| } | ||
| for _, opt := range opts { | ||
| if _, err := opt(&es.options); err != nil { | ||
| return nil | ||
| } | ||
| } | ||
| return es | ||
| } | ||
@@ -151,2 +158,9 @@ | ||
| // encrypt domain data if crypto service is provided | ||
| if es.options.CryptoService != nil { | ||
| if err := es.encryptDomainData(dbRecord); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // sql begin transaction | ||
@@ -248,2 +262,9 @@ tx, err := es.db.Begin() | ||
| // decrypt domain data if crypto service is provided | ||
| if es.options.CryptoService != nil { | ||
| if err := es.decryptDomainData(&dbRecord); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| // db record to event | ||
@@ -386,2 +407,11 @@ evt, err := internal.DbEventToBaseEvent(&dbRecord) | ||
| // decrypt domain data if crypto service is provided | ||
| if es.options.CryptoService != nil { | ||
| for _, dbRecord := range dbRecords { | ||
| if err := es.decryptDomainData(dbRecord); err != nil { | ||
| return nil, 0, err | ||
| } | ||
| } | ||
| } | ||
| // convert | ||
@@ -422,2 +452,9 @@ evts, err := internal.DbEventsToBaseEvents(dbRecords) | ||
| // encrypt domain data if crypto service is provided | ||
| if es.options.CryptoService != nil { | ||
| if err := es.encryptDomainData(dbRecord); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| // sql begin transaction | ||
@@ -660,1 +697,36 @@ tx, err := es.db.Begin() | ||
| } | ||
| func (es *eventStorePostgres) encryptDomainData(dbRecord *internal.Event) error { | ||
| if es.options.CryptoService == nil { | ||
| return fmt.Errorf("'%s' failed - crypto service is nil", es.String()) | ||
| } | ||
| domainData := []byte(dbRecord.DataBytes) | ||
| if len(domainData) < 1 { | ||
| return fmt.Errorf("'%s' failed - domain data is empty", es.String()) | ||
| } | ||
| if encryptedData, err := es.options.CryptoService.Encrypt(domainData); err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to encrypt domain data: %w", es.String(), err) | ||
| } else { | ||
| dbRecord.DataBytes = hex.EncodeToString(encryptedData) | ||
| } | ||
| return nil | ||
| } | ||
| func (es *eventStorePostgres) decryptDomainData(dbRecord *internal.Event) error { | ||
| if es.options.CryptoService == nil { | ||
| return fmt.Errorf("'%s' failed - crypto service is nil", es.String()) | ||
| } | ||
| encryptedData, err := hex.DecodeString(dbRecord.DataBytes) | ||
| if err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to decode hex domain data: %w", es.String(), err) | ||
| } | ||
| if len(encryptedData) < 1 { | ||
| return fmt.Errorf("'%s' failed - encrypted domain data is empty", es.String()) | ||
| } | ||
| if decryptedData, err := es.options.CryptoService.Decrypt(encryptedData); err != nil { | ||
| return fmt.Errorf("'%s' failed - failed to decrypt domain data: %w", es.String(), err) | ||
| } else { | ||
| dbRecord.DataBytes = string(decryptedData) | ||
| } | ||
| return nil | ||
| } |
+21
-1
@@ -12,2 +12,22 @@ module github.com/gradientzero/comby-store-postgres | ||
| require github.com/huandu/go-clone v1.7.2 // indirect | ||
| require ( | ||
| github.com/dustin/go-humanize v1.0.1 // indirect | ||
| github.com/google/uuid v1.6.0 // indirect | ||
| github.com/huandu/go-clone v1.7.2 // indirect | ||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||
| github.com/mattn/go-isatty v0.0.20 // indirect | ||
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||
| golang.org/x/mod v0.14.0 // indirect | ||
| golang.org/x/sys v0.18.0 // indirect | ||
| golang.org/x/tools v0.17.0 // indirect | ||
| lukechampine.com/uint128 v1.2.0 // indirect | ||
| modernc.org/cc/v3 v3.40.0 // indirect | ||
| modernc.org/ccgo/v3 v3.16.13 // indirect | ||
| modernc.org/libc v1.29.0 // indirect | ||
| modernc.org/mathutil v1.6.0 // indirect | ||
| modernc.org/memory v1.7.2 // indirect | ||
| modernc.org/opt v0.1.3 // indirect | ||
| modernc.org/sqlite v1.28.0 // indirect | ||
| modernc.org/strutil v1.1.3 // indirect | ||
| modernc.org/token v1.0.1 // indirect | ||
| ) |