diff --git a/api/config.go b/api/config.go index b785b175..9bfebccf 100644 --- a/api/config.go +++ b/api/config.go @@ -31,6 +31,7 @@ func NewRepos(config APIConfig) repository.Repositories { * Not every service needs access to all of the repos, so we can pass in only the ones it needs. This will make it easier to test */ func NewServices(config APIConfig, repos repository.Repositories) service.Services { + unit21 := service.NewUnit21(repos) httpClient := service.NewHTTPClient(service.HTTPConfig{Timeout: time.Duration(30) * time.Second}) client := service.NewFingerprintClient(httpClient) fingerprint := service.NewFingerprint(client) @@ -52,8 +53,8 @@ func NewServices(config APIConfig, repos repository.Repositories) service.Servic platformRepos := repository.Repositories{Auth: repos.Auth, Platform: repos.Platform} platform := service.NewPlatform(platformRepos) - transaction := service.NewTransaction(repos, config.Redis) - user := service.NewUser(repos, auth, fingerprint, device) + transaction := service.NewTransaction(repos, config.Redis, unit21) + user := service.NewUser(repos, auth, fingerprint, device, unit21) return service.Services{ Auth: auth, diff --git a/pkg/internal/unit21/action.go b/pkg/internal/unit21/action.go index d4b17cea..b816bec2 100644 --- a/pkg/internal/unit21/action.go +++ b/pkg/internal/unit21/action.go @@ -6,7 +6,6 @@ import ( "github.com/String-xyz/string-api/pkg/internal/common" "github.com/String-xyz/string-api/pkg/model" - "github.com/String-xyz/string-api/pkg/repository" "github.com/rs/zerolog/log" ) @@ -17,18 +16,11 @@ type Action interface { eventSubtype string) (unit21Id string, err error) } -type ActionRepo struct { - User repository.User - Device repository.Device - Location repository.Location -} - type action struct { - repo ActionRepo } -func NewAction(r ActionRepo) Action { - return &action{repo: r} +func NewAction() Action { + return &action{} } func (a action) Create( diff --git a/pkg/internal/unit21/base.go b/pkg/internal/unit21/base.go index 822595de..9131b60e 100644 --- a/pkg/internal/unit21/base.go +++ b/pkg/internal/unit21/base.go @@ -63,7 +63,6 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { apiKey := os.Getenv("UNIT21_API_KEY") reqBodyBytes, err := json.Marshal(jsonBody) - if err != nil { log.Err(err).Msg("Could not encode into bytes") return nil, common.StringError(err) @@ -97,7 +96,7 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { return nil, common.StringError(err) } - log.Info().Str("body", string(body)).Msgf("Strinb of body grom response") + log.Info().Str("body", string(body)).Msgf("String of body from response") if res.StatusCode != 200 { log.Err(err).Str("url", url).Int("statusCode", res.StatusCode).Msg("Request failed to update") diff --git a/pkg/internal/unit21/instrument.go b/pkg/internal/unit21/instrument.go index e9ae797c..91c93f2a 100644 --- a/pkg/internal/unit21/instrument.go +++ b/pkg/internal/unit21/instrument.go @@ -15,18 +15,19 @@ type Instrument interface { Update(instrument model.Instrument) (unit21Id string, err error) } -type InstrumentRepo struct { +type InstrumentRepos struct { User repository.User Device repository.Device Location repository.Location } type instrument struct { - repo InstrumentRepo + action Action + repos InstrumentRepos } -func NewInstrument(r InstrumentRepo) Instrument { - return &instrument{repo: r} +func NewInstrument(r InstrumentRepos, a Action) Instrument { + return &instrument{repos: r, action: a} } func (i instrument) Create(instrument model.Instrument) (unit21Id string, err error) { @@ -70,6 +71,14 @@ func (i instrument) Create(instrument model.Instrument) (unit21Id string, err er } log.Info().Str("Unit21Id", u21Response.Unit21Id).Send() + + // Log create instrument action w/ Unit21 + _, err = i.action.Create(instrument, "Creation", u21Response.Unit21Id, "Creation") + if err != nil { + log.Err(err).Msg("Error creating a new instrument action in Unit21") + return u21Response.Unit21Id, common.StringError(err) + } + return u21Response.Unit21Id, nil } @@ -125,7 +134,7 @@ func (i instrument) getSource(userId string) (source string, err error) { log.Warn().Msg("No userId defined") return } - user, err := i.repo.User.GetById(userId) + user, err := i.repos.User.GetById(userId) if err != nil { log.Err(err).Msg("Failed go get user contacts") return "", common.StringError(err) @@ -143,7 +152,7 @@ func (i instrument) getEntities(userId string) (entity instrumentEntity, err err return } - user, err := i.repo.User.GetById(userId) + user, err := i.repos.User.GetById(userId) if err != nil { log.Err(err).Msg("Failed go get user contacts") err = common.StringError(err) @@ -164,7 +173,7 @@ func (i instrument) getInstrumentDigitalData(userId string) (digitalData instrum return } - devices, err := i.repo.Device.ListByUserId(userId, 100, 0) + devices, err := i.repos.Device.ListByUserId(userId, 100, 0) if err != nil { log.Err(err).Msg("Failed to get user devices") err = common.StringError(err) @@ -177,35 +186,36 @@ func (i instrument) getInstrumentDigitalData(userId string) (digitalData instrum return } -func (i instrument) getLocationData(locationId string) (locationData instrumentLocationData, err error) { +func (i instrument) getLocationData(locationId string) (locationData *instrumentLocationData, err error) { if locationId == "" { log.Warn().Msg("No locationId defined") return } - location, err := i.repo.Location.GetById(locationId) + location, err := i.repos.Location.GetById(locationId) if err != nil { log.Err(err).Msg("Failed go get instrument location") err = common.StringError(err) return } - - locationData = instrumentLocationData{ - Type: location.Type, - BuildingNumber: location.BuildingNumber, - UnitNumber: location.UnitNumber, - StreetName: location.StreetName, - City: location.City, - State: location.State, - PostalCode: location.PostalCode, - Country: location.Country, - VerifiedOn: int(location.CreatedAt.Unix()), + if location.CreatedAt.Unix() != 0 { + locationData = &instrumentLocationData{ + Type: location.Type, + BuildingNumber: location.BuildingNumber, + UnitNumber: location.UnitNumber, + StreetName: location.StreetName, + City: location.City, + State: location.State, + PostalCode: location.PostalCode, + Country: location.Country, + VerifiedOn: int(location.CreatedAt.Unix()), + } } return locationData, nil } -func mapToUnit21Instrument(instrument model.Instrument, source string, entityData instrumentEntity, digitalData instrumentDigitalData, locationData instrumentLocationData) *u21Instrument { +func mapToUnit21Instrument(instrument model.Instrument, source string, entityData instrumentEntity, digitalData instrumentDigitalData, locationData *instrumentLocationData) *u21Instrument { var instrumentTagArr []string if instrument.Tags != nil { for key, value := range instrument.Tags { @@ -225,12 +235,10 @@ func mapToUnit21Instrument(instrument model.Instrument, source string, entityDat RegisteredAt: int(instrument.CreatedAt.Unix()), ParentInstrumentId: "", Entities: entityArray, - CustomData: &instrumentCustomData{ - None: nil, - }, - DigitalData: &digitalData, - LocationData: &locationData, - Tags: instrumentTagArr, + CustomData: nil, //TODO: include platform in customData + DigitalData: &digitalData, + LocationData: locationData, + Tags: instrumentTagArr, // Options: &options, } diff --git a/pkg/internal/unit21/transaction.go b/pkg/internal/unit21/transaction.go index 1614aeb7..9a03ef44 100644 --- a/pkg/internal/unit21/transaction.go +++ b/pkg/internal/unit21/transaction.go @@ -16,18 +16,18 @@ type Transaction interface { Update(transaction model.Transaction) (unit21Id string, err error) } -type TransactionRepo struct { - TxLeg repository.TxLeg +type TransactionRepos struct { User repository.User + TxLeg repository.TxLeg Asset repository.Asset } type transaction struct { - repo TransactionRepo + repos TransactionRepos } -func NewTransaction(r TransactionRepo) Transaction { - return &transaction{repo: r} +func NewTransaction(r TransactionRepos) Transaction { + return &transaction{repos: r} } func (t transaction) Evaluate(transaction model.Transaction) (pass bool, err error) { @@ -118,28 +118,28 @@ func (t transaction) Update(transaction model.Transaction) (unit21Id string, err } func (t transaction) getTransactionData(transaction model.Transaction) (txData transactionData, err error) { - senderData, err := t.repo.TxLeg.GetById(transaction.OriginTxLegID) + senderData, err := t.repos.TxLeg.GetById(transaction.OriginTxLegID) if err != nil { log.Err(err).Msg("Failed go get origin transaction leg") err = common.StringError(err) return } - receiverData, err := t.repo.TxLeg.GetById(transaction.DestinationTxLegID) + receiverData, err := t.repos.TxLeg.GetById(transaction.DestinationTxLegID) if err != nil { log.Err(err).Msg("Failed go get origin transaction leg") err = common.StringError(err) return } - senderAsset, err := t.repo.Asset.GetById(senderData.AssetID) + senderAsset, err := t.repos.Asset.GetById(senderData.AssetID) if err != nil { log.Err(err).Msg("Failed go get transaction sender asset") err = common.StringError(err) return } - receiverAsset, err := t.repo.Asset.GetById(receiverData.AssetID) + receiverAsset, err := t.repos.Asset.GetById(receiverData.AssetID) if err != nil { log.Err(err).Msg("Failed go get transaction receiver asset") err = common.StringError(err) diff --git a/pkg/model/common.go b/pkg/model/common.go deleted file mode 100644 index 2ec67411..00000000 --- a/pkg/model/common.go +++ /dev/null @@ -1,12 +0,0 @@ -package model - -type FPVisitor struct { - VisitorID string - Country string - State string - IPAddress string - Timestamp int64 - Confidence float64 - Type string - UserAgent string -} diff --git a/pkg/model/entity.go b/pkg/model/entity.go index 57d460fc..0c8f0229 100644 --- a/pkg/model/entity.go +++ b/pkg/model/entity.go @@ -82,7 +82,7 @@ type Device struct { Type string `json:"type" db:"type"` Description string `json:"description" db:"description"` Fingerprint string `json:"fingerprint" db:"fingerprint"` - IpAddresses pq.StringArray `json:"ipAddresses" db:"ip_addresses"` + IpAddresses pq.StringArray `json:"ipAddresses,omitempty" db:"ip_addresses"` UserID string `json:"userId" db:"user_id"` } diff --git a/pkg/service/base.go b/pkg/service/base.go index ee055faf..8c3a04cf 100644 --- a/pkg/service/base.go +++ b/pkg/service/base.go @@ -14,4 +14,5 @@ type Services struct { User User Verification Verification Device Device + Unit21 Unit21 } diff --git a/pkg/service/device.go b/pkg/service/device.go index de5ca877..b4b5d87f 100644 --- a/pkg/service/device.go +++ b/pkg/service/device.go @@ -27,22 +27,26 @@ func NewDevice(repos repository.Repositories, f Fingerprint) Device { return &device{repos, f} } -func (d device) createDevice(userID string, visitor model.FPVisitor, description string) (model.Device, error) { +func (d device) createDevice(userID string, visitor FPVisitor, description string) (model.Device, error) { + addresses := pq.StringArray{} + if visitor.IPAddress.String != "" { + addresses = pq.StringArray{visitor.IPAddress.String} + } + return d.repos.Device.Create(model.Device{ UserID: userID, Fingerprint: visitor.VisitorID, Type: visitor.Type, - IpAddresses: pq.StringArray{visitor.IPAddress}, + IpAddresses: addresses, Description: description, LastUsedAt: time.Now(), }) } func (d device) CreateUnknownDevice(userID string) (model.Device, error) { - visitor := model.FPVisitor{ + visitor := FPVisitor{ VisitorID: "unknown", Type: "unknown", - IPAddress: "unknown", UserAgent: "unknown", } device, err := d.createDevice(userID, visitor, "an unknown device") diff --git a/pkg/service/fingerprint.go b/pkg/service/fingerprint.go index 6b90fb59..bbcecdcd 100644 --- a/pkg/service/fingerprint.go +++ b/pkg/service/fingerprint.go @@ -1,16 +1,25 @@ package service import ( + "database/sql" "errors" "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/String-xyz/string-api/pkg/model" ) type FPClient common.FingerprintClient type HTTPConfig common.HTTPConfig type HTTPClient common.HTTPClient -type FPVisitor = model.FPVisitor +type FPVisitor struct { + VisitorID string + Country string + State string + IPAddress sql.NullString + Timestamp int64 + Confidence float64 + Type string + UserAgent string +} func NewHTTPClient(config HTTPConfig) HTTPClient { return common.NewHTTPClient(common.HTTPConfig(config)) @@ -59,7 +68,7 @@ func (f fingerprint) hydrateVisitor(visitor common.FPVisitor) (FPVisitor, error) VisitorID: visitor.ID, Country: visit.IPLocation.Coutry.Code, State: state, - IPAddress: visit.IP, + IPAddress: sql.NullString{String: visit.IP}, Timestamp: visit.Timestamp, Confidence: visit.IPLocation.Confidence.Score, Type: visit.BrowserDetails.Device, diff --git a/pkg/service/transaction.go b/pkg/service/transaction.go index 4912c44b..2f0f57d0 100644 --- a/pkg/service/transaction.go +++ b/pkg/service/transaction.go @@ -10,7 +10,6 @@ import ( "time" "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/String-xyz/string-api/pkg/internal/unit21" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/repository" "github.com/String-xyz/string-api/pkg/store" @@ -45,9 +44,14 @@ type InternalIds struct { } type transaction struct { - repos repository.Repositories - redis store.RedisStore - ids InternalIds + repos repository.Repositories + redis store.RedisStore + ids InternalIds + unit21 Unit21 +} + +func NewTransaction(repos repository.Repositories, redis store.RedisStore, unit21 Unit21) Transaction { + return &transaction{repos: repos, redis: redis, unit21: unit21} } type transactionProcessingData struct { @@ -67,10 +71,6 @@ type transactionProcessingData struct { trueGas *uint64 } -func NewTransaction(repos repository.Repositories, redis store.RedisStore) Transaction { - return &transaction{repos: repos, redis: redis} -} - func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest, error) { // TODO: use prefab service to parse d and fill out known params res := model.ExecutionRequest{TransactionRequest: d} @@ -349,8 +349,14 @@ func (t transaction) safetyCheck(p transactionProcessingData) (transactionProces } // Validate Transaction through Real Time Rules engine - // RTR is not released for Unit21 Production (slated for Late February 2023) - evaluation, err := t.unit21Evaluate(p.transactionModel.ID) + txModel, err := t.repos.Transaction.GetById(p.transactionModel.ID) + if err != nil { + log.Err(err).Msg("error getting tx model in unit21 Tx Evalute") + return p, common.StringError(err) + } + + evaluation, err := t.unit21.Transaction.Evaluate(txModel) + if err != nil { // If Unit21 Evaluate fails, just log, but otherwise continue with the transaction log.Err(err).Msg("Error evaluating transaction in Unit21") @@ -477,7 +483,8 @@ func (t transaction) addCardInstrumentIdIfNew(p transactionProcessingData) (stri if err != nil && !strings.Contains(err.Error(), "not found") { // because we are wrapping error and care about its value return "", common.StringError(err) } else if err == nil && instrument.UserID != "" { - return instrument.ID, nil // instrument already exists + go t.unit21.Instrument.Update(instrument) // if instrument already exists, update it anyways + return instrument.ID, nil // return if instrument already exists } // We should gather type from the payment processor @@ -497,7 +504,9 @@ func (t transaction) addCardInstrumentIdIfNew(p transactionProcessingData) (stri if err != nil { return "", common.StringError(err) } - go t.unit21CreateInstrument(instrument) + + go t.unit21.Instrument.Create(instrument) + return instrument.ID, nil } @@ -506,16 +515,19 @@ func (t transaction) addWalletInstrumentIdIfNew(address string, id string) (stri if err != nil && !strings.Contains(err.Error(), "not found") { return "", common.StringError(err) } else if err == nil && instrument.PublicKey == address { - return instrument.ID, nil + go t.unit21.Instrument.Update(instrument) // if instrument already exists, update it anyways + return instrument.ID, nil // return if instrument already exists } // Create a new instrument - instrument = model.Instrument{Type: "CryptoWallet", Status: "external", Network: "ethereum", PublicKey: address, UserID: id} // No locationID or userID because this wallet was not registered with the user and is some other recipient + instrument = model.Instrument{Type: "Crypto Wallet", Status: "external", Network: "ethereum", PublicKey: address, UserID: id} // No locationID or userID because this wallet was not registered with the user and is some other recipient instrument, err = t.repos.Instrument.Create(instrument) if err != nil { return "", common.StringError(err) } - go t.unit21CreateInstrument(instrument) + + go t.unit21.Instrument.Create(instrument) + return instrument.ID, nil } @@ -774,37 +786,6 @@ func floatToFixedString(value float64, decimals int) string { return strconv.FormatUint(uint64(value*(math.Pow10(decimals-1))), 10) } -func (t transaction) unit21CreateInstrument(instrument model.Instrument) (err error) { - u21InstrumentRepo := unit21.InstrumentRepo{ - User: t.repos.User, - Device: t.repos.Device, - Location: t.repos.Location, // empty until fingerprint integration - } - - u21Instrument := unit21.NewInstrument(u21InstrumentRepo) - u21InstrumentId, err := u21Instrument.Create(instrument) - if err != nil { - log.Err(err).Msg("Error creating new instrument in Unit21") - return common.StringError(err) - } - - // Log create instrument action w/ Unit21 - u21ActionRepo := unit21.ActionRepo{ - User: t.repos.User, - Device: t.repos.Device, - Location: t.repos.Location, // empty until fingerprint integration - } - - u21Action := unit21.NewAction(u21ActionRepo) - _, err = u21Action.Create(instrument, "Creation", u21InstrumentId, "Creation") - if err != nil { - log.Err(err).Msg("Error creating a new instrument action in Unit21") - return common.StringError(err) - } - - return -} - func (t transaction) unit21CreateTransaction(transactionId string) (err error) { txModel, err := t.repos.Transaction.GetById(transactionId) if err != nil { @@ -812,14 +793,7 @@ func (t transaction) unit21CreateTransaction(transactionId string) (err error) { return common.StringError(err) } - u21Repo := unit21.TransactionRepo{ - TxLeg: t.repos.TxLeg, - User: t.repos.User, - Asset: t.repos.Asset, - } - - u21Tx := unit21.NewTransaction(u21Repo) - _, err = u21Tx.Create(txModel) + _, err = t.unit21.Transaction.Create(txModel) if err != nil { log.Err(err).Msg("Error updating unit21 in Tx Postprocess") return common.StringError(err) @@ -828,24 +802,6 @@ func (t transaction) unit21CreateTransaction(transactionId string) (err error) { return nil } -func (t transaction) unit21Evaluate(transactionId string) (evaluation bool, err error) { - //Check transaction in Unit21 - txModel, err := t.repos.Transaction.GetById(transactionId) - if err != nil { - log.Err(err).Msg("error getting tx model in unit21 Tx Evalute") - return evaluation, common.StringError(err) - } - - u21Repo := unit21.TransactionRepo{ - TxLeg: t.repos.TxLeg, - User: t.repos.User, - Asset: t.repos.Asset, - } - - u21Tx := unit21.NewTransaction(u21Repo) - return u21Tx.Evaluate(txModel) -} - func (t transaction) updateTransactionStatus(status string, transactionId string) (err error) { updateDB := &model.TransactionUpdates{Status: &status} err = t.repos.Transaction.Update(transactionId, updateDB) diff --git a/pkg/service/unit21.go b/pkg/service/unit21.go new file mode 100644 index 00000000..01c3d831 --- /dev/null +++ b/pkg/service/unit21.go @@ -0,0 +1,29 @@ +package service + +import ( + "github.com/String-xyz/string-api/pkg/internal/unit21" + "github.com/String-xyz/string-api/pkg/repository" +) + +type Unit21 struct { + Action unit21.Action + Entity unit21.Entity + Instrument unit21.Instrument + Transaction unit21.Transaction +} + +func NewUnit21(repos repository.Repositories) Unit21 { + action := unit21.NewAction() + entityRepos := unit21.EntityRepos{Device: repos.Device, Contact: repos.Contact, UserToPlatform: repos.UserToPlatform} + entity := unit21.NewEntity(entityRepos) + instrumentRepos := unit21.InstrumentRepos{User: repos.User, Device: repos.Device, Location: repos.Location} + instrument := unit21.NewInstrument(instrumentRepos, action) + transactionRepos := unit21.TransactionRepos{User: repos.User, TxLeg: repos.TxLeg, Asset: repos.Asset} + transaction := unit21.NewTransaction(transactionRepos) + return Unit21{ + Action: action, + Entity: entity, + Instrument: instrument, + Transaction: transaction, + } +} diff --git a/pkg/service/user.go b/pkg/service/user.go index 27f81231..94408d0f 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -4,11 +4,9 @@ import ( "os" "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/String-xyz/string-api/pkg/internal/unit21" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/repository" "github.com/pkg/errors" - "github.com/rs/zerolog/log" ) type UserRequest = model.UserRequest @@ -38,10 +36,11 @@ type user struct { auth Auth fingerprint Fingerprint device Device + unit21 Unit21 } -func NewUser(repos repository.Repositories, auth Auth, fprint Fingerprint, device Device) User { - return &user{repos, auth, fprint, device} +func NewUser(repos repository.Repositories, auth Auth, fprint Fingerprint, device Device, unit21 Unit21) User { + return &user{repos, auth, fprint, device, unit21} } func (u user) GetStatus(userID string) (model.UserOnboardingStatus, error) { @@ -110,7 +109,7 @@ func (u user) Create(request model.WalletSignaturePayloadSigned) (UserCreateResp } // deviceService.RegisterNewUserDevice() - go u.createUnit21Entity(user) + go u.unit21.Entity.Create(user) return UserCreateResponse{JWT: jwt, User: user}, nil } @@ -136,11 +135,12 @@ func (u user) createUserData(addr string) (model.User, error) { u.repos.Instrument.Rollback() return user, common.StringError(err) } - if err := u.repos.User.Commit(); err != nil { return user, common.StringError(errors.New("error commiting transaction")) } + go u.unit21.Instrument.Create(instrument) + return user, nil } @@ -151,37 +151,7 @@ func (u user) Update(userID string, request UserUpdates) (model.User, error) { return user, common.StringError(err) } - go u.updateUnit21Entity(user) + go u.unit21.Entity.Update(user) return user, nil } - -func (u user) createUnit21Entity(user model.User) { - // Createing a User Entity in Unit21 - u21Repo := unit21.EntityRepos{ - Device: u.repos.Device, - Contact: u.repos.Contact, - UserToPlatform: u.repos.UserToPlatform, - } - - u21Entity := unit21.NewEntity(u21Repo) // TODO: Make it an injected dependency - _, err := u21Entity.Create(user) - if err != nil { - log.Err(err).Msg("Error creating Entity in Unit21") - } -} - -func (u user) updateUnit21Entity(user model.User) { - // Createing a User Entity in Unit21 - u21Repo := unit21.EntityRepos{ - Device: u.repos.Device, - Contact: u.repos.Contact, - UserToPlatform: u.repos.UserToPlatform, - } - - u21Entity := unit21.NewEntity(u21Repo) - _, err := u21Entity.Update(user) - if err != nil { - log.Err(err).Msg("Error updating Entity in Unit21") - } -}