diff --git a/api/api.go b/api/api.go index 64ad419b..751b2725 100644 --- a/api/api.go +++ b/api/api.go @@ -3,11 +3,14 @@ package api import ( "net/http" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + libmiddleware "github.com/String-xyz/go-lib/middleware" + "github.com/String-xyz/go-lib/validator" "github.com/String-xyz/string-api/api/handler" "github.com/String-xyz/string-api/api/middleware" - "github.com/String-xyz/string-api/api/validator" + "github.com/String-xyz/string-api/pkg/service" - "github.com/String-xyz/string-api/pkg/store" "github.com/jmoiron/sqlx" "github.com/labstack/echo/v4" "github.com/rs/zerolog" @@ -15,7 +18,7 @@ import ( type APIConfig struct { DB *sqlx.DB - Redis store.RedisStore + Redis database.RedisStore Logger *zerolog.Logger Port string } @@ -40,7 +43,7 @@ func Start(config APIConfig) { services := NewServices(config, repos) // initialize routes - A route group only needs access to the services layer. It should'n access the repos layer directly - AuthAPIKey(services, e, handler.IsLocalEnv()) + AuthAPIKey(services, e, libcommon.IsLocalEnv()) transactRoute(services, e) quoteRoute(services, e) userRoute(services, e) @@ -67,12 +70,12 @@ func StartInternal(config APIConfig) { } func baseMiddleware(logger *zerolog.Logger, e *echo.Echo) { - e.Use(middleware.Tracer()) - e.Use(middleware.CORS()) - e.Use(middleware.RequestId()) - e.Use(middleware.Recover()) - e.Use(middleware.Logger(logger)) - e.Use(middleware.LogRequest()) + e.Use(libmiddleware.Tracer()) + e.Use(libmiddleware.CORS()) + e.Use(libmiddleware.RequestId()) + e.Use(libmiddleware.Recover()) + e.Use(libmiddleware.Logger(logger)) + e.Use(libmiddleware.LogRequest()) } func platformRoute(services service.Services, e *echo.Echo) { diff --git a/api/handler/auth_key.go b/api/handler/auth_key.go index eae3d826..5bc265fb 100644 --- a/api/handler/auth_key.go +++ b/api/handler/auth_key.go @@ -3,6 +3,8 @@ package handler import ( "net/http" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" "github.com/rs/zerolog" @@ -28,7 +30,7 @@ func NewAuthAPIKey(service service.APIKeyStrategy, internal bool) AuthAPIKey { func (o authAPIKey) Create(c echo.Context) error { key, err := o.service.Create() if err != nil { - LogStringError(c, err, "authKey approve: create") + libcommon.LogStringError(c, err, "authKey approve: create") return echo.NewHTTPError(http.StatusInternalServerError, "Unable to process request") } return c.JSON(http.StatusOK, key) @@ -36,7 +38,7 @@ func (o authAPIKey) Create(c echo.Context) error { func (o authAPIKey) List(c echo.Context) error { if !o.isInternal { - return NotAllowedError(c) + return httperror.NotAllowedError(c) } body := struct { Status string `query:"status"` @@ -45,12 +47,12 @@ func (o authAPIKey) List(c echo.Context) error { }{} err := c.Bind(&body) if err != nil { - LogStringError(c, err, "authKey list: bind") + libcommon.LogStringError(c, err, "authKey list: bind") return echo.NewHTTPError(http.StatusBadRequest) } list, err := o.service.List(body.Limit, body.Offset, body.Status) if err != nil { - LogStringError(c, err, "authKey list") + libcommon.LogStringError(c, err, "authKey list") return echo.NewHTTPError(http.StatusInternalServerError, "ApiKey Service Failed") } return c.JSON(http.StatusCreated, list) @@ -58,7 +60,7 @@ func (o authAPIKey) List(c echo.Context) error { func (o authAPIKey) Approve(c echo.Context) error { if !o.isInternal { - return NotAllowedError(c) + return httperror.NotAllowedError(c) } params := struct { Id string `param:"id"` @@ -66,12 +68,12 @@ func (o authAPIKey) Approve(c echo.Context) error { err := c.Bind(¶ms) if err != nil { - LogStringError(c, err, "authKey approve: bind") + libcommon.LogStringError(c, err, "authKey approve: bind") return echo.NewHTTPError(http.StatusInternalServerError, "Unable to process request") } err = o.service.Approve(params.Id) if err != nil { - LogStringError(c, err, "authKey approve: approve") + libcommon.LogStringError(c, err, "authKey approve: approve") return echo.NewHTTPError(http.StatusInternalServerError, "Unable to process request") } return c.JSON(http.StatusOK, ResultMessage{Status: "Success"}) diff --git a/api/handler/common.go b/api/handler/common.go index a3a42d84..b4f35fa0 100644 --- a/api/handler/common.go +++ b/api/handler/common.go @@ -1,55 +1,18 @@ package handler import ( - "fmt" "net/http" - "os" "regexp" "strings" "time" + libcommon "github.com/String-xyz/go-lib/common" service "github.com/String-xyz/string-api/pkg/service" "golang.org/x/crypto/sha3" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "github.com/labstack/echo/v4" - "github.com/pkg/errors" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" ) -func LogError(c echo.Context, err error, handlerMsg string) { - lg := c.Get("logger").(*zerolog.Logger) - sp, _ := tracer.SpanFromContext(c.Request().Context()) - lg.Error().Stack().Err(err).Uint64("trace_id", sp.Context().TraceID()). - Uint64("span_id", sp.Context().SpanID()).Msg(handlerMsg) -} - -func LogStringError(c echo.Context, err error, handlerMsg string) { - type stackTracer interface { - StackTrace() errors.StackTrace - } - - tracer, ok := errors.Cause(err).(stackTracer) - if !ok { - log.Warn().Str("error", err.Error()).Msg("error does not implement stack trace") - return - } - - cause := errors.Cause(err) - st := tracer.StackTrace() - - if IsLocalEnv() { - st2 := fmt.Sprintf("\nSTACK TRACE:\n%+v: [%+v ]\n\n", cause.Error(), st[0:5]) - // delete the string_api docker path from the stack trace - st2 = strings.ReplaceAll(st2, "/string_api/", "") - fmt.Print(st2) - return - } - - LogError(c, err, handlerMsg) -} - func SetJWTCookie(c echo.Context, jwt service.JWT) error { cookie := new(http.Cookie) cookie.Name = "StringJWT" @@ -57,8 +20,8 @@ func SetJWTCookie(c echo.Context, jwt service.JWT) error { // cookie.HttpOnly = true // due the short expiration time it is not needed to be http only cookie.Expires = jwt.ExpAt // we want the cookie to expire at the same time as the token cookie.SameSite = getCookieSameSiteMode() - cookie.Path = "/" // Send cookie in every sub path request - cookie.Secure = !IsLocalEnv() // in production allow https only + cookie.Path = "/" // Send cookie in every sub path request + cookie.Secure = !libcommon.IsLocalEnv() // in production allow https only c.SetCookie(cookie) return nil @@ -71,8 +34,8 @@ func SetRefreshTokenCookie(c echo.Context, refresh service.RefreshTokenResponse) cookie.HttpOnly = true cookie.Expires = refresh.ExpAt // we want the cookie to expire at the same time as the token cookie.SameSite = getCookieSameSiteMode() - cookie.Path = "/login/" // Send cookie only in /login path request - cookie.Secure = !IsLocalEnv() // in production allow https only + cookie.Path = "/login/" // Send cookie only in /login path request + cookie.Secure = !libcommon.IsLocalEnv() // in production allow https only c.SetCookie(cookie) return nil @@ -100,7 +63,7 @@ func DeleteAuthCookies(c echo.Context) error { cookie.Expires = time.Now() cookie.SameSite = getCookieSameSiteMode() cookie.Path = "/" // Send cookie in every sub path request - cookie.Secure = !IsLocalEnv() + cookie.Secure = !libcommon.IsLocalEnv() c.SetCookie(cookie) cookie = new(http.Cookie) @@ -109,16 +72,12 @@ func DeleteAuthCookies(c echo.Context) error { cookie.Expires = time.Now() cookie.SameSite = getCookieSameSiteMode() cookie.Path = "/login/" // Send cookie only in refresh path request - cookie.Secure = !IsLocalEnv() + cookie.Secure = !libcommon.IsLocalEnv() c.SetCookie(cookie) return nil } -func IsLocalEnv() bool { - return os.Getenv("ENV") == "local" -} - func validAddress(addr string) bool { re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") return re.MatchString(addr) @@ -126,7 +85,7 @@ func validAddress(addr string) bool { func getCookieSameSiteMode() http.SameSite { sameSiteMode := http.SameSiteNoneMode // allow cors - if IsLocalEnv() { + if libcommon.IsLocalEnv() { sameSiteMode = http.SameSiteLaxMode // because SameSiteNoneMode is not allowed in localhost we use lax mode } return sameSiteMode diff --git a/api/handler/http_error.go b/api/handler/http_error.go deleted file mode 100644 index 044aa47d..00000000 --- a/api/handler/http_error.go +++ /dev/null @@ -1,99 +0,0 @@ -package handler - -import ( - "net/http" - "strings" - - validator "github.com/String-xyz/string-api/api/validator" - "github.com/labstack/echo/v4" -) - -type JSONError struct { - Message string `json:"message"` - Code string `json:"code"` - Details any `json:"details"` -} - -func InvalidPayloadError(c echo.Context, err error) error { - errorParams := validator.ExtractErrorParams(err) - return c.JSON(http.StatusBadRequest, JSONError{Message: "Invalid Payload", Code: "INVALID_PAYLOAD", Details: errorParams}) -} - -func InternalError(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusInternalServerError, JSONError{Message: strings.Join(message, " "), Code: "INTERNAL_SERVER"}) - } - return c.JSON(http.StatusInternalServerError, JSONError{Message: "Something went wrong", Code: "INTERNAL_SERVER"}) -} - -func BadRequestError(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusBadRequest, JSONError{Message: strings.Join(message, " "), Code: "BAD_REQUEST"}) - } - return c.JSON(http.StatusBadRequest, JSONError{Message: "Bad Request", Code: "BAD_REQUEST"}) -} - -func NotFoundError(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusNotFound, JSONError{Message: strings.Join(message, " "), Code: "NOT_FOUND"}) - } - return c.JSON(http.StatusNotFound, JSONError{Message: "Resource Not Found", Code: "NOT_FOUND"}) -} - -func NotAllowedError(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusMethodNotAllowed, JSONError{Message: strings.Join(message, " "), Code: "NOT_ALLOWED"}) - } - return c.JSON(http.StatusMethodNotAllowed, JSONError{Message: "Not Allowed", Code: "NOT_ALLOWED"}) -} - -func Unprocessable(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusUnprocessableEntity, JSONError{Message: strings.Join(message, " "), Code: "UNPROCESSABLE_ENTITY"}) - } - return c.JSON(http.StatusUnprocessableEntity, JSONError{Message: "Unable to process entity", Code: "UNPROCESSABLE_ENTITY"}) -} - -func Unauthorized(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusUnauthorized, JSONError{Message: strings.Join(message, " "), Code: "UNAUTHORIZED"}) - } - return c.JSON(http.StatusUnauthorized, JSONError{Message: "Unauthorized", Code: "UNAUTHORIZED"}) -} - -func TokenExpired(c echo.Context, message ...string) error { - msg := "Token Expired" - if len(message) > 0 { - msg = strings.Join(message, " ") - } - return c.JSON(http.StatusUnauthorized, JSONError{Message: msg, Code: "TOKEN_EXPIRED"}) -} - -func MissingToken(c echo.Context, message ...string) error { - msg := "Missing or malformed token" - if len(message) > 0 { - msg = strings.Join(message, " ") - } - return c.JSON(http.StatusUnauthorized, JSONError{Message: msg, Code: "MISSING_TOKEN"}) -} - -func Conflict(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusConflict, JSONError{Message: strings.Join(message, " "), Code: "CONFLICT"}) - } - return c.JSON(http.StatusConflict, JSONError{Message: "Conflict", Code: "CONFLICT"}) -} - -func LinkExpired(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusForbidden, JSONError{Message: strings.Join(message, " "), Code: "LINK_EXPIRED"}) - } - return c.JSON(http.StatusForbidden, JSONError{Message: "Forbidden", Code: "LINK_EXPIRED"}) -} - -func InvalidEmail(c echo.Context, message ...string) error { - if len(message) > 0 { - return c.JSON(http.StatusUnprocessableEntity, JSONError{Message: strings.Join(message, " "), Code: "INVALID_EMAIL"}) - } - return c.JSON(http.StatusUnprocessableEntity, JSONError{Message: "Invalid email", Code: "INVALID_EMAIL"}) -} diff --git a/api/handler/login.go b/api/handler/login.go index 43b415b3..c4acf4e7 100644 --- a/api/handler/login.go +++ b/api/handler/login.go @@ -6,6 +6,8 @@ import ( "os" "strings" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/service" "github.com/golang-jwt/jwt" @@ -36,13 +38,13 @@ func NewLogin(route *echo.Echo, service service.Auth, device service.Device) Log func (l login) NoncePayload(c echo.Context) error { walletAddress := c.QueryParam("walletAddress") if walletAddress == "" { - return BadRequestError(c, "WalletAddress must be provided") + return httperror.BadRequestError(c, "WalletAddress must be provided") } SanitizeChecksums(&walletAddress) payload, err := l.Service.PayloadToSign(walletAddress) if err != nil { - LogStringError(c, err, "login: request wallet login") - return InternalError(c) + libcommon.LogStringError(c, err, "login: request wallet login") + return httperror.InternalError(c) } encodedNonce := b64.StdEncoding.EncodeToString([]byte(payload.Nonce)) @@ -50,36 +52,37 @@ func (l login) NoncePayload(c echo.Context) error { } func (l login) VerifySignature(c echo.Context) error { + ctx := c.Request().Context() var body model.WalletSignaturePayloadSigned err := c.Bind(&body) if err != nil { - LogStringError(c, err, "login: binding body") - return BadRequestError(c) + libcommon.LogStringError(c, err, "login: binding body") + return httperror.BadRequestError(c) } if err := c.Validate(body); err != nil { - return InvalidPayloadError(c, err) + return httperror.InvalidPayloadError(c, err) } // base64 decode nonce decodedNonce, _ := b64.URLEncoding.DecodeString(body.Nonce) if err != nil { - LogStringError(c, err, "login: verify signature decode nonce") - return BadRequestError(c) + libcommon.LogStringError(c, err, "login: verify signature decode nonce") + return httperror.BadRequestError(c) } body.Nonce = string(decodedNonce) - resp, err := l.Service.VerifySignedPayload(body) + resp, err := l.Service.VerifySignedPayload(ctx, body) if err != nil { if strings.Contains(err.Error(), "unknown device") { - return Unprocessable(c) + return httperror.Unprocessable(c) } if strings.Contains(err.Error(), "invalid email") { - return InvalidEmail(c) + return httperror.BadRequestError(c, "Invalid Email") } - LogStringError(c, err, "login: verify signature") - return BadRequestError(c, "Invalid Payload") + libcommon.LogStringError(c, err, "login: verify signature") + return httperror.BadRequestError(c, "Invalid Payload") } // Upsert IP address in user's device @@ -88,53 +91,54 @@ func (l login) VerifySignature(c echo.Context) error { return []byte(os.Getenv("JWT_SECRET_KEY")), nil }) ip := c.RealIP() - l.Device.UpsertDeviceIP(claims.DeviceId, ip) + l.Device.UpsertDeviceIP(ctx, claims.DeviceId, ip) // set auth cookies err = SetAuthCookies(c, resp.JWT) if err != nil { - LogStringError(c, err, "login: unable to set auth cookies") - return InternalError(c) + libcommon.LogStringError(c, err, "login: unable to set auth cookies") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, resp) } func (l login) RefreshToken(c echo.Context) error { + ctx := c.Request().Context() var body model.RefreshTokenPayload err := c.Bind(&body) if err != nil { - LogStringError(c, err, "login: binding body") - return BadRequestError(c) + libcommon.LogStringError(c, err, "login: binding body") + return httperror.BadRequestError(c) } if err := c.Validate(body); err != nil { - return InvalidPayloadError(c, err) + return httperror.InvalidPayloadError(c, err) } SanitizeChecksums(&body.WalletAddress) cookie, err := c.Cookie("refresh_token") if err != nil { - LogStringError(c, err, "RefreshToken: unable to get refresh_token cookie") - return Unauthorized(c) + libcommon.LogStringError(c, err, "RefreshToken: unable to get refresh_token cookie") + return httperror.Unauthorized(c) } - resp, err := l.Service.RefreshToken(cookie.Value, body.WalletAddress) + resp, err := l.Service.RefreshToken(ctx, cookie.Value, body.WalletAddress) if err != nil { if strings.Contains(err.Error(), "wallet address not associated with this user") { - return BadRequestError(c, "wallet address not associated with this user") + return httperror.BadRequestError(c, "wallet address not associated with this user") } - LogStringError(c, err, "login: refresh token") - return BadRequestError(c, "Invalid or expired token") + libcommon.LogStringError(c, err, "login: refresh token") + return httperror.BadRequestError(c, "Invalid or expired token") } // set auth in cookies err = SetAuthCookies(c, resp.JWT) if err != nil { - LogStringError(c, err, "RefreshToken: unable to set auth cookies") - return InternalError(c) + libcommon.LogStringError(c, err, "RefreshToken: unable to set auth cookies") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, resp) @@ -145,22 +149,22 @@ func (l login) Logout(c echo.Context) error { // get refresh token from cookie cookie, err := c.Cookie("refresh_token") if err != nil { - LogStringError(c, err, "Logout: unable to get refresh_token cookie") - return Unauthorized(c) + libcommon.LogStringError(c, err, "Logout: unable to get refresh_token cookie") + return httperror.Unauthorized(c) } // invalidate refresh token. Returns error if token is not found err = l.Service.InvalidateRefreshToken(cookie.Value) if err != nil { - LogStringError(c, err, "Token not found") + libcommon.LogStringError(c, err, "Token not found") } // There is no need to invalidate the access token since it is a short lived token // delete auth cookies err = DeleteAuthCookies(c) if err != nil { - LogStringError(c, err, "Logout: unable to delete auth cookies") - return InternalError(c) + libcommon.LogStringError(c, err, "Logout: unable to delete auth cookies") + return httperror.InternalError(c) } return c.JSON(http.StatusNoContent, nil) diff --git a/api/handler/login_test.go b/api/handler/login_test.go index 6df70eb7..3d6d4172 100644 --- a/api/handler/login_test.go +++ b/api/handler/login_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/String-xyz/string-api/api/validator" + "github.com/String-xyz/go-lib/validator" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/test/stubs" "github.com/labstack/echo/v4" diff --git a/api/handler/platform.go b/api/handler/platform.go index 80f5ed77..0b02591e 100644 --- a/api/handler/platform.go +++ b/api/handler/platform.go @@ -3,6 +3,7 @@ package handler import ( "net/http" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" ) @@ -24,13 +25,13 @@ func (p platform) Create(c echo.Context) error { body := service.CreatePlatform{} err := c.Bind(&body) if err != nil { - LogStringError(c, err, "platform: create bind") + libcommon.LogStringError(c, err, "platform: create bind") return echo.NewHTTPError(http.StatusBadRequest) } m, err := p.service.Create(body) if err != nil { - LogStringError(c, err, "platform: create") + libcommon.LogStringError(c, err, "platform: create") return echo.NewHTTPError(http.StatusInternalServerError) } return c.JSON(http.StatusCreated, m) diff --git a/api/handler/quotes.go b/api/handler/quotes.go index e736a28b..5f31cde8 100644 --- a/api/handler/quotes.go +++ b/api/handler/quotes.go @@ -3,6 +3,8 @@ package handler import ( "net/http" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" @@ -24,11 +26,12 @@ func NewQuote(route *echo.Echo, service service.Transaction) Quotes { } func (q quote) Quote(c echo.Context) error { + ctx := c.Request().Context() var body model.TransactionRequest err := c.Bind(&body) // 'tag' binding: struct fields are annotated if err != nil { - LogStringError(c, err, "quote: quote bind") - return BadRequestError(c) + libcommon.LogStringError(c, err, "quote: quote bind") + return httperror.BadRequestError(c) } SanitizeChecksums(&body.CxAddr, &body.UserAddress) // Sanitize Checksum for body.CxParams? It might look like this: @@ -37,12 +40,12 @@ func (q quote) Quote(c echo.Context) error { } // userId := c.Get("userId").(string) - res, err := q.Service.Quote(body) // TODO: pass in userId and use it + res, err := q.Service.Quote(ctx, body) // TODO: pass in userId and use it if err != nil && errors.Cause(err).Error() == "w3: response handling failed: execution reverted" { - return c.JSON(http.StatusBadRequest, JSONError{Message: "The requested blockchain operation will revert"}) + return httperror.BadRequestError(c, "The requested blockchain operation will revert") } else if err != nil { - LogStringError(c, err, "quote: quote") - return c.JSON(http.StatusInternalServerError, JSONError{Message: "Quote Service Failed"}) + libcommon.LogStringError(c, err, "quote: quote") + return httperror.InternalError(c, "Quote Service Failed") } return c.JSON(http.StatusOK, res) } diff --git a/api/handler/transact.go b/api/handler/transact.go index 7bd0db19..c0a54645 100644 --- a/api/handler/transact.go +++ b/api/handler/transact.go @@ -4,6 +4,8 @@ import ( "net/http" "strings" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" @@ -24,11 +26,12 @@ func NewTransaction(route *echo.Echo, service service.Transaction) Transaction { } func (t transaction) Transact(c echo.Context) error { + ctx := c.Request().Context() var body model.PrecisionSafeExecutionRequest err := c.Bind(&body) if err != nil { - LogStringError(c, err, "transact: execute bind") - return BadRequestError(c) + libcommon.LogStringError(c, err, "transact: execute bind") + return httperror.BadRequestError(c) } SanitizeChecksums(&body.CxAddr, &body.UserAddress) @@ -40,14 +43,14 @@ func (t transaction) Transact(c echo.Context) error { deviceId := c.Get("deviceId").(string) ip := c.RealIP() - res, err := t.Service.Execute(body, userId, deviceId, ip) + res, err := t.Service.Execute(ctx, body, userId, deviceId, ip) if err != nil && (strings.Contains(err.Error(), "risk:") || strings.Contains(err.Error(), "payment:")) { - LogStringError(c, err, "transact: execute") - return Unprocessable(c) + libcommon.LogStringError(c, err, "transact: execute") + return httperror.Unprocessable(c) } if err != nil { - LogStringError(c, err, "transact: execute") - return InternalError(c) + libcommon.LogStringError(c, err, "transact: execute") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, res) diff --git a/api/handler/user.go b/api/handler/user.go index a9084f71..e3800998 100644 --- a/api/handler/user.go +++ b/api/handler/user.go @@ -5,6 +5,8 @@ import ( "net/http" "strings" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" @@ -33,70 +35,73 @@ func NewUser(route *echo.Echo, userSrv service.User, verificationSrv service.Ver } func (u user) Create(c echo.Context) error { + ctx := c.Request().Context() var body model.WalletSignaturePayloadSigned err := c.Bind(&body) if err != nil { - LogStringError(c, err, "user:create user bind") - return BadRequestError(c) + libcommon.LogStringError(c, err, "user:create user bind") + return httperror.BadRequestError(c) } if err := c.Validate(body); err != nil { - return InvalidPayloadError(c, err) + return httperror.InvalidPayloadError(c, err) } // base64 decode nonce decodedNonce, _ := b64.URLEncoding.DecodeString(body.Nonce) if err != nil { - LogStringError(c, err, "user: create user decode nonce") - return BadRequestError(c) + libcommon.LogStringError(c, err, "user: create user decode nonce") + return httperror.BadRequestError(c) } body.Nonce = string(decodedNonce) - resp, err := u.userService.Create(body) + resp, err := u.userService.Create(ctx, body) if err != nil { if strings.Contains(err.Error(), "wallet already associated with user") { - return Conflict(c) + return httperror.ConflictError(c) } - LogStringError(c, err, "user: creating user") - return InternalError(c) + libcommon.LogStringError(c, err, "user: creating user") + return httperror.InternalError(c) } // set auth cookies err = SetAuthCookies(c, resp.JWT) if err != nil { - LogStringError(c, err, "user: unable to set auth cookies") - return InternalError(c) + libcommon.LogStringError(c, err, "user: unable to set auth cookies") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, resp) } func (u user) Status(c echo.Context) error { + ctx := c.Request().Context() valid, userId := validUserId(IdParam(c), c) if !valid { - return Unauthorized(c) + return httperror.Unauthorized(c) } - status, err := u.userService.GetStatus(userId) + status, err := u.userService.GetStatus(ctx, userId) if err != nil { - LogStringError(c, err, "user: get status") - return InternalError(c) + libcommon.LogStringError(c, err, "user: get status") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, status) } func (u user) Update(c echo.Context) error { + ctx := c.Request().Context() var body model.UpdateUserName err := c.Bind(&body) if err != nil { - LogStringError(c, err, "user: update bind") - return BadRequestError(c) + libcommon.LogStringError(c, err, "user: update bind") + return httperror.BadRequestError(c) } _, userId := validUserId(IdParam(c), c) - user, err := u.userService.Update(userId, body) + user, err := u.userService.Update(ctx, userId, body) if err != nil { - LogStringError(c, err, "user: update") - return InternalError(c) + libcommon.LogStringError(c, err, "user: update") + return httperror.InternalError(c) } return c.JSON(http.StatusOK, user) @@ -105,24 +110,25 @@ func (u user) Update(c echo.Context) error { // VerifyEmail send an email with a link, the user must click on the link for the email to be verified // the link sent is handled by (verification.VerifyEmail) handler func (u user) VerifyEmail(c echo.Context) error { + ctx := c.Request().Context() _, userId := validUserId(IdParam(c), c) email := c.QueryParam("email") if email == "" { - return BadRequestError(c, "Missing or invalid email") + return httperror.BadRequestError(c, "Missing or invalid email") } - err := u.verificationService.SendEmailVerification(userId, email) + err := u.verificationService.SendEmailVerification(ctx, userId, email) if err != nil { if strings.Contains(err.Error(), "email already verified") { - return Conflict(c) + return httperror.ConflictError(c) } if strings.Contains(err.Error(), "link expired") { - return LinkExpired(c, "Link expired, please request a new one") + return httperror.ForbiddenError(c, "Link expired, please request a new one") } - LogStringError(c, err, "user: email verification") - return InternalError(c, "Unable to send email verification") + libcommon.LogStringError(c, err, "user: email verification") + return httperror.InternalError(c, "Unable to send email verification") } return c.JSON(http.StatusOK, ResultMessage{Status: "Email Successfully Verified"}) diff --git a/api/handler/user_test.go b/api/handler/user_test.go index 9d0e593f..59921cbf 100644 --- a/api/handler/user_test.go +++ b/api/handler/user_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/String-xyz/string-api/api/validator" + "github.com/String-xyz/go-lib/validator" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/test/stubs" "github.com/labstack/echo/v4" diff --git a/api/handler/verification.go b/api/handler/verification.go index 02f2df34..a4c4d9d1 100644 --- a/api/handler/verification.go +++ b/api/handler/verification.go @@ -3,6 +3,8 @@ package handler import ( "net/http" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/service" "github.com/labstack/echo/v4" ) @@ -29,21 +31,23 @@ func NewVerification(route *echo.Echo, service service.Verification, deviceServi } func (v verification) VerifyEmail(c echo.Context) error { + ctx := c.Request().Context() token := c.QueryParam("token") - err := v.service.VerifyEmail(token) + err := v.service.VerifyEmail(ctx, token) if err != nil { - LogStringError(c, err, "verification: email verification") - return BadRequestError(c) + libcommon.LogStringError(c, err, "verification: email verification") + return httperror.BadRequestError(c) } return c.JSON(http.StatusOK, ResultMessage{Status: "Email successfully verified"}) } func (v verification) VerifyDevice(c echo.Context) error { + ctx := c.Request().Context() token := c.QueryParam("token") - err := v.deviceService.VerifyDevice(token) + err := v.deviceService.VerifyDevice(ctx, token) if err != nil { - LogStringError(c, err, "verification: device verification") - return BadRequestError(c) + libcommon.LogStringError(c, err, "verification: device verification") + return httperror.BadRequestError(c) } return c.JSON(http.StatusOK, ResultMessage{Status: "Device successfully verified"}) } @@ -51,7 +55,7 @@ func (v verification) VerifyDevice(c echo.Context) error { func (v verification) verify(c echo.Context) error { verificationType := c.QueryParam("type") if verificationType == "" { - return BadRequestError(c) + return httperror.BadRequestError(c) } if verificationType == "email" { return v.VerifyEmail(c) diff --git a/api/middleware/middleware.go b/api/middleware/middleware.go index 6fe1c7e3..3d0ac09b 100644 --- a/api/middleware/middleware.go +++ b/api/middleware/middleware.go @@ -3,72 +3,15 @@ package middleware import ( "net/http" "os" - "strings" - "github.com/String-xyz/string-api/api/handler" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/httperror" "github.com/String-xyz/string-api/pkg/service" "github.com/golang-jwt/jwt" "github.com/labstack/echo/v4" echoMiddleware "github.com/labstack/echo/v4/middleware" - "github.com/pkg/errors" - "github.com/rs/zerolog" - echoDatadog "gopkg.in/DataDog/dd-trace-go.v1/contrib/labstack/echo.v4" ) -func CORS() echo.MiddlewareFunc { - return echoMiddleware.CORSWithConfig(echoMiddleware.CORSConfig{ - AllowOrigins: []string{"*"}, - AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}, - AllowCredentials: true, // allow cookie auth - }) -} - -func Recover() echo.MiddlewareFunc { - return echoMiddleware.Recover() -} - -func Logger(logger *zerolog.Logger) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - c.Set("logger", logger) - return next(c) - } - } -} - -func LogRequest() echo.MiddlewareFunc { - return echoMiddleware.RequestLoggerWithConfig(echoMiddleware.RequestLoggerConfig{ - LogURI: true, - LogStatus: true, - LogRequestID: true, - LogLatency: true, - LogMethod: true, - LogHost: true, - LogError: true, - LogValuesFunc: func(c echo.Context, v echoMiddleware.RequestLoggerValues) error { - env := os.Getenv("ENV") - logger := c.Get("logger").(*zerolog.Logger) - logger.Info(). - Str("path", v.URI). - Str("method", v.Method). - Int("status_code", v.Status). - Str("request_id", v.RequestID). - Str("host", v.Host). - Dur("latency", v.Latency). - Str("env", env). - Err(v.Error). - Msg("request") - - return nil - }, - }) -} - -// RequestID generates a unique request ID -func RequestId() echo.MiddlewareFunc { - return echoMiddleware.RequestID() -} - func BearerAuth() echo.MiddlewareFunc { config := echoMiddleware.JWTConfig{ TokenLookup: "header:Authorization,cookie:StringJWT", @@ -84,15 +27,8 @@ func BearerAuth() echo.MiddlewareFunc { }, SigningKey: []byte(os.Getenv("JWT_SECRET_KEY")), ErrorHandlerWithContext: func(err error, c echo.Context) error { - if strings.Contains(err.Error(), "token is expired") { - return handler.TokenExpired(c) - } - - if strings.Contains(errors.Cause(err).Error(), "missing or malformed jwt") { - return handler.MissingToken(c) - } - return handler.Unauthorized(c) + return httperror.Unauthorized(c) }, } return echoMiddleware.JWTWithConfig(config) @@ -109,10 +45,6 @@ func APIKeyAuth(service service.Auth) echo.MiddlewareFunc { return echoMiddleware.KeyAuthWithConfig(config) } -func Tracer() echo.MiddlewareFunc { - return echoDatadog.Middleware() -} - func Georestrict(service service.Geofencing) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { @@ -125,8 +57,7 @@ func Georestrict(service service.Geofencing) echo.MiddlewareFunc { // For now we are denying if err != nil || !isAllowed { if err != nil { - // TODO: Move the common.go file to the upper level - handler.LogStringError(c, err, "Error in georestrict middleware") + libcommon.LogStringError(c, err, "Error in georestrict middleware") } return c.JSON(http.StatusForbidden, "Error: Geo Location Forbidden") } diff --git a/api/validator/validator.go b/api/validator/validator.go deleted file mode 100644 index b77ad2d8..00000000 --- a/api/validator/validator.go +++ /dev/null @@ -1,82 +0,0 @@ -package validator - -import ( - "fmt" - "reflect" - "strings" - - "github.com/go-playground/validator/v10" -) - -var tagsMessage = map[string]string{ - "required": "is required", - "email": "must be a valid email", - "gte": "must be greater or equal to", - "gt": "must be at least", - "numeric": "must be a valid numeric value", -} - -type InvalidParamError struct { - Param string `json:"param"` - Value any `json:"value"` - ExpectedType string `json:"expectedType"` - Message string `json:"message"` -} - -type InvalidParams []InvalidParamError - -type Validator struct { - validator *validator.Validate -} - -// Validate runs validation on structs as default -func (v *Validator) Validate(i interface{}) error { - return v.validator.Struct(i) -} - -// New Returns an API Validator with the underlying struct validator -func New() *Validator { - v := validator.New() - v.RegisterTagNameFunc(func(fld reflect.StructField) string { - name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] - if name == "-" { - return "" - } - return name - }) - - return &Validator{v} -} - -// ExtractErrorParams loops over the errors returned by a validation -// this is the simplest validation, we at some point will want to extend it -func ExtractErrorParams(err error) InvalidParams { - params := InvalidParams{} - if _, ok := err.(*validator.InvalidValidationError); ok { - return params - } - - for _, err := range err.(validator.ValidationErrors) { - p := InvalidParamError{ - Param: err.Field(), - Value: err.Value(), - ExpectedType: err.Type().String(), - Message: message(err), - } - - params = append(params, p) - } - - return params -} - -func message(f validator.FieldError) string { - message := tagsMessage[f.Tag()] - if strings.HasPrefix(f.Tag(), "g") { - return fmt.Sprintf("%v %s %s", f.Value(), message, f.Param()) - } - if message == "" { - return "Some fields are missing or invalid, please provide all required data" - } - return fmt.Sprintf("%s %s", f.Field(), message) -} diff --git a/api/validator/validator_test.go b/api/validator/validator_test.go deleted file mode 100644 index 2920f762..00000000 --- a/api/validator/validator_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package validator - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/assert" -) - -// tester has all its field as required fields -type tester struct { - Email string `json:"email" validate:"required,email"` // email must be a valid email - Name string `json:"name" validate:"required,gt=2"` // the name field should have at least 2 letters(this can be adjusted) - Age int `json:"age" validate:"required,gte=18,numeric"` // user must be 18yr or older -} - -func TestValid(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "name":"marlon", "age": 18}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.NoError(t, err) -} - -func TestInvalidEmail(t *testing.T) { - v := New() - js := `{"email":"marlon@string", "name":"marlon", "age": 18}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) -} - -func TestInvalidAge(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "name":"marlon", "age": 10}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) - -} - -func TestInvalidName(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "name":"m", "age": 18}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) - -} - -func TestMissingEmail(t *testing.T) { - v := New() - js := `{"name":"marlon", "age": 18}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) -} - -func TestMissingName(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "age": 18}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) -} - -func TestMissingAge(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "name":"m"}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.NoError(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) -} - -func TestInvalidNumeric(t *testing.T) { - v := New() - js := `{"email":"marlon@string.xyz", "name":"marlon", "age":"string"}` - ts := tester{} - err := json.Unmarshal([]byte(js), &ts) - assert.Error(t, err) - err = v.Validate(ts) - assert.Error(t, err) - bt, _ := json.Marshal(ExtractErrorParams(err)) - t.Log(string(bt)) -} diff --git a/cmd/app/main.go b/cmd/app/main.go index f720ffd8..4781ddd1 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -3,12 +3,13 @@ package main import ( "os" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/api" - "github.com/String-xyz/string-api/api/handler" "github.com/String-xyz/string-api/pkg/store" "github.com/joho/godotenv" "github.com/rs/zerolog" "github.com/rs/zerolog/pkgerrors" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) @@ -16,7 +17,7 @@ func main() { // load .env file godotenv.Load(".env") // removed the err since in cloud this wont be loaded lg := zerolog.New(os.Stdout) - if !handler.IsLocalEnv() { + if !libcommon.IsLocalEnv() { tracer.Start() defer tracer.Stop() } @@ -30,10 +31,12 @@ func main() { // zerolog.SetGlobalLevel(zerolog.Disabled) // quiet mode db := store.MustNewPG() + redis := store.NewRedis() + // setup api api.Start(api.APIConfig{ DB: db, - Redis: store.NewRedisStore(), + Redis: redis, Port: port, Logger: &lg, }) diff --git a/cmd/internal/main.go b/cmd/internal/main.go index fe41c72d..59647b80 100644 --- a/cmd/internal/main.go +++ b/cmd/internal/main.go @@ -3,8 +3,8 @@ package main import ( "os" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/api" - "github.com/String-xyz/string-api/api/handler" "github.com/String-xyz/string-api/pkg/store" "github.com/joho/godotenv" "github.com/rs/zerolog" @@ -16,7 +16,7 @@ func main() { // load .env file godotenv.Load(".env") // removed the err since in cloud this wont be loaded - if !handler.IsLocalEnv() { + if !libcommon.IsLocalEnv() { tracer.Start() defer tracer.Stop() } @@ -29,10 +29,13 @@ func main() { zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack db := store.MustNewPG() lg := zerolog.New(os.Stdout) + + redis := store.NewRedis() + // setup api api.StartInternal(api.APIConfig{ DB: db, - Redis: store.NewRedisStore(), + Redis: redis, Port: port, Logger: &lg, }) diff --git a/go.mod b/go.mod index c71f1e3d..ca37936c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/String-xyz/go-lib v1.2.1 github.com/aws/aws-sdk-go v1.44.168 github.com/aws/aws-sdk-go-v2/config v1.18.7 github.com/aws/aws-sdk-go-v2/service/ssm v1.33.4 @@ -16,7 +17,7 @@ require ( github.com/google/uuid v1.3.0 github.com/jmoiron/sqlx v1.3.5 github.com/joho/godotenv v1.4.0 - github.com/labstack/echo/v4 v4.8.0 + github.com/labstack/echo/v4 v4.10.0 github.com/lib/pq v1.10.6 github.com/lmittmann/w3 v0.9.1 github.com/pkg/errors v0.9.1 @@ -24,8 +25,8 @@ require ( github.com/sendgrid/sendgrid-go v3.12.0+incompatible github.com/stretchr/testify v1.8.1 github.com/twilio/twilio-go v1.1.0 - golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be - gopkg.in/DataDog/dd-trace-go.v1 v1.45.1 + golang.org/x/crypto v0.2.0 + gopkg.in/DataDog/dd-trace-go.v1 v1.46.1 ) require ( @@ -70,7 +71,7 @@ require ( github.com/holiman/uint256 v1.2.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/labstack/gommon v0.3.1 // indirect + github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -88,13 +89,13 @@ require ( github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect + golang.org/x/net v0.5.0 // indirect + golang.org/x/sys v0.4.0 // indirect + golang.org/x/text v0.6.0 // indirect + golang.org/x/time v0.2.0 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect google.golang.org/grpc v1.32.0 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index 007b1179..8dcb662f 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,10 @@ github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpz github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/String-xyz/go-lib v1.2.0 h1:bjaWfoOtwbrbZ84XBtPLe4uG7wyXeLDmK+7WHyvANRQ= +github.com/String-xyz/go-lib v1.2.0/go.mod h1:TFAJPYo6YXvk3A1p1WkFuoN5k1wGHbRTxuOg9KLjpUI= +github.com/String-xyz/go-lib v1.2.1 h1:8pWAux7yUkmb99M98XUtcwk/I89XiKhpEm+3LvryrVE= +github.com/String-xyz/go-lib v1.2.1/go.mod h1:TFAJPYo6YXvk3A1p1WkFuoN5k1wGHbRTxuOg9KLjpUI= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -201,10 +205,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.8.0 h1:wdc6yKVaHxkNOEdz4cRZs1pQkwSXPiRjq69yWP4QQS8= -github.com/labstack/echo/v4 v4.8.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= -github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= -github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA= +github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -246,8 +250,8 @@ github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= @@ -313,8 +317,9 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZ github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -330,8 +335,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= -golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE= +golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -355,8 +360,9 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -397,8 +403,9 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -407,10 +414,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE= +golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -449,8 +457,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/DataDog/dd-trace-go.v1 v1.45.1 h1:yx7Hv2It/xxa/ETigd4bSvYMB22PxgGP8Y5N+K+Ibpo= -gopkg.in/DataDog/dd-trace-go.v1 v1.45.1/go.mod h1:kaa8caaECrtY0V/MUtPQAh1lx/euFzPJwrY1taTx3O4= +gopkg.in/DataDog/dd-trace-go.v1 v1.46.1 h1:ovyaxbICb6FJK5VvkFqZyB7PPoUV+kKGXK2JEk+C0mU= +gopkg.in/DataDog/dd-trace-go.v1 v1.46.1/go.mod h1:kaa8caaECrtY0V/MUtPQAh1lx/euFzPJwrY1taTx3O4= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/internal/common/base64.go b/pkg/internal/common/base64.go index 8477b6b7..19304c85 100644 --- a/pkg/internal/common/base64.go +++ b/pkg/internal/common/base64.go @@ -3,12 +3,14 @@ package common import ( "encoding/base64" "encoding/json" + + libcommon "github.com/String-xyz/go-lib/common" ) func EncodeToBase64(object interface{}) (string, error) { buffer, err := json.Marshal(object) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return base64.StdEncoding.EncodeToString(buffer), nil } @@ -17,11 +19,11 @@ func DecodeFromBase64[T any](from string) (T, error) { var result *T = new(T) buffer, err := base64.StdEncoding.DecodeString(from) if err != nil { - return *result, StringError(err) + return *result, libcommon.StringError(err) } err = json.Unmarshal(buffer, &result) if err != nil { - return *result, StringError(err) + return *result, libcommon.StringError(err) } return *result, nil } diff --git a/pkg/internal/common/crypt.go b/pkg/internal/common/crypt.go index 46e49162..0f5a47d0 100644 --- a/pkg/internal/common/crypt.go +++ b/pkg/internal/common/crypt.go @@ -1,82 +1,22 @@ package common import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" "encoding/base64" - "encoding/json" - "io" "os" + libcommon "github.com/String-xyz/go-lib/common" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" ) -func Encrypt(object interface{}, secret string) (string, error) { - buffer, err := json.Marshal(object) - if err != nil { - return "", StringError(err) - } - return EncryptString(string(buffer), secret) -} - -func Decrypt[T any](from string, secret string) (T, error) { - var result T - decrypted, err := DecryptString(from, secret) - if err != nil { - return result, StringError(err) - } - err = json.Unmarshal([]byte(decrypted), &result) - if err != nil { - return result, StringError(err) - } - return result, nil -} - -func EncryptString(data string, secret string) (string, error) { - block, err := aes.NewCipher([]byte(secret)) - if err != nil { - return "", StringError(err) - } - plainText := []byte(data) - cipherText := make([]byte, aes.BlockSize+len(plainText)) - iv := cipherText[:aes.BlockSize] - if _, err := io.ReadFull(rand.Reader, iv); err != nil { - return "", StringError(err) - } - cfb := cipher.NewCFBEncrypter(block, iv) - cfb.XORKeyStream(cipherText[aes.BlockSize:], plainText) - return base64.StdEncoding.EncodeToString(cipherText), nil -} - -func DecryptString(data string, secret string) (string, error) { - block, err := aes.NewCipher([]byte(secret)) - if err != nil { - return "", StringError(err) - } - cipherText, err := base64.StdEncoding.DecodeString(data) - if err != nil { - return "", StringError(err) - } - iv := cipherText[:aes.BlockSize] - - cipherText = cipherText[aes.BlockSize:] - - cfb := cipher.NewCFBDecrypter(block, iv) - plainText := make([]byte, len(cipherText)) - cfb.XORKeyStream(plainText, cipherText) - return string(plainText), nil -} - func EncryptBytesToKMS(data []byte) (string, error) { region := os.Getenv("AWS_REGION") session, err := session.NewSession(&aws.Config{ Region: aws.String(region), }) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } kmsService := kms.New(session) keyId := os.Getenv("AWS_KMS_KEY_ID") @@ -85,7 +25,7 @@ func EncryptBytesToKMS(data []byte) (string, error) { Plaintext: data, }) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return base64.StdEncoding.EncodeToString(result.CiphertextBlob), nil } @@ -93,7 +33,7 @@ func EncryptBytesToKMS(data []byte) (string, error) { func EncryptStringToKMS(data string) (string, error) { res, err := EncryptBytesToKMS([]byte(data)) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return res, nil } @@ -101,18 +41,18 @@ func EncryptStringToKMS(data string) (string, error) { func DecryptBlobFromKMS(blob string) (string, error) { bytes, err := base64.StdEncoding.DecodeString(blob) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } session, err := session.NewSessionWithOptions(session.Options{ SharedConfigState: session.SharedConfigEnable, }) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } kmsService := kms.New(session) result, err := kmsService.Decrypt(&kms.DecryptInput{CiphertextBlob: bytes}) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return string(result.Plaintext), nil } diff --git a/pkg/internal/common/crypt_test.go b/pkg/internal/common/crypt_test.go index 3163e6d1..31fb7be4 100644 --- a/pkg/internal/common/crypt_test.go +++ b/pkg/internal/common/crypt_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + libcommon "github.com/String-xyz/go-lib/common" "github.com/joho/godotenv" "github.com/stretchr/testify/assert" ) @@ -39,10 +40,10 @@ func TestEncodeDecodeObject(t *testing.T) { func TestEncryptDecryptString(t *testing.T) { str := "this is a string" - strEncrypted, err := EncryptString(str, "secret_encryption_key_0123456789") + strEncrypted, err := libcommon.EncryptString(str, "secret_encryption_key_0123456789") assert.NoError(t, err) - strDecrypted, err := DecryptString(strEncrypted, "secret_encryption_key_0123456789") + strDecrypted, err := libcommon.DecryptString(strEncrypted, "secret_encryption_key_0123456789") assert.NoError(t, err) assert.Equal(t, str, strDecrypted) @@ -54,10 +55,10 @@ func TestEncryptDecryptObject(t *testing.T) { objEncoded, err := EncodeToBase64(obj) assert.NoError(t, err) - objEncrypted, err := EncryptString(objEncoded, "secret_encryption_key_0123456789") + objEncrypted, err := libcommon.EncryptString(objEncoded, "secret_encryption_key_0123456789") assert.NoError(t, err) - objDecrypted, err := DecryptString(objEncrypted, "secret_encryption_key_0123456789") + objDecrypted, err := libcommon.DecryptString(objEncrypted, "secret_encryption_key_0123456789") assert.NoError(t, err) objDecoded, err := DecodeFromBase64[randomObject1](objDecrypted) @@ -68,10 +69,10 @@ func TestEncryptDecryptObject(t *testing.T) { func TestEncryptDecryptUnencoded(t *testing.T) { obj := randomObject1{Timestamp: time.Now().Unix(), Email: "test@test.com", Address: "0xdecafbabe"} - objEncrypted, err := Encrypt(obj, "secret_encryption_key_0123456789") + objEncrypted, err := libcommon.Encrypt(obj, "secret_encryption_key_0123456789") assert.NoError(t, err) - objDecrypted, err := Decrypt[randomObject1](objEncrypted, "secret_encryption_key_0123456789") + objDecrypted, err := libcommon.Decrypt[randomObject1](objEncrypted, "secret_encryption_key_0123456789") assert.NoError(t, err) assert.Equal(t, obj, objDecrypted) } diff --git a/pkg/internal/common/error.go b/pkg/internal/common/error.go deleted file mode 100644 index 3e136d0d..00000000 --- a/pkg/internal/common/error.go +++ /dev/null @@ -1,24 +0,0 @@ -package common - -import ( - "github.com/pkg/errors" -) - -func StringError(err error, optionalMsg ...string) error { - if err == nil { - return nil - } - - concat := "" - - for _, msgs := range optionalMsg { - concat += msgs + " " - } - - if errors.Cause(err) == nil || errors.Cause(err) == err { - // fmt.Printf("\nWARNING: Error does not implement StackTracer\n") - return errors.Wrap(errors.New(err.Error()), concat) - } - - return errors.Wrap(err, concat) -} diff --git a/pkg/internal/common/evm.go b/pkg/internal/common/evm.go index c62acc0f..58a05a6c 100644 --- a/pkg/internal/common/evm.go +++ b/pkg/internal/common/evm.go @@ -8,7 +8,8 @@ import ( "strconv" "strings" - "github.com/ethereum/go-ethereum/common" + libcommon "github.com/String-xyz/go-lib/common" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" "github.com/lmittmann/w3" @@ -18,7 +19,7 @@ import ( func ParseEncoding(function *w3.Func, signature string, params []string) ([]byte, error) { signatureArgs := strings.Split(strings.Split(strings.Split(signature, "(")[1], ")")[0], ",") if len(signatureArgs) != len(params) { - return nil, StringError(errors.New("executor parseParams: mismatched arguments")) + return nil, libcommon.StringError(errors.New("executor parseParams: mismatched arguments")) } args := []interface{}{} for i, s := range signatureArgs { @@ -34,13 +35,13 @@ func ParseEncoding(function *w3.Func, signature string, params []string) ([]byte case "uint8": v, err := strconv.ParseUint(params[i], 0, 8) if err != nil { - return nil, StringError(err) + return nil, libcommon.StringError(err) } args = append(args, v) case "uint32": v, err := strconv.ParseUint(params[i], 0, 32) if err != nil { - return nil, StringError(err) + return nil, libcommon.StringError(err) } args = append(args, v) case "uint256": @@ -48,24 +49,24 @@ func ParseEncoding(function *w3.Func, signature string, params []string) ([]byte case "int8": v, err := strconv.ParseInt(params[i], 0, 8) if err != nil { - return nil, StringError(err) + return nil, libcommon.StringError(err) } args = append(args, v) case "int32": v, err := strconv.ParseInt(params[i], 0, 32) if err != nil { - return nil, StringError(err) + return nil, libcommon.StringError(err) } args = append(args, v) case "int256": args = append(args, w3.I(params[i])) default: - return nil, StringError(errors.New("executor: parseParams: unsupported type")) + return nil, libcommon.StringError(errors.New("executor: parseParams: unsupported type")) } } result, err := function.EncodeArgs(args...) if err != nil { - return nil, StringError(err) + return nil, libcommon.StringError(err) } return result, nil } @@ -92,7 +93,7 @@ func IsWallet(addr string) bool { } addr = SanitizeChecksum(addr) // Copy correct checksum, although endpoint handlers are doing this already - address := common.HexToAddress(addr) + address := ethcommon.HexToAddress(addr) bytecode, err := geth.CodeAt(context.Background(), address, nil) if err != nil { return false diff --git a/pkg/internal/common/json.go b/pkg/internal/common/json.go index 8abc28e1..3424ef85 100644 --- a/pkg/internal/common/json.go +++ b/pkg/internal/common/json.go @@ -7,6 +7,7 @@ import ( "reflect" "time" + libcommon "github.com/String-xyz/go-lib/common" "github.com/pkg/errors" ) @@ -15,20 +16,20 @@ func GetJson(url string, target interface{}) error { client := &http.Client{Timeout: 10 * time.Second} response, err := client.Get(url) if err != nil { - return StringError(err) + return libcommon.StringError(err) } defer response.Body.Close() jsonData, err := io.ReadAll(response.Body) if err != nil { - return StringError(err) + return libcommon.StringError(err) } targetType := reflect.TypeOf(target) if len(jsonData) != int(targetType.Size()) { - return StringError(errors.New("Malformed JSON Response")) + return libcommon.StringError(errors.New("Malformed JSON Response")) } err = json.Unmarshal([]byte(jsonData), target) if err != nil { - return StringError(err) + return libcommon.StringError(err) } return nil } @@ -38,16 +39,16 @@ func GetJsonGeneric(url string, target interface{}) error { client := &http.Client{Timeout: 10 * time.Second} response, err := client.Get(url) if err != nil { - return StringError(err) + return libcommon.StringError(err) } defer response.Body.Close() jsonData, err := io.ReadAll(response.Body) if err != nil { - return StringError(err) + return libcommon.StringError(err) } err = json.Unmarshal([]byte(jsonData), target) if err != nil { - return StringError(err) + return libcommon.StringError(err) } return nil } diff --git a/pkg/internal/common/receipt.go b/pkg/internal/common/receipt.go index df923da6..ddf08c64 100644 --- a/pkg/internal/common/receipt.go +++ b/pkg/internal/common/receipt.go @@ -3,6 +3,7 @@ package common import ( "os" + libcommon "github.com/String-xyz/go-lib/common" "github.com/sendgrid/sendgrid-go" "github.com/sendgrid/sendgrid-go/helpers/mail" ) @@ -65,7 +66,7 @@ func EmailReceipt(email string, params ReceiptGenerationParams, body [][2]string client := sendgrid.NewSendClient(os.Getenv("SENDGRID_API_KEY")) _, err := client.Send(message) if err != nil { - return StringError(err) + return libcommon.StringError(err) } return nil } diff --git a/pkg/internal/common/sign.go b/pkg/internal/common/sign.go index 985ce3c4..d23f0e6e 100644 --- a/pkg/internal/common/sign.go +++ b/pkg/internal/common/sign.go @@ -6,7 +6,9 @@ import ( "os" "strconv" - "github.com/ethereum/go-ethereum/common" + libcommon "github.com/String-xyz/go-lib/common" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) @@ -14,15 +16,15 @@ import ( func EVMSign(buffer []byte, eip131 bool) (string, error) { privateKey, err := DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return EVMSignWithPrivateKey(buffer, privateKey, eip131) } func EVMSignWithPrivateKey(buffer []byte, privateKey string, eip131 bool) (string, error) { - sk, err := crypto.ToECDSA(common.FromHex(privateKey)) + sk, err := crypto.ToECDSA(ethcommon.FromHex(privateKey)) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } if eip131 { @@ -33,7 +35,7 @@ func EVMSignWithPrivateKey(buffer []byte, privateKey string, eip131 bool) (strin hash := crypto.Keccak256Hash(buffer) signature, err := crypto.Sign(hash.Bytes(), sk) if err != nil { - return "", StringError(err) + return "", libcommon.StringError(err) } return hexutil.Encode(signature), nil } @@ -42,16 +44,16 @@ func ValidateEVMSignature(signature string, buffer []byte, eip131 bool) (bool, e // Get private key skStr, err := DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { - return false, StringError(err) + return false, libcommon.StringError(err) } - sk, err := crypto.ToECDSA(common.FromHex(skStr)) + sk, err := crypto.ToECDSA(ethcommon.FromHex(skStr)) if err != nil { - return false, StringError(err) + return false, libcommon.StringError(err) } pk := sk.Public() pkECDSA, ok := pk.(*ecdsa.PublicKey) if !ok { - return false, StringError(errors.New("ValidateSignature: Failed to cast pk to ECDSA")) + return false, libcommon.StringError(errors.New("ValidateSignature: Failed to cast pk to ECDSA")) } pkBytes := crypto.FromECDSAPub(pkECDSA) @@ -65,7 +67,7 @@ func ValidateEVMSignature(signature string, buffer []byte, eip131 bool) (bool, e sigBytes, err := hexutil.Decode(signature) if err != nil { - return false, StringError(err) + return false, libcommon.StringError(err) } // Handle cases where EIP-155 is not implemented, as with most wallets @@ -88,7 +90,7 @@ func ValidateExternalEVMSignature(signature string, address string, buffer []byt sigBytes, err := hexutil.Decode(signature) if err != nil { - return false, StringError(err) + return false, libcommon.StringError(err) } // Handle cases where EIP-155 is not implemented, as with most wallets @@ -98,7 +100,7 @@ func ValidateExternalEVMSignature(signature string, address string, buffer []byt sigPKECDSA, err := crypto.SigToPub(hash.Bytes(), sigBytes) if err != nil { - return false, StringError(err) + return false, libcommon.StringError(err) } sigPKBytes := crypto.FromECDSAPub(sigPKECDSA) diff --git a/pkg/internal/common/util.go b/pkg/internal/common/util.go index 08c388a8..34cc061d 100644 --- a/pkg/internal/common/util.go +++ b/pkg/internal/common/util.go @@ -9,11 +9,11 @@ import ( "io" "math" "os" - "reflect" "strconv" + libcommon "github.com/String-xyz/go-lib/common" "github.com/ethereum/go-ethereum/accounts" - ethcomm "github.com/ethereum/go-ethereum/common" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog/log" @@ -24,7 +24,7 @@ func ToSha256(v string) string { return hex.EncodeToString(bs[:]) } -func RecoverAddress(message string, signature string) (ethcomm.Address, error) { +func RecoverAddress(message string, signature string) (ethcommon.Address, error) { sig := hexutil.MustDecode(signature) if sig[crypto.RecoveryIDOffset] == 27 || sig[crypto.RecoveryIDOffset] == 28 { sig[crypto.RecoveryIDOffset] -= 27 @@ -32,7 +32,7 @@ func RecoverAddress(message string, signature string) (ethcomm.Address, error) { msg := accounts.TextHash([]byte(message)) recovered, err := crypto.SigToPub(msg, sig) if err != nil { - return ethcomm.Address{}, StringError(err) + return ethcommon.Address{}, libcommon.StringError(err) } return crypto.PubkeyToAddress(*recovered), nil } @@ -41,51 +41,13 @@ func BigNumberToFloat(bigNumber string, decimals uint64) (floatReturn float64, e floatReturn, err = strconv.ParseFloat(bigNumber, 64) if err != nil { log.Err(err).Msg("Failed to convert bigNumber to float") - err = StringError(err) + err = libcommon.StringError(err) return } floatReturn = floatReturn * math.Pow(10, -float64(decimals)) return } -func isNil(i interface{}) bool { - if i == nil { - return true - } - switch reflect.TypeOf(i).Kind() { - case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: - return reflect.ValueOf(i).IsNil() - } - return false -} - -// keysAndValues is only being used for optional updates -// do not use it for insert or select -func KeysAndValues(item interface{}) ([]string, map[string]interface{}) { - tag := "db" - v := reflect.TypeOf(item) - reflectValue := reflect.ValueOf(item) - reflectValue = reflect.Indirect(reflectValue) - - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - keyNames := make([]string, 0, v.NumField()) - keyValues := make(map[string]interface{}, v.NumField()) - - for i := 0; i < v.NumField(); i++ { - field := reflectValue.Field(i).Interface() - if !isNil(field) { - t := v.Field(i).Tag.Get(tag) + "=:" + v.Field(i).Tag.Get(tag) - keyNames = append(keyNames, t) - keyValues[v.Field(i).Tag.Get(tag)] = field - } - } - - return keyNames, keyValues -} - func GetBaseURL() string { return os.Getenv("BASE_URL") } @@ -94,15 +56,11 @@ func FloatToUSDString(amount float64) string { return fmt.Sprintf("USD $%.2f", math.Round(amount*100)/100) } -func IsLocalEnv() bool { - return os.Getenv("ENV") == "local" -} - func BetterStringify(jsonBody any) (betterString string, err error) { bodyBytes, err := json.Marshal(jsonBody) if err != nil { log.Err(err).Interface("body", jsonBody).Msg("Could not encode to bytes") - return betterString, StringError(err) + return betterString, libcommon.StringError(err) } bodyReader := bytes.NewReader(bodyBytes) @@ -110,7 +68,7 @@ func BetterStringify(jsonBody any) (betterString string, err error) { betterBytes, err := io.ReadAll(bodyReader) betterString = string(betterBytes) if err != nil { - return betterString, StringError(err) + return betterString, libcommon.StringError(err) } return diff --git a/pkg/internal/common/util_test.go b/pkg/internal/common/util_test.go index 76cf9530..2f5071b4 100644 --- a/pkg/internal/common/util_test.go +++ b/pkg/internal/common/util_test.go @@ -3,6 +3,7 @@ package common import ( "testing" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/model" "github.com/stretchr/testify/assert" ) @@ -14,10 +15,11 @@ func TestRecoverSignature(t *testing.T) { assert.Equal(t, "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC", addr.Hex()) } +// TODO: This test should be moved to the go-lib repo func TestKeysAndValues(t *testing.T) { mType := "type" m := model.ContactUpdates{Type: &mType} - names, vals := KeysAndValues(m) + names, vals := libcommon.KeysAndValues(m) assert.Len(t, names, 1) assert.Len(t, vals, 1) } diff --git a/pkg/internal/unit21/action.go b/pkg/internal/unit21/action.go index 2916c201..57e24385 100644 --- a/pkg/internal/unit21/action.go +++ b/pkg/internal/unit21/action.go @@ -4,7 +4,9 @@ import ( "encoding/json" "os" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/internal/common" + "github.com/String-xyz/string-api/pkg/model" "github.com/rs/zerolog/log" ) @@ -41,14 +43,14 @@ func (a action) Create( body, err := u21Post(url, mapToUnit21ActionEvent(instrument, actionData, unit21InstrumentId, eventSubtype)) if err != nil { log.Err(err).Msg("Unit21 Action create failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var u21Response *createEventResponse err = json.Unmarshal(body, &u21Response) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("unit21Id", u21Response.Unit21Id).Msg("Create Action") diff --git a/pkg/internal/unit21/base.go b/pkg/internal/unit21/base.go index 9131b60e..72178d12 100644 --- a/pkg/internal/unit21/base.go +++ b/pkg/internal/unit21/base.go @@ -9,7 +9,7 @@ import ( "os" "time" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/rs/zerolog/log" ) @@ -19,7 +19,7 @@ func u21Put(url string, jsonBody any) (body []byte, err error) { reqBodyBytes, err := json.Marshal(jsonBody) if err != nil { log.Err(err).Msg("Could not encode into bytes") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } log.Info().Str("body", string(reqBodyBytes)).Send() bodyReader := bytes.NewReader(reqBodyBytes) @@ -27,7 +27,7 @@ func u21Put(url string, jsonBody any) (body []byte, err error) { req, err := http.NewRequest(http.MethodPut, url, bodyReader) if err != nil { log.Err(err).Str("url", url).Msg("Could not create request") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } req.Header.Add("accept", "application/json") @@ -39,7 +39,7 @@ func u21Put(url string, jsonBody any) (body []byte, err error) { res, err := client.Do(req) if err != nil { log.Err(err).Str("url", url).Msg("Request failed to update") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } defer res.Body.Close() @@ -47,12 +47,12 @@ func u21Put(url string, jsonBody any) (body []byte, err error) { body, err = io.ReadAll(res.Body) if err != nil { log.Err(err).Str("url", url).Msg("Error extracting body") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } if res.StatusCode != 200 { log.Err(err).Str("url", url).Int("statusCode", res.StatusCode).Msg("Request failed to update") - err = common.StringError(fmt.Errorf("request failed with status code %s and return body: %s", fmt.Sprint(res.StatusCode), string(body))) + err = libcommon.StringError(fmt.Errorf("request failed with status code %s and return body: %s", fmt.Sprint(res.StatusCode), string(body))) return } @@ -65,7 +65,7 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { reqBodyBytes, err := json.Marshal(jsonBody) if err != nil { log.Err(err).Msg("Could not encode into bytes") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } bodyReader := bytes.NewReader(reqBodyBytes) @@ -73,7 +73,7 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { req, err := http.NewRequest(http.MethodPost, url, bodyReader) if err != nil { log.Err(err).Str("url", url).Msg("Could not create request") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } req.Header.Add("accept", "application/json") @@ -85,7 +85,7 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { res, err := client.Do(req) if err != nil { log.Err(err).Str("url", url).Msg("Request failed to update") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } defer res.Body.Close() @@ -93,14 +93,14 @@ func u21Post(url string, jsonBody any) (body []byte, err error) { body, err = io.ReadAll(res.Body) if err != nil { log.Err(err).Str("url", url).Msg("Error extracting body from") - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } 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") - err = common.StringError(fmt.Errorf("request failed with status code %s and return body: %s", fmt.Sprint(res.StatusCode), string(body))) + err = libcommon.StringError(fmt.Errorf("request failed with status code %s and return body: %s", fmt.Sprint(res.StatusCode), string(body))) return } diff --git a/pkg/internal/unit21/entity.go b/pkg/internal/unit21/entity.go index 95f329d3..7a6f9a0e 100644 --- a/pkg/internal/unit21/entity.go +++ b/pkg/internal/unit21/entity.go @@ -1,18 +1,19 @@ package unit21 import ( + "context" "encoding/json" "os" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/repository" "github.com/rs/zerolog/log" ) type Entity interface { - Create(user model.User) (unit21Id string, err error) - Update(user model.User) (unit21Id string, err error) + Create(ctx context.Context, user model.User) (unit21Id string, err error) + Update(ctx context.Context, user model.User) (unit21Id string, err error) AddInstruments(entityId string, instrumentId []string) (err error) } @@ -31,40 +32,40 @@ func NewEntity(r EntityRepos) Entity { } // https://docs.unit21.ai/reference/create_entity -func (e entity) Create(user model.User) (unit21Id string, err error) { +func (e entity) Create(ctx context.Context, user model.User) (unit21Id string, err error) { // ultimately may want a join here. - communications, err := e.getCommunications(user.Id) + communications, err := e.getCommunications(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity communications") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - digitalData, err := e.getEntityDigitalData(user.Id) + digitalData, err := e.getEntityDigitalData(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity digitalData") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - customData, err := e.getCustomData(user.Id) + customData, err := e.getCustomData(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity customData") - return "", common.StringError(err) + return "", libcommon.StringError(err) } url := "https://" + os.Getenv("UNIT21_ENV") + ".unit21.com/v1/entities/create" body, err := u21Post(url, mapUserToEntity(user, communications, digitalData, customData)) if err != nil { log.Err(err).Msg("Unit21 Entity create failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var entity *createEntityResponse err = json.Unmarshal(body, &entity) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("Unit21Id", entity.Unit21Id).Send() @@ -73,28 +74,28 @@ func (e entity) Create(user model.User) (unit21Id string, err error) { } // https://docs.unit21.ai/reference/update_entity -func (e entity) Update(user model.User) (unit21Id string, err error) { +func (e entity) Update(ctx context.Context, user model.User) (unit21Id string, err error) { // ultimately may want a join here. - communications, err := e.getCommunications(user.Id) + communications, err := e.getCommunications(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity communications") - err = common.StringError(err) + err = libcommon.StringError(err) return } - digitalData, err := e.getEntityDigitalData(user.Id) + digitalData, err := e.getEntityDigitalData(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity digitalData") - err = common.StringError(err) + err = libcommon.StringError(err) return } - customData, err := e.getCustomData(user.Id) + customData, err := e.getCustomData(ctx, user.Id) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity customData") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -104,7 +105,7 @@ func (e entity) Update(user model.User) (unit21Id string, err error) { if err != nil { log.Err(err).Msg("Unit21 Entity create failed") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -112,7 +113,7 @@ func (e entity) Update(user model.User) (unit21Id string, err error) { err = json.Unmarshal(body, &entity) if err != nil { log.Err(err).Msg("Reading body failed") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -131,19 +132,19 @@ func (e entity) AddInstruments(entityId string, instrumentIds []string) (err err _, err = u21Put(url, instruments) if err != nil { log.Err(err).Msg("Unit21 Entity Add Instruments failed") - err = common.StringError(err) + err = libcommon.StringError(err) return } return } -func (e entity) getCommunications(userId string) (communications entityCommunication, err error) { +func (e entity) getCommunications(ctx context.Context, userId string) (communications entityCommunication, err error) { // Get user contacts - contacts, err := e.repo.Contact.ListByUserId(userId, 100, 0) + contacts, err := e.repo.Contact.ListByUserId(ctx, userId, 100, 0) if err != nil { log.Err(err).Msg("Failed to get user contacts") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -158,11 +159,11 @@ func (e entity) getCommunications(userId string) (communications entityCommunica return } -func (e entity) getEntityDigitalData(userId string) (deviceData entityDigitalData, err error) { - devices, err := e.repo.Device.ListByUserId(userId, 100, 0) +func (e entity) getEntityDigitalData(ctx context.Context, userId string) (deviceData entityDigitalData, err error) { + devices, err := e.repo.Device.ListByUserId(ctx, userId, 100, 0) if err != nil { log.Err(err).Msg("Failed to get user devices") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -173,11 +174,11 @@ func (e entity) getEntityDigitalData(userId string) (deviceData entityDigitalDat return } -func (e entity) getCustomData(userId string) (customData entityCustomData, err error) { - devices, err := e.repo.UserToPlatform.ListByUserId(userId, 100, 0) +func (e entity) getCustomData(ctx context.Context, userId string) (customData entityCustomData, err error) { + devices, err := e.repo.UserToPlatform.ListByUserId(ctx, userId, 100, 0) if err != nil { log.Err(err).Msg("Failed to get user platforms") - err = common.StringError(err) + err = libcommon.StringError(err) return } diff --git a/pkg/internal/unit21/entity_test.go b/pkg/internal/unit21/entity_test.go index e30fd38e..cfb8cd7b 100644 --- a/pkg/internal/unit21/entity_test.go +++ b/pkg/internal/unit21/entity_test.go @@ -1,6 +1,7 @@ package unit21 import ( + "context" "database/sql" "testing" "time" @@ -31,6 +32,7 @@ func TestCreateEntity(t *testing.T) { } func TestUpdateEntity(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -74,7 +76,7 @@ func TestUpdateEntity(t *testing.T) { u21Entity := NewEntity(repos) // update in u21 - u21EntityId, err = u21Entity.Update(user) + u21EntityId, err = u21Entity.Update(ctx, user) assert.NoError(t, err) assert.Greater(t, len([]rune(u21EntityId)), 0) @@ -117,6 +119,7 @@ func TestAddInstruments(t *testing.T) { } func createMockUser(mock sqlmock.Sqlmock, sqlxDB *sqlx.DB) (entityId string, unit21Id string, err error) { + ctx := context.Background() entityId = uuid.NewString() user := model.User{ Id: entityId, @@ -151,12 +154,13 @@ func createMockUser(mock sqlmock.Sqlmock, sqlxDB *sqlx.DB) (entityId string, uni u21Entity := NewEntity(repos) - u21EntityId, err := u21Entity.Create(user) + u21EntityId, err := u21Entity.Create(ctx, user) return entityId, u21EntityId, err } func createMockInstrumentForUser(userId string, mock sqlmock.Sqlmock, sqlxDB *sqlx.DB) (instrument model.Instrument, unit21Id string, err error) { + ctx := context.Background() instrumentId := uuid.NewString() locationId := uuid.NewString() @@ -201,7 +205,7 @@ func createMockInstrumentForUser(userId string, mock sqlmock.Sqlmock, sqlxDB *sq u21Instrument := NewInstrument(repos, action) - u21InstrumentId, err := u21Instrument.Create(instrument) + u21InstrumentId, err := u21Instrument.Create(ctx, instrument) return instrument, u21InstrumentId, err } diff --git a/pkg/internal/unit21/evaluate_test.go b/pkg/internal/unit21/evaluate_test.go index 565cf61a..2a042aac 100644 --- a/pkg/internal/unit21/evaluate_test.go +++ b/pkg/internal/unit21/evaluate_test.go @@ -1,6 +1,7 @@ package unit21 import ( + "context" "testing" "time" @@ -13,6 +14,7 @@ import ( // This transaction should pass func TestEvaluateTransactionPass(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -24,13 +26,14 @@ func TestEvaluateTransactionPass(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.True(t, pass) } // Entity makes a credit card purchase over $1,500 func TestEvaluateTransactionAbnormalAmounts(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -42,7 +45,7 @@ func TestEvaluateTransactionAbnormalAmounts(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.False(t, pass) } @@ -50,6 +53,7 @@ func TestEvaluateTransactionAbnormalAmounts(t *testing.T) { // User links more than 5 cards to their account in a 1 hour span // Not currently functioning due to lag in Unit21 data ingestion func TestEvaluateTransactionManyLinkedCards(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -80,13 +84,14 @@ func TestEvaluateTransactionManyLinkedCards(t *testing.T) { instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) time.Sleep(10 * time.Second) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.False(t, pass) } // 10 or more FAILED transactions in a 1 hour span func TestEvaluateTransactionHighFailedTransactionAmount(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -102,12 +107,12 @@ func TestEvaluateTransactionHighFailedTransactionAmount(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.False(t, pass) transaction.Status = "Failed" mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - u21TransactionId, err := executeMockTransactionForUser(transaction, sqlxDB) + u21TransactionId, err := executeMockTransactionForUser(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.Greater(t, len([]rune(u21TransactionId)), 0) } @@ -120,7 +125,7 @@ func TestEvaluateTransactionHighFailedTransactionAmount(t *testing.T) { instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) time.Sleep(10 * time.Second) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.False(t, pass) } @@ -128,6 +133,7 @@ func TestEvaluateTransactionHighFailedTransactionAmount(t *testing.T) { // User onboarded in the last 48 hours and has // transacted more than 7.5K in the last 90 minutes func TestEvaluateTransactionNewUserHighSpend(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -143,11 +149,11 @@ func TestEvaluateTransactionNewUserHighSpend(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.True(t, pass) mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - u21TransactionId, err := executeMockTransactionForUser(transaction, sqlxDB) + u21TransactionId, err := executeMockTransactionForUser(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.Greater(t, len([]rune(u21TransactionId)), 0) } @@ -160,12 +166,12 @@ func TestEvaluateTransactionNewUserHighSpend(t *testing.T) { instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) time.Sleep(10 * time.Second) - pass, err := evaluateMockTransaction(transaction, sqlxDB) + pass, err := evaluateMockTransaction(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.False(t, pass) } -func evaluateMockTransaction(transaction model.Transaction, sqlxDB *sqlx.DB) (pass bool, err error) { +func evaluateMockTransaction(ctx context.Context, transaction model.Transaction, sqlxDB *sqlx.DB) (pass bool, err error) { repos := TransactionRepos{ TxLeg: repository.NewTxLeg((sqlxDB)), User: repository.NewUser(sqlxDB), @@ -174,7 +180,7 @@ func evaluateMockTransaction(transaction model.Transaction, sqlxDB *sqlx.DB) (pa u21Transaction := NewTransaction(repos) - pass, err = u21Transaction.Evaluate(transaction) + pass, err = u21Transaction.Evaluate(ctx, transaction) return } diff --git a/pkg/internal/unit21/instrument.go b/pkg/internal/unit21/instrument.go index 17810cac..5ba4c554 100644 --- a/pkg/internal/unit21/instrument.go +++ b/pkg/internal/unit21/instrument.go @@ -1,18 +1,19 @@ package unit21 import ( + "context" "encoding/json" "os" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/repository" "github.com/rs/zerolog/log" ) type Instrument interface { - Create(instrument model.Instrument) (unit21Id string, err error) - Update(instrument model.Instrument) (unit21Id string, err error) + Create(ctx context.Context, instrument model.Instrument) (unit21Id string, err error) + Update(ctx context.Context, instrument model.Instrument) (unit21Id string, err error) } type InstrumentRepos struct { @@ -30,44 +31,44 @@ func NewInstrument(r InstrumentRepos, a Action) Instrument { return &instrument{repos: r, action: a} } -func (i instrument) Create(instrument model.Instrument) (unit21Id string, err error) { +func (i instrument) Create(ctx context.Context, instrument model.Instrument) (unit21Id string, err error) { - source, err := i.getSource(instrument.UserId) + source, err := i.getSource(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument source") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - entities, err := i.getEntities(instrument.UserId) + entities, err := i.getEntities(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument entity") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - digitalData, err := i.getInstrumentDigitalData(instrument.UserId) + digitalData, err := i.getInstrumentDigitalData(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity digitalData") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - locationData, err := i.getLocationData(instrument.LocationId.String) + locationData, err := i.getLocationData(ctx, instrument.LocationId.String) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument location") - return "", common.StringError(err) + return "", libcommon.StringError(err) } url := "https://" + os.Getenv("UNIT21_ENV") + ".unit21.com/v1/instruments/create" body, err := u21Post(url, mapToUnit21Instrument(instrument, source, entities, digitalData, locationData)) if err != nil { log.Err(err).Msg("Unit21 Instrument create failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var u21Response *createInstrumentResponse err = json.Unmarshal(body, &u21Response) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("Unit21Id", u21Response.Unit21Id).Send() @@ -76,36 +77,36 @@ func (i instrument) Create(instrument model.Instrument) (unit21Id string, err er _, 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, libcommon.StringError(err) } return u21Response.Unit21Id, nil } -func (i instrument) Update(instrument model.Instrument) (unit21Id string, err error) { +func (i instrument) Update(ctx context.Context, instrument model.Instrument) (unit21Id string, err error) { - source, err := i.getSource(instrument.UserId) + source, err := i.getSource(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument source") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - entities, err := i.getEntities(instrument.UserId) + entities, err := i.getEntities(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument entity") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - digitalData, err := i.getInstrumentDigitalData(instrument.UserId) + digitalData, err := i.getInstrumentDigitalData(ctx, instrument.UserId) if err != nil { log.Err(err).Msg("Failed to gather Unit21 entity digitalData") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - locationData, err := i.getLocationData(instrument.LocationId.String) + locationData, err := i.getLocationData(ctx, instrument.LocationId.String) if err != nil { log.Err(err).Msg("Failed to gather Unit21 instrument location") - return "", common.StringError(err) + return "", libcommon.StringError(err) } orgName := os.Getenv("UNIT21_ORG_NAME") @@ -114,14 +115,14 @@ func (i instrument) Update(instrument model.Instrument) (unit21Id string, err er if err != nil { log.Err(err).Msg("Unit21 Instrument create failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var u21Response *updateInstrumentResponse err = json.Unmarshal(body, &u21Response) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("Unit21Id", u21Response.Unit21Id).Send() @@ -129,15 +130,15 @@ func (i instrument) Update(instrument model.Instrument) (unit21Id string, err er return u21Response.Unit21Id, nil } -func (i instrument) getSource(userId string) (source string, err error) { +func (i instrument) getSource(ctx context.Context, userId string) (source string, err error) { if userId == "" { log.Warn().Msg("No userId defined") return } - user, err := i.repos.User.GetById(userId) + user, err := i.repos.User.GetById(ctx, userId) if err != nil { log.Err(err).Msg("Failed go get user contacts") - return "", common.StringError(err) + return "", libcommon.StringError(err) } if user.Tags["internal"] == "true" { @@ -146,16 +147,16 @@ func (i instrument) getSource(userId string) (source string, err error) { return "external", nil } -func (i instrument) getEntities(userId string) (entity instrumentEntity, err error) { +func (i instrument) getEntities(ctx context.Context, userId string) (entity instrumentEntity, err error) { if userId == "" { log.Warn().Msg("No userId defined") return } - user, err := i.repos.User.GetById(userId) + user, err := i.repos.User.GetById(ctx, userId) if err != nil { log.Err(err).Msg("Failed go get user contacts") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -167,16 +168,16 @@ func (i instrument) getEntities(userId string) (entity instrumentEntity, err err return entity, nil } -func (i instrument) getInstrumentDigitalData(userId string) (digitalData instrumentDigitalData, err error) { +func (i instrument) getInstrumentDigitalData(ctx context.Context, userId string) (digitalData instrumentDigitalData, err error) { if userId == "" { log.Warn().Msg("No userId defined") return } - devices, err := i.repos.Device.ListByUserId(userId, 100, 0) + devices, err := i.repos.Device.ListByUserId(ctx, userId, 100, 0) if err != nil { log.Err(err).Msg("Failed to get user devices") - err = common.StringError(err) + err = libcommon.StringError(err) return } @@ -186,16 +187,16 @@ func (i instrument) getInstrumentDigitalData(userId string) (digitalData instrum return } -func (i instrument) getLocationData(locationId string) (locationData *instrumentLocationData, err error) { +func (i instrument) getLocationData(ctx context.Context, locationId string) (locationData *instrumentLocationData, err error) { if locationId == "" { log.Warn().Msg("No locationId defined") return } - location, err := i.repos.Location.GetById(locationId) + location, err := i.repos.Location.GetById(ctx, locationId) if err != nil { log.Err(err).Msg("Failed go get instrument location") - err = common.StringError(err) + err = libcommon.StringError(err) return } if location.CreatedAt.Unix() != 0 { diff --git a/pkg/internal/unit21/instrument_test.go b/pkg/internal/unit21/instrument_test.go index 3afbec8d..aafedeaf 100644 --- a/pkg/internal/unit21/instrument_test.go +++ b/pkg/internal/unit21/instrument_test.go @@ -1,6 +1,7 @@ package unit21 import ( + "context" "database/sql" "testing" "time" @@ -27,6 +28,7 @@ func TestCreateInstrument(t *testing.T) { } func TestUpdateInstrument(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -80,7 +82,7 @@ func TestUpdateInstrument(t *testing.T) { u21Instrument := NewInstrument(repos, action) - u21InstrumentId, err = u21Instrument.Update(instrument) + u21InstrumentId, err = u21Instrument.Update(ctx, instrument) assert.NoError(t, err) assert.Greater(t, len([]rune(u21InstrumentId)), 0) diff --git a/pkg/internal/unit21/transaction.go b/pkg/internal/unit21/transaction.go index 3b73a2a3..68892e20 100644 --- a/pkg/internal/unit21/transaction.go +++ b/pkg/internal/unit21/transaction.go @@ -1,19 +1,22 @@ package unit21 import ( + "context" "encoding/json" "os" + libcommon "github.com/String-xyz/go-lib/common" "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" ) type Transaction interface { - Evaluate(transaction model.Transaction) (pass bool, err error) - Create(transaction model.Transaction) (unit21Id string, err error) - Update(transaction model.Transaction) (unit21Id string, err error) + Evaluate(ctx context.Context, transaction model.Transaction) (pass bool, err error) + Create(ctx context.Context, transaction model.Transaction) (unit21Id string, err error) + Update(ctx context.Context, transaction model.Transaction) (unit21Id string, err error) } type TransactionRepos struct { @@ -31,17 +34,17 @@ func NewTransaction(r TransactionRepos) Transaction { return &transaction{repos: r} } -func (t transaction) Evaluate(transaction model.Transaction) (pass bool, err error) { - transactionData, err := t.getTransactionData(transaction) +func (t transaction) Evaluate(ctx context.Context, transaction model.Transaction) (pass bool, err error) { + transactionData, err := t.getTransactionData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 transaction source") - return false, common.StringError(err) + return false, libcommon.StringError(err) } - digitalData, err := t.getEventDigitalData(transaction) + digitalData, err := t.getEventDigitalData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 digital data") - return false, common.StringError(err) + return false, libcommon.StringError(err) } url := os.Getenv("UNIT21_RTR_URL") @@ -52,7 +55,7 @@ func (t transaction) Evaluate(transaction model.Transaction) (pass bool, err err body, err := u21Post(url, mapToUnit21TransactionEvent(transaction, transactionData, digitalData)) if err != nil { log.Err(err).Msg("Unit21 Transaction evaluate failed") - return false, common.StringError(err) + return false, libcommon.StringError(err) } // var u21Response *createEventResponse @@ -60,7 +63,7 @@ func (t transaction) Evaluate(transaction model.Transaction) (pass bool, err err err = json.Unmarshal(body, &response) if err != nil { log.Err(err).Msg("Reading body failed") - return false, common.StringError(err) + return false, libcommon.StringError(err) } for _, rule := range *response.RuleExecutions { @@ -72,48 +75,48 @@ func (t transaction) Evaluate(transaction model.Transaction) (pass bool, err err return true, nil } -func (t transaction) Create(transaction model.Transaction) (unit21Id string, err error) { - transactionData, err := t.getTransactionData(transaction) +func (t transaction) Create(ctx context.Context, transaction model.Transaction) (unit21Id string, err error) { + transactionData, err := t.getTransactionData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 transaction source") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - digitalData, err := t.getEventDigitalData(transaction) + digitalData, err := t.getEventDigitalData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 digital data") - return "", common.StringError(err) + return "", libcommon.StringError(err) } url := "https://" + os.Getenv("UNIT21_ENV") + ".unit21.com/v1/events/create" body, err := u21Post(url, mapToUnit21TransactionEvent(transaction, transactionData, digitalData)) if err != nil { log.Err(err).Msg("Unit21 Transaction create failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var u21Response *createEventResponse err = json.Unmarshal(body, &u21Response) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("unit21Id", u21Response.Unit21Id).Send() return u21Response.Unit21Id, nil } -func (t transaction) Update(transaction model.Transaction) (unit21Id string, err error) { - transactionData, err := t.getTransactionData(transaction) +func (t transaction) Update(ctx context.Context, transaction model.Transaction) (unit21Id string, err error) { + transactionData, err := t.getTransactionData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 transaction source") - return "", common.StringError(err) + return "", libcommon.StringError(err) } - digitalData, err := t.getEventDigitalData(transaction) + digitalData, err := t.getEventDigitalData(ctx, transaction) if err != nil { log.Err(err).Msg("Failed to gather Unit21 digital data") - return "", common.StringError(err) + return "", libcommon.StringError(err) } orgName := os.Getenv("UNIT21_ORG_NAME") @@ -122,66 +125,66 @@ func (t transaction) Update(transaction model.Transaction) (unit21Id string, err if err != nil { log.Err(err).Msg("Unit21 Transaction create failed:") - return "", common.StringError(err) + return "", libcommon.StringError(err) } var u21Response *updateEventResponse err = json.Unmarshal(body, &u21Response) if err != nil { log.Err(err).Msg("Reading body failed") - return "", common.StringError(err) + return "", libcommon.StringError(err) } log.Info().Str("unit21Id", u21Response.Unit21Id).Send() return u21Response.Unit21Id, nil } -func (t transaction) getTransactionData(transaction model.Transaction) (txData transactionData, err error) { - senderData, err := t.repos.TxLeg.GetById(transaction.OriginTxLegId) +func (t transaction) getTransactionData(ctx context.Context, transaction model.Transaction) (txData transactionData, err error) { + senderData, err := t.repos.TxLeg.GetById(ctx, transaction.OriginTxLegId) if err != nil { log.Err(err).Msg("Failed go get origin transaction leg") - err = common.StringError(err) + err = libcommon.StringError(err) return } - receiverData, err := t.repos.TxLeg.GetById(transaction.DestinationTxLegId) + receiverData, err := t.repos.TxLeg.GetById(ctx, transaction.DestinationTxLegId) if err != nil { log.Err(err).Msg("Failed go get origin transaction leg") - err = common.StringError(err) + err = libcommon.StringError(err) return } - senderAsset, err := t.repos.Asset.GetById(senderData.AssetId) + senderAsset, err := t.repos.Asset.GetById(ctx, senderData.AssetId) if err != nil { log.Err(err).Msg("Failed go get transaction sender asset") - err = common.StringError(err) + err = libcommon.StringError(err) return } - receiverAsset, err := t.repos.Asset.GetById(receiverData.AssetId) + receiverAsset, err := t.repos.Asset.GetById(ctx, receiverData.AssetId) if err != nil { log.Err(err).Msg("Failed go get transaction receiver asset") - err = common.StringError(err) + err = libcommon.StringError(err) return } amount, err := common.BigNumberToFloat(senderData.Value, 6) if err != nil { log.Err(err).Msg("Failed to convert amount") - err = common.StringError(err) + err = libcommon.StringError(err) return } senderAmount, err := common.BigNumberToFloat(senderData.Amount, senderAsset.Decimals) if err != nil { log.Err(err).Msg("Failed to convert senderAmount") - err = common.StringError(err) + err = libcommon.StringError(err) return } receiverAmount, err := common.BigNumberToFloat(receiverData.Amount, receiverAsset.Decimals) if err != nil { log.Err(err).Msg("Failed to convert receiverAmount") - err = common.StringError(err) + err = libcommon.StringError(err) return } var stringFee float64 @@ -189,7 +192,7 @@ func (t transaction) getTransactionData(transaction model.Transaction) (txData t stringFee, err = common.BigNumberToFloat(transaction.StringFee, 6) if err != nil { log.Err(err).Msg("Failed to convert stringFee") - err = common.StringError(err) + err = libcommon.StringError(err) return } } @@ -199,7 +202,7 @@ func (t transaction) getTransactionData(transaction model.Transaction) (txData t processingFee, err = common.BigNumberToFloat(transaction.ProcessingFee, 6) if err != nil { log.Err(err).Msg("Failed to convert processingFee") - err = common.StringError(err) + err = libcommon.StringError(err) return } } @@ -231,15 +234,15 @@ func (t transaction) getTransactionData(transaction model.Transaction) (txData t return } -func (t transaction) getEventDigitalData(transaction model.Transaction) (digitalData eventDigitalData, err error) { +func (t transaction) getEventDigitalData(ctx context.Context, transaction model.Transaction) (digitalData eventDigitalData, err error) { if transaction.DeviceId == "" { return } - device, err := t.repos.Device.GetById(transaction.DeviceId) + device, err := t.repos.Device.GetById(ctx, transaction.DeviceId) if err != nil { log.Err(err).Msg("Failed to get transaction device") - err = common.StringError(err) + err = libcommon.StringError(err) return } diff --git a/pkg/internal/unit21/transaction_test.go b/pkg/internal/unit21/transaction_test.go index fe90507b..45f0b487 100644 --- a/pkg/internal/unit21/transaction_test.go +++ b/pkg/internal/unit21/transaction_test.go @@ -1,6 +1,7 @@ package unit21 import ( + "context" "database/sql" "testing" "time" @@ -15,6 +16,7 @@ import ( ) func TestCreateTransaction(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -26,7 +28,7 @@ func TestCreateTransaction(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - u21TransactionId, err := executeMockTransactionForUser(transaction, sqlxDB) + u21TransactionId, err := executeMockTransactionForUser(ctx, transaction, sqlxDB) assert.NoError(t, err) assert.Greater(t, len([]rune(u21TransactionId)), 0) @@ -36,6 +38,7 @@ func TestCreateTransaction(t *testing.T) { } func TestUpdateTransaction(t *testing.T) { + ctx := context.Background() db, mock, sqlxDB, err := initializeTest(t) assert.NoError(t, err) defer db.Close() @@ -47,7 +50,7 @@ func TestUpdateTransaction(t *testing.T) { instrumentId1 := uuid.NewString() instrumentId2 := uuid.NewString() mockTransactionRows(mock, transaction, userId, assetId1, assetId2, instrumentId1, instrumentId2) - u21TransactionId, err := executeMockTransactionForUser(transaction, sqlxDB) + u21TransactionId, err := executeMockTransactionForUser(ctx, transaction, sqlxDB) assert.NoError(t, err) OriginTxLegId := uuid.NewString() @@ -92,7 +95,7 @@ func TestUpdateTransaction(t *testing.T) { u21Transaction := NewTransaction(repos) - u21TransactionId, err = u21Transaction.Update(transaction) + u21TransactionId, err = u21Transaction.Update(ctx, transaction) assert.NoError(t, err) assert.Greater(t, len([]rune(u21TransactionId)), 0) @@ -101,7 +104,7 @@ func TestUpdateTransaction(t *testing.T) { // TODO: mock call to client once it's manually tested } -func executeMockTransactionForUser(transaction model.Transaction, sqlxDB *sqlx.DB) (unit21Id string, err error) { +func executeMockTransactionForUser(ctx context.Context, transaction model.Transaction, sqlxDB *sqlx.DB) (unit21Id string, err error) { repos := TransactionRepos{ TxLeg: repository.NewTxLeg(sqlxDB), User: repository.NewUser(sqlxDB), @@ -110,7 +113,7 @@ func executeMockTransactionForUser(transaction model.Transaction, sqlxDB *sqlx.D u21Transaction := NewTransaction(repos) - unit21Id, err = u21Transaction.Create(transaction) + unit21Id, err = u21Transaction.Create(ctx, transaction) return } diff --git a/pkg/repository/asset.go b/pkg/repository/asset.go index ff80e38c..5f2c8f5e 100644 --- a/pkg/repository/asset.go +++ b/pkg/repository/asset.go @@ -1,37 +1,40 @@ package repository import ( + "context" "database/sql" "fmt" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type Asset interface { - Transactable + database.Transactable Create(model.Asset) (model.Asset, error) - GetById(id string) (model.Asset, error) + GetById(ctx context.Context, id string) (model.Asset, error) GetByName(name string) (model.Asset, error) - Update(Id string, updates any) error + Update(ctx context.Context, Id string, updates any) error } type asset[T any] struct { - base[T] + baserepo.Base[T] } -func NewAsset(db *sqlx.DB) Asset { - return &asset[model.Asset]{base[model.Asset]{store: db, table: "asset"}} +func NewAsset(db database.Queryable) Asset { + return &asset[model.Asset]{baserepo.Base[model.Asset]{Store: db, Table: "asset"}} } func (a asset[T]) Create(insert model.Asset) (model.Asset, error) { m := model.Asset{} - rows, err := a.store.NamedQuery(` + rows, err := a.Store.NamedQuery(` INSERT INTO asset (name, description, decimals, is_crypto, network_id, value_oracle) VALUES(:name, :description, :decimals, :is_crypto, :network_id, :value_oracle) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) @@ -43,9 +46,9 @@ func (a asset[T]) Create(insert model.Asset) (model.Asset, error) { func (a asset[T]) GetByName(name string) (model.Asset, error) { m := model.Asset{} - err := a.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE name = $1", a.table), name) + err := a.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE name = $1", a.Table), name) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } return m, nil } diff --git a/pkg/repository/auth.go b/pkg/repository/auth.go index 9acd3ed9..f6f1cdb8 100644 --- a/pkg/repository/auth.go +++ b/pkg/repository/auth.go @@ -6,10 +6,10 @@ import ( "fmt" "time" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/String-xyz/string-api/pkg/store" - "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" ) @@ -40,36 +40,36 @@ type AuthStrategy interface { Delete(key string) error } -type auth struct { - store *sqlx.DB - redis store.RedisStore +type auth[T any] struct { + baserepo.Base[T] + redis database.RedisStore } -func NewAuth(redis store.RedisStore, store *sqlx.DB) AuthStrategy { - return &auth{redis: redis, store: store} +func NewAuth(redis database.RedisStore, db database.Queryable) AuthStrategy { + return &auth[model.AuthStrategy]{baserepo.Base[model.AuthStrategy]{Store: db, Table: "auth_strategy"}, redis} } // Create creates a strategy with user password/email // Ideally this should be move to PG instead of redis -func (a auth) Create(authType AuthType, m model.AuthStrategy) error { +func (a auth[T]) Create(authType AuthType, m model.AuthStrategy) error { hash, err := bcrypt.GenerateFromPassword([]byte(m.Data), 8) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } strat := &m strat.Data = string(hash) return a.redis.Set(strat.ContactData, strat, 0) } -func (a auth) CreateAny(key string, val any, expire time.Duration) error { +func (a auth[T]) CreateAny(key string, val any, expire time.Duration) error { return a.redis.Set(key, val, expire) } // CreateAPIKey creates and persists an API Key for a platform -func (a auth) CreateAPIKey(entityId string, authType AuthType, key string, persistOnly bool) (model.AuthStrategy, error) { +func (a auth[T]) CreateAPIKey(entityId string, authType AuthType, key string, persistOnly bool) (model.AuthStrategy, error) { // only insert to postgres and skip redis cache if persistOnly { - rows, err := a.store.Queryx("INSERT INTO auth_strategy(type,data) VALUES($1, $2) RETURNING *", authType, key) + rows, err := a.Store.Queryx("INSERT INTO auth_strategy(type,data) VALUES($1, $2) RETURNING *", authType, key) if err == nil { m := model.AuthStrategy{} var scanErr error @@ -93,7 +93,7 @@ func (a auth) CreateAPIKey(entityId string, authType AuthType, key string, persi } // CreateJWTRefresh creates and persists a refresh jwt token -func (a auth) CreateJWTRefresh(key string, userId string) (model.AuthStrategy, error) { +func (a auth[T]) CreateJWTRefresh(key string, userId string) (model.AuthStrategy, error) { expireAt := time.Hour * 24 * 7 // 7 days expiration m := model.AuthStrategy{ Id: key, @@ -107,51 +107,51 @@ func (a auth) CreateJWTRefresh(key string, userId string) (model.AuthStrategy, e return m, a.redis.Set(key, m, expireAt) } -func (a auth) Get(key string) (model.AuthStrategy, error) { +func (a auth[T]) Get(key string) (model.AuthStrategy, error) { m, err := a.redis.Get(key) if err != nil { - return model.AuthStrategy{}, common.StringError(err) + return model.AuthStrategy{}, libcommon.StringError(err) } authStrat := model.AuthStrategy{} err = json.Unmarshal(m, &authStrat) if err != nil { - return model.AuthStrategy{}, common.StringError(err) + return model.AuthStrategy{}, libcommon.StringError(err) } return authStrat, nil } // return the user id from the refresh token or error if token is invalid or expired -func (a auth) GetUserIdFromRefreshToken(refreshToken string) (string, error) { +func (a auth[T]) GetUserIdFromRefreshToken(refreshToken string) (string, error) { authStrat, err := a.Get(refreshToken) if err != nil { - return "", common.StringError(err) + return "", libcommon.StringError(err) } // assert token has not expired if authStrat.ExpiresAt.Before(time.Now()) { - return "", common.StringError(fmt.Errorf("refresh token expired")) + return "", libcommon.StringError(fmt.Errorf("refresh token expired")) } // assert token has not been deactivated if authStrat.DeactivatedAt != nil { - return "", common.StringError(fmt.Errorf("refresh token deactivated at %s", authStrat.DeactivatedAt)) + return "", libcommon.StringError(fmt.Errorf("refresh token deactivated at %s", authStrat.DeactivatedAt)) } // if all is well, return the user id return authStrat.Data, nil } -func (a auth) GetKeyString(key string) (string, error) { +func (a auth[T]) GetKeyString(key string) (string, error) { m, err := a.redis.Get(key) if err != nil { - return "", common.StringError(err) + return "", libcommon.StringError(err) } return string(m), nil } // List all the available auth_keys on the postgres db -func (a auth) List(limit, offset int) ([]model.AuthStrategy, error) { +func (a auth[T]) List(limit, offset int) ([]model.AuthStrategy, error) { list := []model.AuthStrategy{} - err := a.store.Select(&list, "SELECT * FROM auth_strategy LIMIT $1 OFFSET $2", limit, offset) + err := a.Store.Select(&list, "SELECT * FROM auth_strategy LIMIT $1 OFFSET $2", limit, offset) if err != nil && err == sql.ErrNoRows { return list, nil } @@ -159,9 +159,9 @@ func (a auth) List(limit, offset int) ([]model.AuthStrategy, error) { } // ListByStatus lists all auth_keys with a given status on the postgres db -func (a auth) ListByStatus(limit, offset int, status string) ([]model.AuthStrategy, error) { +func (a auth[T]) ListByStatus(limit, offset int, status string) ([]model.AuthStrategy, error) { list := []model.AuthStrategy{} - err := a.store.Select(&list, "SELECT * FROM auth_strategy WHERE status = $1 LIMIT $2 OFFSET $3", status, limit, offset) + err := a.Store.Select(&list, "SELECT * FROM auth_strategy WHERE status = $1 LIMIT $2 OFFSET $3", status, limit, offset) if err != nil && err == sql.ErrNoRows { return list, nil } @@ -169,13 +169,13 @@ func (a auth) ListByStatus(limit, offset int, status string) ([]model.AuthStrate } // UpdateStatus updates the status on postgres db and returns the updated row -func (a auth) UpdateStatus(Id, status string) (model.AuthStrategy, error) { - row := a.store.QueryRowx("UPDATE auth_strategy SET status = $2 WHERE id = $1 RETURNING *", Id, status) +func (a auth[T]) UpdateStatus(Id, status string) (model.AuthStrategy, error) { + row := a.Store.QueryRowx("UPDATE auth_strategy SET status = $2 WHERE id = $1 RETURNING *", Id, status) m := model.AuthStrategy{} err := row.StructScan(&m) return m, err } -func (a auth) Delete(key string) error { +func (a auth[T]) Delete(key string) error { return a.redis.Delete(key) } diff --git a/pkg/repository/base.go b/pkg/repository/base.go deleted file mode 100644 index 1668fd2d..00000000 --- a/pkg/repository/base.go +++ /dev/null @@ -1,182 +0,0 @@ -package repository - -import ( - "context" - "database/sql" - "errors" - "fmt" - "strings" - - "github.com/jmoiron/sqlx" - - "github.com/String-xyz/string-api/pkg/internal/common" -) - -var ErrNotFound = errors.New("not found") - -type Repositories struct { - Auth AuthStrategy - User User - Contact Contact - Instrument Instrument - Device Device - UserToPlatform UserToPlatform - Asset Asset - Network Network - Platform Platform - Transaction Transaction - TxLeg TxLeg - Location Location -} - -type Queryable interface { - sqlx.Ext - sqlx.ExecerContext - sqlx.PreparerContext - sqlx.QueryerContext - sqlx.Preparer - - GetContext(context.Context, interface{}, string, ...interface{}) error - SelectContext(context.Context, interface{}, string, ...interface{}) error - Get(interface{}, string, ...interface{}) error - MustExecContext(context.Context, string, ...interface{}) sql.Result - PreparexContext(context.Context, string) (*sqlx.Stmt, error) - QueryRowContext(context.Context, string, ...interface{}) *sql.Row - Select(interface{}, string, ...interface{}) error - QueryRow(string, ...interface{}) *sql.Row - PrepareNamedContext(context.Context, string) (*sqlx.NamedStmt, error) - PrepareNamed(string) (*sqlx.NamedStmt, error) - Preparex(string) (*sqlx.Stmt, error) - NamedExec(string, interface{}) (sql.Result, error) - NamedExecContext(context.Context, string, interface{}) (sql.Result, error) - MustExec(string, ...interface{}) sql.Result - NamedQuery(string, interface{}) (*sqlx.Rows, error) -} - -type Readable interface { - Select(interface{}, string, ...interface{}) error - Get(interface{}, string, ...interface{}) error -} - -type Transactable interface { - // MustBegin panic if Tx cant start - // the underlying store is set to *sqlx.Tx - // You must call rollBack(), Commit() or Reset() to return back from *sqlx.Tx to *sqlx.DB - MustBegin() Queryable - // Rollback rollback the underyling Tx and resets back to *sqlx.DB from *sqlx.Tx - Rollback() - // Commit commits the undelying Tx and resets to back to *sqlx.DB from *sqlx.Tx - Commit() error - // SetTx sets the underying store to be sqlx.Tx so it can be used for transaction across multiple repos - SetTx(t Queryable) - // Reset changes the store back to *sqlx.DB from *sqlx.Tx - // Useful when there are many repos using the same *sqlx.Tx - Reset(b ...Transactable) -} - -type base[T any] struct { - store Queryable - db Queryable - table string -} - -func (b *base[T]) MustBegin() Queryable { - db := b.store.(*sqlx.DB) - b.db = db - t := db.MustBegin() - b.store = t - return t -} - -func (b *base[T]) Rollback() { - t := b.store.(*sqlx.Tx) - t.Rollback() - b.Reset() -} - -func (b *base[T]) Commit() error { - t := b.store.(*sqlx.Tx) - err := t.Commit() - if err != nil { - common.StringError(err) - } - return err -} - -func (b *base[T]) SetTx(t Queryable) { - b.db = b.store - b.store = t -} - -func (b *base[T]) Reset(repos ...Transactable) { - b.store = b.db - for _, v := range repos { - v.Reset() - } -} - -func (b base[T]) List(limit int, offset int) (list []T, err error) { - if limit == 0 { - limit = 20 - } - - err = b.store.Select(&list, fmt.Sprintf("SELECT * FROM %s LIMIT $1 OFFSET $2", b.table), limit, offset) - if err == sql.ErrNoRows { - return list, err - } - return list, err -} - -func (b base[T]) GetById(id string) (m T, err error) { - err = b.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE id = $1 AND deactivated_at IS NULL", b.table), id) - if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) - } - return m, err -} - -// Returns the first match of the user's ID -func (b base[T]) GetByUserId(userId string) (m T, err error) { - err = b.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND deactivated_at IS NULL LIMIT 1", b.table), userId) - if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) - } - return m, err -} - -func (b base[T]) ListByUserId(userId string, limit int, offset int) ([]T, error) { - list := []T{} - if limit == 0 { - limit = 20 - } - err := b.store.Select(&list, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 LIMIT $2 OFFSET $3", b.table), userId, limit, offset) - if err == sql.ErrNoRows { - return list, common.StringError(err) - } - if err != nil { - return list, common.StringError(err) - } - - return list, nil -} - -func (b base[T]) Update(id string, updates any) error { - names, keyToUpdate := common.KeysAndValues(updates) - if len(names) == 0 { - return common.StringError(errors.New("no fields to update")) - } - query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", b.table, strings.Join(names, ", "), id) - _, err := b.store.NamedExec(query, keyToUpdate) - if err != nil { - return common.StringError(err) - } - return err -} - -func (b base[T]) Select(model interface{}, query string, params ...interface{}) error { - return b.store.Select(model, query, params) -} - -func (b base[T]) Get(model interface{}, query string, params ...interface{}) error { - return b.store.Get(model, query, params) -} diff --git a/pkg/repository/base_test.go b/pkg/repository/base_test.go deleted file mode 100644 index 9edfc201..00000000 --- a/pkg/repository/base_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package repository - -import ( - "testing" - - "github.com/DATA-DOG/go-sqlmock" - "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" -) - -func TestBaseUpdate(t *testing.T) { - db, mock, err := sqlmock.New() - sqlxDB := sqlx.NewDb(db, "sqlmock") - if err != nil { - t.Fatalf("error %s was not expected when opening stub db", err) - } - defer db.Close() - mock.ExpectExec(`UPDATE contact SET`).WithArgs("type") - mType := "type" - m := model.ContactUpdates{Type: &mType} - - NewContact(sqlxDB).Update("Id", m) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("error '%s' was not expected, while updating a contact", err) - } -} diff --git a/pkg/repository/contact.go b/pkg/repository/contact.go index 6a918bba..894bb203 100644 --- a/pkg/repository/contact.go +++ b/pkg/repository/contact.go @@ -1,23 +1,25 @@ package repository import ( + "context" "database/sql" "fmt" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type Contact interface { - Transactable - Readable + database.Transactable Create(model.Contact) (model.Contact, error) - GetById(id string) (model.Contact, error) - GetByUserId(userId string) (model.Contact, error) - ListByUserId(userId string, imit int, offset int) ([]model.Contact, error) - List(limit int, offset int) ([]model.Contact, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.Contact, error) + GetByUserId(ctx context.Context, userId string) (model.Contact, error) + ListByUserId(ctx context.Context, userId string, imit int, offset int) ([]model.Contact, error) + List(ctx context.Context, limit int, offset int) ([]model.Contact, error) + Update(ctx context.Context, id string, updates any) error GetByData(data string) (model.Contact, error) GetByUserIdAndPlatformId(userId string, platformId string) (model.Contact, error) GetByUserIdAndType(userId string, _type string) (model.Contact, error) @@ -25,25 +27,25 @@ type Contact interface { } type contact[T any] struct { - base[T] + repository.Base[T] } -func NewContact(db *sqlx.DB) Contact { - return &contact[model.Contact]{base: base[model.Contact]{store: db, table: "contact"}} +func NewContact(db database.Queryable) Contact { + return &contact[model.Contact]{repository.Base[model.Contact]{Store: db, Table: "contact"}} } func (u contact[T]) Create(insert model.Contact) (model.Contact, error) { m := model.Contact{} - rows, err := u.store.NamedQuery(` + rows, err := u.Store.NamedQuery(` INSERT INTO contact (user_id, data, type, status) VALUES(:user_id, :data, :type, :status) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } @@ -53,9 +55,9 @@ func (u contact[T]) Create(insert model.Contact) (model.Contact, error) { func (u contact[T]) GetByData(data string) (model.Contact, error) { m := model.Contact{} - err := u.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE data = $1", u.table), data) + err := u.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE data = $1", u.Table), data) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } return m, nil } @@ -63,7 +65,7 @@ func (u contact[T]) GetByData(data string) (model.Contact, error) { // TODO: replace references to GetByUserIdAndStatus with the following: func (u contact[T]) GetByUserIdAndPlatformId(userId string, platformId string) (model.Contact, error) { m := model.Contact{} - err := u.store.Get(&m, fmt.Sprintf(` + err := u.Store.Get(&m, fmt.Sprintf(` SELECT contact.* FROM %s LEFT JOIN contact_platform @@ -72,27 +74,27 @@ func (u contact[T]) GetByUserIdAndPlatformId(userId string, platformId string) ( ON contact_to_platform.platform_id = platform.id WHERE contact.user_id = $1 AND platform.id = $2 - `, u.table), userId, platformId) + `, u.Table), userId, platformId) if err != nil && err == sql.ErrNoRows { - return m, ErrNotFound + return m, serror.NOT_FOUND } - return m, common.StringError(err) + return m, libcommon.StringError(err) } func (u contact[T]) GetByUserIdAndType(userId string, _type string) (model.Contact, error) { m := model.Contact{} - err := u.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = $2 LIMIT 1", u.table), userId, _type) + err := u.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = $2 LIMIT 1", u.Table), userId, _type) if err != nil && err == sql.ErrNoRows { - return m, ErrNotFound + return m, serror.NOT_FOUND } - return m, common.StringError(err) + return m, libcommon.StringError(err) } func (u contact[T]) GetByUserIdAndStatus(userId, status string) (model.Contact, error) { m := model.Contact{} - err := u.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND status = $2 LIMIT 1", u.table), userId, status) + err := u.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND status = $2 LIMIT 1", u.Table), userId, status) if err != nil && err == sql.ErrNoRows { - return m, ErrNotFound + return m, serror.NOT_FOUND } - return m, common.StringError(err) + return m, libcommon.StringError(err) } diff --git a/pkg/repository/contact_to_platform.go b/pkg/repository/contact_to_platform.go index f4be9e74..6f0bc954 100644 --- a/pkg/repository/contact_to_platform.go +++ b/pkg/repository/contact_to_platform.go @@ -1,40 +1,42 @@ package repository import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type ContactToPlatform interface { - Transactable - Readable + database.Transactable Create(model.ContactToPlatform) (model.ContactToPlatform, error) - GetById(id string) (model.ContactToPlatform, error) - List(limit int, offset int) ([]model.ContactToPlatform, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.ContactToPlatform, error) + List(ctx context.Context, limit int, offset int) ([]model.ContactToPlatform, error) + Update(ctx context.Context, id string, updates any) error } type contactToPlatform[T any] struct { - base[T] + repository.Base[T] } -func NewContactPlatform(db *sqlx.DB) ContactToPlatform { - return &contactToPlatform[model.ContactToPlatform]{base: base[model.ContactToPlatform]{store: db, table: "contact_to_platform"}} +func NewContactPlatform(db database.Queryable) ContactToPlatform { + return &contactToPlatform[model.ContactToPlatform]{repository.Base[model.ContactToPlatform]{Store: db, Table: "contact_to_platform"}} } func (u contactToPlatform[T]) Create(insert model.ContactToPlatform) (model.ContactToPlatform, error) { m := model.ContactToPlatform{} - rows, err := u.store.NamedQuery(` + rows, err := u.Store.NamedQuery(` INSERT INTO contact_to_platform (contact_id, platform_id) VALUES(:contact_id, :platform_id) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } defer rows.Close() diff --git a/pkg/repository/device.go b/pkg/repository/device.go index 08410694..de565a33 100644 --- a/pkg/repository/device.go +++ b/pkg/repository/device.go @@ -1,47 +1,50 @@ package repository import ( + "context" "database/sql" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type Device interface { - Transactable + database.Transactable Create(model.Device) (model.Device, error) - GetById(id string) (model.Device, error) + GetById(ctx context.Context, id string) (model.Device, error) // GetByUserIdAndFingerprint gets a device by fingerprint ID and userId, using a compound index // the visitor might exisit for two users but the uniqueness comes from (userId, fingerprint) GetByUserIdAndFingerprint(userId string, fingerprint string) (model.Device, error) - GetByUserId(userId string) (model.Device, error) - ListByUserId(userId string, imit int, offset int) ([]model.Device, error) - Update(id string, updates any) error + GetByUserId(ctx context.Context, id string) (model.Device, error) + ListByUserId(ctx context.Context, userId string, imit int, offset int) ([]model.Device, error) + Update(ctx context.Context, id string, updates any) error } type device[T any] struct { - base[T] + baserepo.Base[T] } -func NewDevice(db *sqlx.DB) Device { - return &device[model.Device]{base[model.Device]{store: db, table: "device"}} +func NewDevice(db database.Queryable) Device { + return &device[model.Device]{baserepo.Base[model.Device]{Store: db, Table: "device"}} } func (d device[T]) Create(insert model.Device) (model.Device, error) { m := model.Device{} - rows, err := d.store.NamedQuery(` + rows, err := d.Store.NamedQuery(` INSERT INTO device (last_used_at,validated_at, type, description, user_id, fingerprint, ip_addresses) VALUES(:last_used_at,:validated_at, :type, :description, :user_id, :fingerprint, :ip_addresses) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } @@ -51,9 +54,9 @@ func (d device[T]) Create(insert model.Device) (model.Device, error) { func (d device[T]) GetByUserIdAndFingerprint(userId, fingerprint string) (model.Device, error) { m := model.Device{} - err := d.store.Get(&m, "SELECT * FROM device WHERE user_id = $1 AND fingerprint = $2 LIMIT 1", userId, fingerprint) + err := d.Store.Get(&m, "SELECT * FROM device WHERE user_id = $1 AND fingerprint = $2 LIMIT 1", userId, fingerprint) if err != nil && err == sql.ErrNoRows { - return m, ErrNotFound + return m, serror.NOT_FOUND } return m, err } diff --git a/pkg/repository/instrument.go b/pkg/repository/instrument.go index 35f6e86f..7e6463bd 100644 --- a/pkg/repository/instrument.go +++ b/pkg/repository/instrument.go @@ -1,20 +1,24 @@ package repository import ( + "context" "database/sql" "fmt" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) type Instrument interface { - Transactable + database.Transactable Create(model.Instrument) (model.Instrument, error) - Update(id string, updates any) error - GetById(id string) (model.Instrument, error) + Update(ctx context.Context, id string, updates any) error + GetById(ctx context.Context, id string) (model.Instrument, error) GetWalletByAddr(addr string) (model.Instrument, error) GetCardByFingerprint(fingerprint string) (m model.Instrument, err error) GetWalletByUserId(userId string) (model.Instrument, error) @@ -23,25 +27,25 @@ type Instrument interface { } type instrument[T any] struct { - base[T] + baserepo.Base[T] } func NewInstrument(db *sqlx.DB) Instrument { - return &instrument[model.Instrument]{base[model.Instrument]{store: db, table: "instrument"}} + return &instrument[model.Instrument]{baserepo.Base[model.Instrument]{Store: db, Table: "instrument"}} } func (i instrument[T]) Create(insert model.Instrument) (model.Instrument, error) { m := model.Instrument{} - rows, err := i.store.NamedQuery(` + rows, err := i.Store.NamedQuery(` INSERT INTO instrument (type, status, network, public_key, user_id, last_4) VALUES(:type, :status, :network, :public_key, :user_id, :last_4) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } @@ -51,11 +55,11 @@ func (i instrument[T]) Create(insert model.Instrument) (model.Instrument, error) func (i instrument[T]) GetWalletByAddr(addr string) (model.Instrument, error) { m := model.Instrument{} - err := i.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE public_key = $1", i.table), addr) + err := i.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE public_key = $1", i.Table), addr) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } else if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } return m, nil } @@ -66,22 +70,22 @@ func (i instrument[T]) GetCardByFingerprint(fingerprint string) (m model.Instrum func (i instrument[T]) GetWalletByUserId(userId string) (model.Instrument, error) { m := model.Instrument{} - err := i.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = 'Crypto Wallet'", i.table), userId) + err := i.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = 'Crypto Wallet'", i.Table), userId) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } else if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } return m, nil } func (i instrument[T]) GetBankByUserId(userId string) (model.Instrument, error) { m := model.Instrument{} - err := i.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = 'Bank Account'", i.table), userId) + err := i.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE user_id = $1 AND type = 'Bank Account'", i.Table), userId) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } else if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } return m, nil } @@ -90,11 +94,11 @@ func (i instrument[T]) WalletAlreadyExists(addr string) (bool, error) { wallet, err := i.GetWalletByAddr(addr) if err != nil && errors.Cause(err).Error() != "not found" { // because we are wrapping error and care about its value - return true, common.StringError(err) + return true, libcommon.StringError(err) } else if err == nil && wallet.UserId != "" { - return true, common.StringError(errors.New("wallet already associated with user")) + return true, libcommon.StringError(errors.New("wallet already associated with user")) } else if err == nil && wallet.PublicKey == addr { - return true, common.StringError(errors.New("wallet already exists")) + return true, libcommon.StringError(errors.New("wallet already exists")) } return false, nil diff --git a/pkg/repository/location.go b/pkg/repository/location.go index 1b6976ff..f1d386f6 100644 --- a/pkg/repository/location.go +++ b/pkg/repository/location.go @@ -1,38 +1,42 @@ package repository import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" "github.com/jmoiron/sqlx" ) type Location interface { - Transactable + database.Transactable Create(model.Location) (model.Location, error) - GetById(id string) (model.Location, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.Location, error) + Update(ctx context.Context, id string, updates any) error } type location[T any] struct { - base[T] + baserepo.Base[T] } func NewLocation(db *sqlx.DB) Location { - return &location[model.Location]{base[model.Location]{store: db, table: "location"}} + return &location[model.Location]{baserepo.Base[model.Location]{Store: db, Table: "location"}} } func (i location[T]) Create(insert model.Location) (model.Location, error) { m := model.Location{} - rows, err := i.store.NamedQuery(` + rows, err := i.Store.NamedQuery(` INSERT INTO location (name) VALUES(:name) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } diff --git a/pkg/repository/location_test.go b/pkg/repository/location_test.go index 5d06c4b5..b688d6b4 100644 --- a/pkg/repository/location_test.go +++ b/pkg/repository/location_test.go @@ -1,6 +1,7 @@ package repository import ( + "context" "testing" "time" @@ -11,6 +12,7 @@ import ( ) func TestGetLocation(t *testing.T) { + ctx := context.Background() id := uuid.NewString() db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) sqlxDB := sqlx.NewDb(db, "sqlmock") @@ -24,7 +26,7 @@ func TestGetLocation(t *testing.T) { mock.ExpectQuery("SELECT * FROM location WHERE id = $1 AND deactivated_at IS NULL").WillReturnRows(rows).WithArgs(id) - location, err := NewLocation(sqlxDB).GetById(id) + location, err := NewLocation(sqlxDB).GetById(ctx, id) assert.NoError(t, err) assert.NotEmpty(t, location.Id) if err := mock.ExpectationsWereMet(); err != nil { diff --git a/pkg/repository/network.go b/pkg/repository/network.go index f4a29e32..8c055f1c 100644 --- a/pkg/repository/network.go +++ b/pkg/repository/network.go @@ -1,38 +1,41 @@ package repository import ( + "context" "database/sql" "fmt" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type Network interface { - Transactable + database.Transactable Create(model.Network) (model.Network, error) - GetById(id string) (model.Network, error) + GetById(ctx context.Context, id string) (model.Network, error) GetByChainId(chainId uint64) (model.Network, error) - Update(id string, updates any) error + Update(ctx context.Context, id string, updates any) error } type network[T any] struct { - base[T] + baserepo.Base[T] } -func NewNetwork(db *sqlx.DB) Network { - return &network[model.Network]{base[model.Network]{store: db, table: "network"}} +func NewNetwork(db database.Queryable) Network { + return &network[model.Network]{baserepo.Base[model.Network]{Store: db, Table: "network"}} } func (n network[T]) Create(insert model.Network) (model.Network, error) { m := model.Network{} - rows, err := n.store.NamedQuery(` + rows, err := n.Store.NamedQuery(` INSERT INTO network (name, network_id, chain_id, gas_oracle, rpc_url, explorer_url) VALUES(:name, :network_id, :chain_id, :gas_oracle, :rpc_url, :explorer_url) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } defer rows.Close() @@ -40,7 +43,7 @@ func (n network[T]) Create(insert model.Network) (model.Network, error) { for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } @@ -49,9 +52,9 @@ func (n network[T]) Create(insert model.Network) (model.Network, error) { func (n network[T]) GetByChainId(chainId uint64) (model.Network, error) { m := model.Network{} - err := n.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE chain_id = $1", n.table), chainId) + err := n.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE chain_id = $1", n.Table), chainId) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } return m, nil } diff --git a/pkg/repository/platform.go b/pkg/repository/platform.go index 45627b6b..4999c5ed 100644 --- a/pkg/repository/platform.go +++ b/pkg/repository/platform.go @@ -1,11 +1,13 @@ package repository import ( + "context" "time" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx/types" ) @@ -17,35 +19,35 @@ type PlaformUpdates struct { } type Platform interface { - Transactable + database.Transactable Create(model.Platform) (model.Platform, error) - GetById(id string) (model.Platform, error) - List(limit int, offset int) ([]model.Platform, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.Platform, error) + List(ctx context.Context, limit int, offset int) ([]model.Platform, error) + Update(ctx context.Context, id string, updates any) error } type platform[T any] struct { - base[T] + baserepo.Base[T] } -func NewPlatform(db *sqlx.DB) Platform { - return &platform[model.Platform]{base: base[model.Platform]{store: db, table: "platform"}} +func NewPlatform(db database.Queryable) Platform { + return &platform[model.Platform]{baserepo.Base[model.Platform]{Store: db, Table: "platform"}} } func (p platform[T]) Create(m model.Platform) (model.Platform, error) { plat := model.Platform{} - rows, err := p.store.NamedQuery(` + rows, err := p.Store.NamedQuery(` INSERT INTO platform (name, description) VALUES(:name, :description) RETURNING *`, m) if err != nil { - return plat, common.StringError(err) + return plat, libcommon.StringError(err) } for rows.Next() { err := rows.StructScan(&plat) if err != nil { - return plat, common.StringError(err) + return plat, libcommon.StringError(err) } } defer rows.Close() diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go new file mode 100644 index 00000000..6f6866c7 --- /dev/null +++ b/pkg/repository/repository.go @@ -0,0 +1,16 @@ +package repository + +type Repositories struct { + Auth AuthStrategy + User User + Contact Contact + Instrument Instrument + Device Device + UserToPlatform UserToPlatform + Asset Asset + Network Network + Platform Platform + Transaction Transaction + TxLeg TxLeg + Location Location +} diff --git a/pkg/repository/transaction.go b/pkg/repository/transaction.go index daf59b99..a931d3b5 100644 --- a/pkg/repository/transaction.go +++ b/pkg/repository/transaction.go @@ -1,39 +1,42 @@ package repository import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type Transaction interface { - Transactable + database.Transactable Create(model.Transaction) (model.Transaction, error) - GetById(id string) (model.Transaction, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.Transaction, error) + Update(ctx context.Context, id string, updates any) error } type transaction[T any] struct { - base[T] + baserepo.Base[T] } -func NewTransaction(db *sqlx.DB) Transaction { - return &transaction[model.Transaction]{base[model.Transaction]{store: db, table: "transaction"}} +func NewTransaction(db database.Queryable) Transaction { + return &transaction[model.Transaction]{baserepo.Base[model.Transaction]{Store: db, Table: "transaction"}} } func (t transaction[T]) Create(insert model.Transaction) (model.Transaction, error) { m := model.Transaction{} // TODO: Add platform_id once it becomes available - rows, err := t.store.NamedQuery(` + rows, err := t.Store.NamedQuery(` INSERT INTO transaction (status, network_id, device_id, platform_id, ip_address) VALUES(:status, :network_id, :device_id, :platform_id, :ip_address) RETURNING id`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.Scan(&m.Id) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } diff --git a/pkg/repository/tx_leg.go b/pkg/repository/tx_leg.go index 1e1ac14b..e881d42b 100644 --- a/pkg/repository/tx_leg.go +++ b/pkg/repository/tx_leg.go @@ -1,38 +1,41 @@ package repository import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type TxLeg interface { - Transactable + database.Transactable Create(model.TxLeg) (model.TxLeg, error) - GetById(id string) (model.TxLeg, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.TxLeg, error) + Update(ctx context.Context, id string, updates any) error } type txLeg[T any] struct { - base[T] + baserepo.Base[T] } -func NewTxLeg(db *sqlx.DB) TxLeg { - return &txLeg[model.TxLeg]{base[model.TxLeg]{store: db, table: "tx_leg"}} +func NewTxLeg(db database.Queryable) TxLeg { + return &txLeg[model.TxLeg]{baserepo.Base[model.TxLeg]{Store: db, Table: "tx_leg"}} } func (t txLeg[T]) Create(insert model.TxLeg) (model.TxLeg, error) { m := model.TxLeg{} - rows, err := t.store.NamedQuery(` + rows, err := t.Store.NamedQuery(` INSERT INTO tx_leg (timestamp, amount, value, asset_id, user_id, instrument_id) VALUES(:timestamp, :amount, :value, :asset_id, :user_id, :instrument_id) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } diff --git a/pkg/repository/user.go b/pkg/repository/user.go index a839a4b7..c30db674 100644 --- a/pkg/repository/user.go +++ b/pkg/repository/user.go @@ -1,65 +1,67 @@ package repository import ( + "context" "database/sql" "errors" "fmt" "strings" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type User interface { - Transactable - Readable + database.Transactable Create(model.User) (model.User, error) - GetById(id string) (model.User, error) - List(limit int, offset int) ([]model.User, error) - Update(id string, updates any) (model.User, error) + GetById(ctx context.Context, id string) (model.User, error) + List(ctx context.Context, limit int, offset int) ([]model.User, error) + Update(ctx context.Context, id string, updates any) (model.User, error) GetByType(label string) (model.User, error) UpdateStatus(id string, status string) (model.User, error) } type user[T any] struct { - base[T] + baserepo.Base[T] } -func NewUser(db *sqlx.DB) User { - return &user[model.User]{base[model.User]{store: db, table: "string_user"}} +func NewUser(db database.Queryable) User { + return &user[model.User]{baserepo.Base[model.User]{Store: db, Table: "string_user"}} } func (u user[T]) Create(insert model.User) (model.User, error) { m := model.User{} - rows, err := u.store.NamedQuery(` + rows, err := u.Store.NamedQuery(` INSERT INTO string_user (type, status, first_name, middle_name, last_name) VALUES(:type, :status, :first_name, :middle_name, :last_name) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } defer rows.Close() for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } return m, nil } -func (u user[T]) Update(id string, updates any) (model.User, error) { - names, keyToUpdate := common.KeysAndValues(updates) +func (u user[T]) Update(ctx context.Context, id string, updates any) (model.User, error) { + names, keyToUpdate := libcommon.KeysAndValues(updates) var user model.User if len(names) == 0 { - return user, common.StringError(errors.New("no fields to update")) + return user, libcommon.StringError(errors.New("no fields to update")) } - query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s' RETURNING *", u.table, strings.Join(names, ", "), id) - rows, err := u.store.NamedQuery(query, keyToUpdate) + query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s' RETURNING *", u.Table, strings.Join(names, ", "), id) + rows, err := u.Store.NamedQuery(query, keyToUpdate) if err != nil { - return user, common.StringError(err) + return user, libcommon.StringError(err) } defer rows.Close() @@ -68,7 +70,7 @@ func (u user[T]) Update(id string, updates any) (model.User, error) { } if err != nil { - return user, common.StringError(err) + return user, libcommon.StringError(err) } return user, err } @@ -76,20 +78,20 @@ func (u user[T]) Update(id string, updates any) (model.User, error) { // update user status func (u user[T]) UpdateStatus(id string, status string) (model.User, error) { m := model.User{} - err := u.store.Get(&m, fmt.Sprintf("UPDATE %s SET status = $1 WHERE id = $2 RETURNING *", u.table), status, id) + err := u.Store.Get(&m, fmt.Sprintf("UPDATE %s SET status = $1 WHERE id = $2 RETURNING *", u.Table), status, id) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } return m, nil } func (u user[T]) GetByType(label string) (model.User, error) { m := model.User{} - err := u.store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE type = $1 LIMIT 1", u.table), label) + err := u.Store.Get(&m, fmt.Sprintf("SELECT * FROM %s WHERE type = $1 LIMIT 1", u.Table), label) if err != nil && err == sql.ErrNoRows { - return m, common.StringError(ErrNotFound) + return m, serror.NOT_FOUND } else if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } return m, nil } diff --git a/pkg/repository/user_test.go b/pkg/repository/user_test.go index b4e4ebf2..f8cecd37 100644 --- a/pkg/repository/user_test.go +++ b/pkg/repository/user_test.go @@ -1,6 +1,7 @@ package repository import ( + "context" "testing" "time" @@ -33,6 +34,7 @@ func TestCreateUser(t *testing.T) { } func TestGetUser(t *testing.T) { + ctx := context.Background() id := uuid.NewString() db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) sqlxDB := sqlx.NewDb(db, "sqlmock") @@ -46,7 +48,7 @@ func TestGetUser(t *testing.T) { mock.ExpectQuery("SELECT * FROM string_user WHERE id = $1 AND deactivated_at IS NULL").WillReturnRows(rows).WithArgs(id) - user, err := NewUser(sqlxDB).GetById(id) + user, err := NewUser(sqlxDB).GetById(ctx, id) assert.NoError(t, err) assert.Equal(t, id, user.Id) if err := mock.ExpectationsWereMet(); err != nil { @@ -55,6 +57,7 @@ func TestGetUser(t *testing.T) { } func TestListUser(t *testing.T) { + ctx := context.Background() id1, id2 := uuid.NewString(), uuid.NewString() db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) sqlxDB := sqlx.NewDb(db, "sqlmock") @@ -69,7 +72,7 @@ func TestListUser(t *testing.T) { mock.ExpectQuery("SELECT * FROM string_user LIMIT $1 OFFSET $2").WillReturnRows(rows).WithArgs(10, 0) - NewUser(sqlxDB).List(10, 0) + NewUser(sqlxDB).List(ctx, 10, 0) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("error '%s' was not expected, getting the list of users", err) } diff --git a/pkg/repository/user_to_platform.go b/pkg/repository/user_to_platform.go index f5d7f534..e1480ce7 100644 --- a/pkg/repository/user_to_platform.go +++ b/pkg/repository/user_to_platform.go @@ -1,41 +1,43 @@ package repository import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + baserepo "github.com/String-xyz/go-lib/repository" "github.com/String-xyz/string-api/pkg/model" - "github.com/jmoiron/sqlx" ) type UserToPlatform interface { - Transactable - Readable + database.Transactable Create(model.UserToPlatform) (model.UserToPlatform, error) - GetById(id string) (model.UserToPlatform, error) - List(limit int, offset int) ([]model.UserToPlatform, error) - ListByUserId(userId string, imit int, offset int) ([]model.UserToPlatform, error) - Update(id string, updates any) error + GetById(ctx context.Context, id string) (model.UserToPlatform, error) + List(ctx context.Context, limit int, offset int) ([]model.UserToPlatform, error) + ListByUserId(ctx context.Context, userId string, imit int, offset int) ([]model.UserToPlatform, error) + Update(ctx context.Context, id string, updates any) error } type userToPlatform[T any] struct { - base[T] + baserepo.Base[T] } -func NewUserToPlatform(db *sqlx.DB) UserToPlatform { - return &userToPlatform[model.UserToPlatform]{base: base[model.UserToPlatform]{store: db, table: "user_to_platform"}} +func NewUserToPlatform(db database.Queryable) UserToPlatform { + return &userToPlatform[model.UserToPlatform]{baserepo.Base[model.UserToPlatform]{Store: db, Table: "user_to_platform"}} } func (u userToPlatform[T]) Create(insert model.UserToPlatform) (model.UserToPlatform, error) { m := model.UserToPlatform{} - rows, err := u.store.NamedQuery(` + rows, err := u.Store.NamedQuery(` INSERT INTO user_to_platform (user_id, platform_id) VALUES(:user_id, :platform_id) RETURNING *`, insert) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } for rows.Next() { err = rows.StructScan(&m) if err != nil { - return m, common.StringError(err) + return m, libcommon.StringError(err) } } defer rows.Close() diff --git a/pkg/service/auth.go b/pkg/service/auth.go index 1c1c0b2c..8a81205b 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -1,13 +1,16 @@ package service import ( + "context" netmail "net/mail" "os" "regexp" "strings" "time" + libcommon "github.com/String-xyz/go-lib/common" "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/golang-jwt/jwt/v4" @@ -48,11 +51,11 @@ type Auth interface { // VerifySignedPayload receives a signed payload from the user and verifies the signature // if signaure is valid it returns a JWT to authenticate the user - VerifySignedPayload(model.WalletSignaturePayloadSigned) (UserCreateResponse, error) + VerifySignedPayload(ctx context.Context, signature model.WalletSignaturePayloadSigned) (UserCreateResponse, error) GenerateJWT(string, ...model.Device) (JWT, error) ValidateAPIKey(key string) bool - RefreshToken(token string, walletAddress string) (UserCreateResponse, error) + RefreshToken(ctx context.Context, token string, walletAddress string) (UserCreateResponse, error) InvalidateRefreshToken(token string) error } @@ -72,63 +75,63 @@ func (a auth) PayloadToSign(walletAddress string) (SignablePayload, error) { signable := SignablePayload{} if !hexRegex.MatchString(walletAddress) { - return signable, common.StringError(errors.New("missing or invalid address")) + return signable, libcommon.StringError(errors.New("missing or invalid address")) } payload.Address = walletAddress payload.Timestamp = time.Now().Unix() key := os.Getenv("STRING_ENCRYPTION_KEY") - encrypted, err := common.Encrypt(payload, key) + encrypted, err := libcommon.Encrypt(payload, key) if err != nil { - return signable, common.StringError(err) + return signable, libcommon.StringError(err) } return SignablePayload{walletAuthenticationPrefix + encrypted}, nil } -func (a auth) VerifySignedPayload(request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) { +func (a auth) VerifySignedPayload(ctx context.Context, request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) { resp := UserCreateResponse{} key := os.Getenv("STRING_ENCRYPTION_KEY") - payload, err := common.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) + payload, err := libcommon.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } if err := verifyWalletAuthentication(request); err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // Verify user is registered to this wallet address instrument, err := a.repos.Instrument.GetWalletByAddr(payload.Address) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } - user, err := a.repos.User.GetById(instrument.UserId) + user, err := a.repos.User.GetById(ctx, instrument.UserId) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // TODO: remove user.Email and replace with association with contact via user and platform user.Email = getValidatedEmailOrEmpty(a.repos.Contact, user.Id) device, err := a.device.CreateDeviceIfNeeded(user.Id, request.Fingerprint.VisitorId, request.Fingerprint.RequestId) if err != nil && !strings.Contains(err.Error(), "not found") { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // Send verification email if device is unknown and user has a validated email if user.Email != "" && !isDeviceValidated(device) { go a.verification.SendDeviceVerification(user.Id, user.Email, device.Id, device.Description) - return resp, common.StringError(errors.New("unknown device")) + return resp, libcommon.StringError(errors.New("unknown device")) } // Create the JWT jwt, err := a.GenerateJWT(user.Id, device) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // Invalidate device if it is unknown and was validated so it cannot be used again - err = a.device.InvalidateUnknownDevice(device) + err = a.device.InvalidateUnknownDevice(ctx, device) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } return UserCreateResponse{JWT: jwt, User: user}, nil @@ -193,13 +196,13 @@ func (a auth) InvalidateRefreshToken(refreshToken string) error { return a.repos.Auth.Delete(common.ToSha256(refreshToken)) } -func (a auth) RefreshToken(refreshToken string, walletAddress string) (UserCreateResponse, error) { +func (a auth) RefreshToken(ctx context.Context, refreshToken string, walletAddress string) (UserCreateResponse, error) { resp := UserCreateResponse{} // get user id from refresh token userId, err := a.repos.Auth.GetUserIdFromRefreshToken(common.ToSha256(refreshToken)) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // verify wallet address @@ -207,37 +210,37 @@ func (a auth) RefreshToken(refreshToken string, walletAddress string) (UserCreat instrument, err := a.repos.Instrument.GetWalletByAddr(walletAddress) if err != nil { if strings.Contains(err.Error(), "not found") { - return resp, common.StringError(errors.New("wallet address not associated with this user: " + walletAddress)) + return resp, libcommon.StringError(errors.New("wallet address not associated with this user: " + walletAddress)) } - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } if instrument.UserId != userId { - return resp, common.StringError(errors.New("wallet address not associated with this user: " + walletAddress)) + return resp, libcommon.StringError(errors.New("wallet address not associated with this user: " + walletAddress)) } // get device - device, err := a.repos.Device.GetByUserId(userId) + device, err := a.repos.Device.GetByUserId(ctx, userId) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // create new jwt jwt, err := a.GenerateJWT(userId, device) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } resp.JWT = jwt // delete old refresh token err = a.InvalidateRefreshToken(refreshToken) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } - user, err := a.repos.User.GetById(instrument.UserId) + user, err := a.repos.User.GetById(ctx, instrument.UserId) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // get email @@ -249,23 +252,23 @@ func (a auth) RefreshToken(refreshToken string, walletAddress string) (UserCreat func verifyWalletAuthentication(request model.WalletSignaturePayloadSigned) error { key := os.Getenv("STRING_ENCRYPTION_KEY") - preSignedPayload, err := common.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) + preSignedPayload, err := libcommon.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } // Verify users signature bytes := []byte(request.Nonce) valid, err := common.ValidateExternalEVMSignature(request.Signature, preSignedPayload.Address, bytes, true) // true: expect eip131 if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } if !valid { - return common.StringError(errors.New("user signature invalid")) + return libcommon.StringError(errors.New("user signature invalid")) } // Verify timestamp is not expired past 15 minutes if time.Now().Unix() > preSignedPayload.Timestamp+(15*60) { - return common.StringError(errors.New("login payload expired")) + return libcommon.StringError(errors.New("login payload expired")) } return nil diff --git a/pkg/service/chain.go b/pkg/service/chain.go index 493c0855..089fd5a7 100644 --- a/pkg/service/chain.go +++ b/pkg/service/chain.go @@ -3,7 +3,9 @@ package service import ( - "github.com/String-xyz/string-api/pkg/internal/common" + "context" + + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/repository" ) @@ -23,18 +25,18 @@ func stringFee(chainId uint64) (float64, error) { return 0.03, nil } -func ChainInfo(chainId uint64, networkRepo repository.Network, assetRepo repository.Asset) (Chain, error) { +func ChainInfo(ctx context.Context, chainId uint64, networkRepo repository.Network, assetRepo repository.Asset) (Chain, error) { network, err := networkRepo.GetByChainId(chainId) if err != nil { - return Chain{}, common.StringError(err) + return Chain{}, libcommon.StringError(err) } - asset, err := assetRepo.GetById(network.GasTokenId) + asset, err := assetRepo.GetById(ctx, network.GasTokenId) if err != nil { - return Chain{}, common.StringError(err) + return Chain{}, libcommon.StringError(err) } fee, err := stringFee(chainId) if err != nil { - return Chain{}, common.StringError(err) + return Chain{}, libcommon.StringError(err) } return Chain{ChainId: chainId, RPC: network.RPCUrl, Explorer: network.ExplorerUrl, CoingeckoName: asset.ValueOracle.String, OwlracleName: network.GasOracle, StringFee: fee, UUID: network.Id, GasTokenId: network.GasTokenId}, nil } diff --git a/pkg/service/checkout.go b/pkg/service/checkout.go index 34278d0c..cddcbb67 100644 --- a/pkg/service/checkout.go +++ b/pkg/service/checkout.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/checkout/checkout-sdk-go" checkoutCommon "github.com/checkout/checkout-sdk-go/common" "github.com/checkout/checkout-sdk-go/payments" @@ -26,7 +26,7 @@ func getConfig() (*checkout.Config, error) { var config, err = checkout.SdkConfig(&sk, &pk, checkoutEnv) if err != nil { - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } return config, err } @@ -38,13 +38,13 @@ func convertAmount(amount float64) uint64 { func CreateToken(card *tokens.Card) (token *tokens.Response, err error) { config, err := getConfig() if err != nil { - return nil, common.StringError(err) + return nil, libcommon.StringError(err) } client := tokens.NewClient(*config) token, err = client.Request(&tokens.Request{Card: card}) if err != nil { - return token, common.StringError(err) + return token, libcommon.StringError(err) } return token, nil } @@ -64,12 +64,12 @@ func AuthorizeCharge(p transactionProcessingData) (transactionProcessingData, er auth := AuthorizedCharge{} config, err := getConfig() if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } client := payments.NewClient(*config) var paymentTokenId string - if common.IsLocalEnv() { + if libcommon.IsLocalEnv() { if p.executionRequest.CardToken != "" { paymentTokenId = p.executionRequest.CardToken } else { @@ -88,7 +88,7 @@ func AuthorizeCharge(p transactionProcessingData) (transactionProcessingData, er } paymentToken, err := CreateToken(&card) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } paymentTokenId = paymentToken.Created.Token } @@ -122,7 +122,7 @@ func AuthorizeCharge(p transactionProcessingData) (transactionProcessingData, er } response, err := client.Request(request, ¶ms) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Collect authorization ID and Instrument ID @@ -147,7 +147,7 @@ func AuthorizeCharge(p transactionProcessingData) (transactionProcessingData, er func CaptureCharge(p transactionProcessingData) (transactionProcessingData, error) { config, err := getConfig() if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } client := payments.NewClient(*config) @@ -163,7 +163,7 @@ func CaptureCharge(p transactionProcessingData) (transactionProcessingData, erro capture, err := client.Captures(p.cardAuthorization.AuthId, &request, ¶ms) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } p.cardCapture = capture diff --git a/pkg/service/cost.go b/pkg/service/cost.go index 630852f7..bc51154f 100644 --- a/pkg/service/cost.go +++ b/pkg/service/cost.go @@ -6,6 +6,9 @@ import ( "os" "time" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/String-xyz/string-api/pkg/internal/common" "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/store" @@ -47,10 +50,10 @@ type Cost interface { } type cost struct { - redis store.RedisStore // cached token and gas costs + redis database.RedisStore // cached token and gas costs } -func NewCost(redis store.RedisStore) Cost { +func NewCost(redis database.RedisStore) Cost { return &cost{ redis: redis, } @@ -63,7 +66,7 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote, // Query cost of native token in USD nativeCost, err := c.LookupUSD(chain.CoingeckoName, 1) if err != nil { - return model.Quote{}, common.StringError(err) + return model.Quote{}, libcommon.StringError(err) } // Use it to convert transactioncost and apply buffer @@ -77,7 +80,7 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote, // Query owlracle for gas ethGasFee, err := c.lookupGas(chain.OwlracleName) if err != nil { - return model.Quote{}, common.StringError(err) + return model.Quote{}, libcommon.StringError(err) } // Convert it from gwei to eth to USD and apply buffer @@ -92,7 +95,7 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote, // Also for buying tokens directly tokenCost, err := c.LookupUSD(p.TokenName, costToken) if err != nil { - return model.Quote{}, common.StringError(err) + return model.Quote{}, libcommon.StringError(err) } if p.UseBuffer { tokenCost *= 1.0 + common.TokenBuffer(p.TokenName) @@ -145,18 +148,18 @@ func (c cost) getExternalAPICallInterval(rateLimitPerMinute float64, uniqueEntri func (c cost) LookupUSD(coin string, quantity float64) (float64, error) { cacheName := "usd_value_" + coin cacheObject, err := store.GetObjectFromCache[CostCache](c.redis, cacheName) - if err != nil && errors.Cause(err).Error() != "redis: nil" { - return 0.0, common.StringError(err) + if err != nil && serror.IsError(err, serror.NOT_FOUND) { + return 0.0, libcommon.StringError(err) } if cacheObject == (CostCache{}) || (err == nil && time.Now().Unix()-cacheObject.Timestamp > c.getExternalAPICallInterval(10, 6)) { cacheObject.Timestamp = time.Now().Unix() cacheObject.Value, err = c.coingeckoUSD(coin, 1) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } err = store.PutObjectInCache(c.redis, cacheName, cacheObject) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } } @@ -167,17 +170,17 @@ func (c cost) lookupGas(network string) (float64, error) { cacheName := "gas_price_" + network cacheObject, err := store.GetObjectFromCache[CostCache](c.redis, cacheName) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } if cacheObject == (CostCache{}) || time.Now().Unix()-cacheObject.Timestamp > c.getExternalAPICallInterval(1.6, 6) { cacheObject.Timestamp = time.Now().Unix() cacheObject.Value, err = c.owlracle(network) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } err = store.PutObjectInCache(c.redis, cacheName, cacheObject) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } } @@ -189,7 +192,7 @@ func (c cost) coingeckoUSD(coin string, quantity float64) (float64, error) { var res map[string]interface{} err := common.GetJsonGeneric(requestURL, &res) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } prices, found := res[coin] if found { @@ -199,7 +202,7 @@ func (c cost) coingeckoUSD(coin string, quantity float64) (float64, error) { return usd.(float64), nil } } - // return 0, common.StringError(errors.New("Price not found for " + coin)) + // return 0, libcommon.StringError(errors.New("Price not found for " + coin)) // fmt.Printf("\n\nPRICE LOOKUP %+v", coin) // TODO: this is getting hit somewhere, figure out why return 0, nil @@ -214,7 +217,7 @@ func (c cost) owlracle(network string) (float64, error) { var res OwlracleJSON err := common.GetJsonGeneric(requestURL, &res) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } if len(res.Speeds) > 0 { return res.Speeds[0].MaxFeePerGas, nil diff --git a/pkg/service/device.go b/pkg/service/device.go index 68552c50..e05c953e 100644 --- a/pkg/service/device.go +++ b/pkg/service/device.go @@ -1,22 +1,27 @@ package service import ( + "context" "os" "time" + libcommon "github.com/String-xyz/go-lib/common" + serror "github.com/String-xyz/go-lib/stringerror" "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/lib/pq" "github.com/pkg/errors" ) type Device interface { - VerifyDevice(encrypted string) error - UpsertDeviceIP(deviceId string, Ip string) (err error) + VerifyDevice(ctx context.Context, encrypted string) error + UpsertDeviceIP(ctx context.Context, deviceId string, Ip string) (err error) + InvalidateUnknownDevice(ctx context.Context, device model.Device) error CreateDeviceIfNeeded(userId, visitorId, requestId string) (model.Device, error) CreateUnknownDevice(userId string) (model.Device, error) - InvalidateUnknownDevice(device model.Device) error } type device struct { @@ -28,23 +33,23 @@ func NewDevice(repos repository.Repositories, f Fingerprint) Device { return &device{repos, f} } -func (d device) VerifyDevice(encrypted string) error { +func (d device) VerifyDevice(ctx context.Context, encrypted string) error { key := os.Getenv("STRING_ENCRYPTION_KEY") - received, err := common.Decrypt[DeviceVerification](encrypted, key) + received, err := libcommon.Decrypt[DeviceVerification](encrypted, key) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } now := time.Now() if now.Unix()-received.Timestamp > (60 * 15) { - return common.StringError(errors.New("link expired")) + return libcommon.StringError(errors.New("link expired")) } - err = d.repos.Device.Update(received.DeviceId, model.DeviceUpdates{ValidatedAt: &now}) + err = d.repos.Device.Update(ctx, received.DeviceId, model.DeviceUpdates{ValidatedAt: &now}) return err } -func (d device) UpsertDeviceIP(deviceId string, ip string) (err error) { - device, err := d.repos.Device.GetById(deviceId) +func (d device) UpsertDeviceIP(ctx context.Context, deviceId string, ip string) (err error) { + device, err := d.repos.Device.GetById(ctx, deviceId) if err != nil { return } @@ -52,7 +57,7 @@ func (d device) UpsertDeviceIP(deviceId string, ip string) (err error) { if !contains { ipAddresses := append(device.IpAddresses, ip) updates := &model.DeviceUpdates{IpAddresses: &ipAddresses} - err = d.repos.Device.Update(deviceId, updates) + err = d.repos.Device.Update(ctx, deviceId, updates) if err != nil { return } @@ -65,7 +70,7 @@ func (d device) CreateDeviceIfNeeded(userId, visitorId, requestId string) (model /* fingerprint is not available, create an unknown device. It should be invalidated on every login */ device, err := d.getOrCreateUnknownDevice(userId, "unknown") if err != nil { - return device, common.StringError(err) + return device, libcommon.StringError(err) } if !isDeviceValidated(device) { @@ -73,7 +78,7 @@ func (d device) CreateDeviceIfNeeded(userId, visitorId, requestId string) (model return device, nil } - return device, common.StringError(err) + return device, libcommon.StringError(err) } else { /* device recognized, create or get the device */ device, err := d.repos.Device.GetByUserIdAndFingerprint(userId, visitorId) @@ -82,16 +87,16 @@ func (d device) CreateDeviceIfNeeded(userId, visitorId, requestId string) (model } /* create device only if the error is not found */ - if err == repository.ErrNotFound { + if serror.IsError(err, serror.NOT_FOUND) { visitor, fpErr := d.fingerprint.GetVisitor(visitorId, requestId) if fpErr != nil { - return model.Device{}, common.StringError(fpErr) + return model.Device{}, libcommon.StringError(fpErr) } device, dErr := d.createDevice(userId, visitor, "a new device "+visitor.UserAgent+" ") return device, dErr } - return device, common.StringError(err) + return device, libcommon.StringError(err) } } @@ -102,16 +107,16 @@ func (d device) CreateUnknownDevice(userId string) (model.Device, error) { UserAgent: "unknown", } device, err := d.createDevice(userId, visitor, "an unknown device") - return device, common.StringError(err) + return device, libcommon.StringError(err) } -func (d device) InvalidateUnknownDevice(device model.Device) error { +func (d device) InvalidateUnknownDevice(ctx context.Context, device model.Device) error { if device.Fingerprint != "unknown" { return nil // only unknown devices can be invalidated } device.ValidatedAt = &time.Time{} // Zero time to set it to nil - return d.repos.Device.Update(device.Id, device) + return d.repos.Device.Update(ctx, device.Id, device) } func (d device) createDevice(userId string, visitor FPVisitor, description string) (model.Device, error) { @@ -134,8 +139,8 @@ func (d device) getOrCreateUnknownDevice(userId, visitorId string) (model.Device var device model.Device device, err := d.repos.Device.GetByUserIdAndFingerprint(userId, "unknown") - if err != nil && err != repository.ErrNotFound { - return device, common.StringError(err) + if err != nil && !serror.IsError(err, serror.NOT_FOUND) { + return device, libcommon.StringError(err) } if device.Id != "" { @@ -144,7 +149,7 @@ func (d device) getOrCreateUnknownDevice(userId, visitorId string) (model.Device // if device is not found, create a new one device, err = d.CreateUnknownDevice(userId) - return device, common.StringError(err) + return device, libcommon.StringError(err) } func isDeviceValidated(device model.Device) bool { diff --git a/pkg/service/executor.go b/pkg/service/executor.go index 8881eaad..3e61d219 100644 --- a/pkg/service/executor.go +++ b/pkg/service/executor.go @@ -8,8 +8,9 @@ import ( "math/big" "os" - stringCommon "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/ethereum/go-ethereum/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/string-api/pkg/internal/common" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -56,12 +57,12 @@ func (e *executor) Initialize(RPC string) error { var err error e.client, err = w3.Dial(RPC) if err != nil { - return stringCommon.StringError(err) + return libcommon.StringError(err) } // Do it again for our low-level client e.geth, err = ethclient.Dial(RPC) if err != nil { - return stringCommon.StringError(err) + return libcommon.StringError(err) } return nil } @@ -69,7 +70,7 @@ func (e *executor) Initialize(RPC string) error { func (e *executor) Close() error { err := e.client.Close() if err != nil { - return stringCommon.StringError(err) + return libcommon.StringError(err) } e.geth.Close() return nil @@ -77,13 +78,13 @@ func (e *executor) Close() error { func (e executor) Estimate(call ContractCall) (CallEstimate, error) { // Get private key - skStr, err := stringCommon.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) + skStr, err := common.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } - sk, err := crypto.ToECDSA(common.FromHex(skStr)) + sk, err := crypto.ToECDSA(ethcommon.FromHex(skStr)) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } // TODO: avoid panicking so that we get an intelligible error message to := w3.A(call.CxAddr) @@ -91,7 +92,7 @@ func (e executor) Estimate(call ContractCall) (CallEstimate, error) { // Get public key publicKeyECDSA, ok := sk.Public().(*ecdsa.PublicKey) if !ok { - return CallEstimate{}, stringCommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) + return CallEstimate{}, libcommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) } sender := crypto.PubkeyToAddress(*publicKeyECDSA) @@ -99,14 +100,14 @@ func (e executor) Estimate(call ContractCall) (CallEstimate, error) { var chainId64 uint64 err = e.client.Call(eth.ChainID().Returns(&chainId64)) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } // Get sender nonce var nonce uint64 err = e.client.Call(eth.Nonce(sender, nil).Returns(&nonce)) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } // Get dynamic fee tx gas params @@ -116,13 +117,13 @@ func (e executor) Estimate(call ContractCall) (CallEstimate, error) { // Get handle to function we wish to call funcEVM, err := w3.NewFunc(call.CxFunc, call.CxReturn) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } // Encode function parameters - data, err := stringCommon.ParseEncoding(funcEVM, call.CxFunc, call.CxParams) + data, err := common.ParseEncoding(funcEVM, call.CxFunc, call.CxParams) if err != nil { - return CallEstimate{}, stringCommon.StringError(err) + return CallEstimate{}, libcommon.StringError(err) } // Generate blockchain message @@ -140,20 +141,20 @@ func (e executor) Estimate(call ContractCall) (CallEstimate, error) { err = e.client.Call(eth.EstimateGas(&msg, nil).Returns(&estimatedGas)) if err != nil { // Execution Will Revert! - return CallEstimate{Value: *value, Gas: estimatedGas, Success: false}, stringCommon.StringError(err) + return CallEstimate{Value: *value, Gas: estimatedGas, Success: false}, libcommon.StringError(err) } return CallEstimate{Value: *value, Gas: estimatedGas, Success: true}, nil } func (e executor) Initiate(call ContractCall) (string, *big.Int, error) { // Get private key - skStr, err := stringCommon.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) + skStr, err := common.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } - sk, err := crypto.ToECDSA(common.FromHex(skStr)) + sk, err := crypto.ToECDSA(ethcommon.FromHex(skStr)) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } // TODO: avoid panicking so that we get an intelligible error message to := w3.A(call.CxAddr) @@ -161,7 +162,7 @@ func (e executor) Initiate(call ContractCall) (string, *big.Int, error) { // Get public key publicKeyECDSA, ok := sk.Public().(*ecdsa.PublicKey) if !ok { - return "", nil, stringCommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) + return "", nil, libcommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) } sender := crypto.PubkeyToAddress(*publicKeyECDSA) @@ -172,14 +173,14 @@ func (e executor) Initiate(call ContractCall) (string, *big.Int, error) { var chainId64 uint64 err = e.client.Call(eth.ChainID().Returns(&chainId64)) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } // Get sender nonce var nonce uint64 err = e.client.Call(eth.Nonce(sender, nil).Returns(&nonce)) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } // Get dynamic fee tx gas params @@ -189,13 +190,13 @@ func (e executor) Initiate(call ContractCall) (string, *big.Int, error) { // Get handle to function we wish to call funcEVM, err := w3.NewFunc(call.CxFunc, call.CxReturn) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } // Encode function parameters - data, err := stringCommon.ParseEncoding(funcEVM, call.CxFunc, call.CxParams) + data, err := common.ParseEncoding(funcEVM, call.CxFunc, call.CxParams) if err != nil { - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } // Type conversion for chainId @@ -219,23 +220,23 @@ func (e executor) Initiate(call ContractCall) (string, *big.Int, error) { tx := types.MustSignNewTx(sk, signer, &dynamicFeeTx) // Call tx and retrieve hash - var hash common.Hash + var hash ethcommon.Hash err = e.client.Call(eth.SendTx(tx).Returns(&hash)) if err != nil { // Execution failed! - return "", nil, stringCommon.StringError(err) + return "", nil, libcommon.StringError(err) } return hash.String(), value, nil } func (e executor) TxWait(txId string) (uint64, error) { - txHash := common.HexToHash(txId) + txHash := ethcommon.HexToHash(txId) receipt := types.Receipt{} for receipt.Status == 0 { pendingReceipt, err := e.geth.TransactionReceipt(context.Background(), txHash) // TransactionReceipt returns error "not found" while tx is pending if err != nil && err.Error() != "not found" { - return 0, stringCommon.StringError(err) + return 0, libcommon.StringError(err) } if pendingReceipt != nil { receipt = *pendingReceipt @@ -250,32 +251,32 @@ func (e executor) GetByChainId() (uint64, error) { var chainId64 uint64 err := e.client.Call(eth.ChainID().Returns(&chainId64)) if err != nil { - return 0, stringCommon.StringError(err) + return 0, libcommon.StringError(err) } return chainId64, nil } func (e executor) GetBalance() (float64, error) { // Get private key - skStr, err := stringCommon.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) + skStr, err := common.DecryptBlobFromKMS(os.Getenv("EVM_PRIVATE_KEY")) if err != nil { - return 0, stringCommon.StringError(err) + return 0, libcommon.StringError(err) } - sk, err := crypto.ToECDSA(common.FromHex(skStr)) + sk, err := crypto.ToECDSA(ethcommon.FromHex(skStr)) if err != nil { - return 0, stringCommon.StringError(err) + return 0, libcommon.StringError(err) } // Get public key publicKeyECDSA, ok := sk.Public().(*ecdsa.PublicKey) if !ok { - return 0, stringCommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) + return 0, libcommon.StringError(errors.New("Estimate: Error casting public key to ECDSA")) } account := crypto.PubkeyToAddress(*publicKeyECDSA) wei := big.Int{} err = e.client.Call(eth.Balance(account, nil).Returns(&wei)) if err != nil { - return 0, stringCommon.StringError(err) + return 0, libcommon.StringError(err) } fwei := new(big.Float) fwei.SetString(wei.String()) diff --git a/pkg/service/fingerprint.go b/pkg/service/fingerprint.go index 4171cb5a..b598020b 100644 --- a/pkg/service/fingerprint.go +++ b/pkg/service/fingerprint.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" + libcommon "github.com/String-xyz/go-lib/common" "github.com/String-xyz/string-api/pkg/internal/common" ) @@ -45,7 +46,7 @@ func NewFingerprint(client FPClient) Fingerprint { func (f fingerprint) GetVisitor(id, requestId string) (FPVisitor, error) { visitor, err := f.client.GetVisitorById(id, common.FPVisitorOpts{Limit: 1, RequestId: requestId}) if err != nil { - return FPVisitor{}, common.StringError(err) + return FPVisitor{}, libcommon.StringError(err) } return f.hydrateVisitor(visitor) } @@ -55,7 +56,7 @@ func (f fingerprint) hydrateVisitor(visitor common.FPVisitor) (FPVisitor, error) // of the user, if we at some point want to return all the visit, we will need to create a different // hydration method. if len(visitor.Visits) == 0 || len(visitor.Visits) > 1 { - return FPVisitor{}, common.StringError(errors.New("visitor history does not match")) + return FPVisitor{}, libcommon.StringError(errors.New("visitor history does not match")) } var state string diff --git a/pkg/service/geofencing.go b/pkg/service/geofencing.go index d6a31f7a..7516b187 100644 --- a/pkg/service/geofencing.go +++ b/pkg/service/geofencing.go @@ -6,8 +6,8 @@ import ( "net/http" "os" - "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/String-xyz/string-api/pkg/store" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" "github.com/pkg/errors" ) @@ -20,10 +20,10 @@ type Geofencing interface { } type geofencing struct { - redis store.RedisStore + redis database.RedisStore } -func NewGeofencing(redis store.RedisStore) Geofencing { +func NewGeofencing(redis database.RedisStore) Geofencing { return &geofencing{redis} } @@ -41,12 +41,12 @@ func (g geofencing) IsAllowed(ip string) (bool, error) { // if err != nil { // location, err = getLocationFromAPI(ip) // if err != nil { - // return false, common.StringError(err) + // return false, libcommon.StringError(err) // } // err = g.setLocation(ip, location) // if err != nil { - // return false, common.StringError(err) + // return false, libcommon.StringError(err) // } // } @@ -57,12 +57,12 @@ func (g geofencing) IsAllowed(ip string) (bool, error) { func (c geofencing) setLocation(ip string, location GeoLocation) error { locationStr, err := json.Marshal(location) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } err = c.redis.Set("location-ip"+ip, locationStr, A_DAY_IN_NANOSEC) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } return nil } @@ -70,17 +70,17 @@ func (c geofencing) setLocation(ip string, location GeoLocation) error { func (g geofencing) getLocation(ip string) (GeoLocation, error) { cachedData, err := g.redis.Get("location-ip" + ip) if err != nil { - return GeoLocation{}, common.StringError(err) + return GeoLocation{}, libcommon.StringError(err) } location := GeoLocation{} if cachedData == nil { - return location, common.StringError(err) + return location, libcommon.StringError(err) } err = json.Unmarshal(cachedData, &location) if err != nil { - return location, common.StringError(err) + return location, libcommon.StringError(err) } return location, nil @@ -91,14 +91,14 @@ func getLocationFromAPI(ip string) (GeoLocation, error) { res, err := http.Get(url) if err != nil { - return GeoLocation{}, common.StringError(err) + return GeoLocation{}, libcommon.StringError(err) } // read the response body body, err := io.ReadAll(res.Body) if err != nil { - return GeoLocation{}, common.StringError(err) + return GeoLocation{}, libcommon.StringError(err) } dataObj := GeoLocation{} @@ -106,11 +106,11 @@ func getLocationFromAPI(ip string) (GeoLocation, error) { // unmarshal the json into our struct err = json.Unmarshal(body, &dataObj) if err != nil { - return GeoLocation{}, common.StringError(err) + return GeoLocation{}, libcommon.StringError(err) } if dataObj.Ip != ip || dataObj.CountryCode == "" || dataObj.RegionCode == "" { - return GeoLocation{}, common.StringError(errors.New("The Data returned by the external location service is invalid")) + return GeoLocation{}, libcommon.StringError(errors.New("The Data returned by the external location service is invalid")) } return dataObj, nil diff --git a/pkg/service/platform.go b/pkg/service/platform.go index 4d1c2408..c4b6024e 100644 --- a/pkg/service/platform.go +++ b/pkg/service/platform.go @@ -1,6 +1,7 @@ package service import ( + libcommon "github.com/String-xyz/go-lib/common" "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" @@ -27,13 +28,13 @@ func (a platform) Create(c CreatePlatform) (model.Platform, error) { plat, err := a.repos.Platform.Create(m) if err != nil { - return model.Platform{}, common.StringError(err) + return model.Platform{}, libcommon.StringError(err) } _, err = a.repos.Auth.CreateAPIKey(plat.Id, c.Authentication, hashed, false) pt := &plat if err != nil { - return *pt, common.StringError(err) + return *pt, libcommon.StringError(err) } return plat, nil diff --git a/pkg/service/sms.go b/pkg/service/sms.go index f730a9f5..a12057ab 100644 --- a/pkg/service/sms.go +++ b/pkg/service/sms.go @@ -4,7 +4,7 @@ import ( "os" "strings" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/pkg/errors" "github.com/twilio/twilio-go" twilioApi "github.com/twilio/twilio-go/rest/api/v2010" @@ -30,7 +30,7 @@ func SendSMS(message string, recipients []string) error { } } if errs != nil { - return common.StringError(errs) + return libcommon.StringError(errs) } return nil } @@ -40,7 +40,7 @@ func MessageStaff(message string) error { recipients := strings.Split(devNumbers, ",") err := SendSMS(message, recipients) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } return nil } diff --git a/pkg/service/transaction.go b/pkg/service/transaction.go index e98fa751..e1ffcb57 100644 --- a/pkg/service/transaction.go +++ b/pkg/service/transaction.go @@ -1,6 +1,7 @@ package service import ( + "context" "encoding/json" "fmt" "math" @@ -9,10 +10,13 @@ import ( "strings" "time" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + "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/String-xyz/string-api/pkg/store" + repository "github.com/String-xyz/string-api/pkg/repository" "github.com/checkout/checkout-sdk-go/payments" "github.com/lib/pq" "github.com/pkg/errors" @@ -20,8 +24,8 @@ import ( ) type Transaction interface { - Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) - Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error) + Quote(ctx context.Context, d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) + Execute(ctx context.Context, e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error) } type TransactionRepos struct { @@ -45,12 +49,12 @@ type InternalIds struct { type transaction struct { repos repository.Repositories - redis store.RedisStore + redis database.RedisStore ids InternalIds unit21 Unit21 } -func NewTransaction(repos repository.Repositories, redis store.RedisStore, unit21 Unit21) Transaction { +func NewTransaction(repos repository.Repositories, redis database.RedisStore, unit21 Unit21) Transaction { return &transaction{repos: repos, redis: redis, unit21: unit21} } @@ -74,23 +78,23 @@ type transactionProcessingData struct { trueGas *uint64 } -func (t transaction) Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) { +func (t transaction) Quote(ctx context.Context, d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) { // TODO: use prefab service to parse d and fill out known params res := model.PrecisionSafeExecutionRequest{TransactionRequest: d} // chain, err := model.ChainInfo(uint64(d.ChainId)) - chain, err := ChainInfo(uint64(d.ChainId), t.repos.Network, t.repos.Asset) + chain, err := ChainInfo(ctx, uint64(d.ChainId), t.repos.Network, t.repos.Asset) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } executor := NewExecutor() err = executor.Initialize(chain.RPC) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } estimateUSD, _, err := t.testTransaction(executor, d, chain, true) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } res.PrecisionSafeQuote = common.QuoteToPrecise(estimateUSD) executor.Close() @@ -98,72 +102,73 @@ func (t transaction) Quote(d model.TransactionRequest) (model.PrecisionSafeExecu // Sign entire payload bytes, err := json.Marshal(res) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } signature, err := common.EVMSign(bytes, true) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } res.Signature = signature return res, nil } -func (t transaction) Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) { +func (t transaction) Execute(ctx context.Context, e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) { t.getStringInstrumentsAndUserId() p := transactionProcessingData{precisionSafeExecutionRequest: &e, executionRequest: &model.ExecutionRequest{}, userId: &userId, deviceId: &deviceId, ip: &ip} // Pre-flight transaction setup - p, err = t.transactionSetup(p) + p, err = t.transactionSetup(ctx, p) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } // Run safety checks - p, err = t.safetyCheck(p) + p, err = t.safetyCheck(ctx, p) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } // Send request to the blockchain and update model status, hash, transaction amount - p, err = t.initiateTransaction(p) + p, err = t.initiateTransaction(ctx, p) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } // this Executor will not exist in scope of postProcess (*p.executor).Close() - // Send required information to new thread and return txId to the endpoint - go t.postProcess(p) + // Send required information to new thread and return txId to the endpoint. Create a new context since this will run in background + ctx2 := context.Background() + go t.postProcess(ctx2, p) return model.TransactionReceipt{TxId: *p.txId, TxURL: p.chain.Explorer + "/tx/" + *p.txId}, nil } -func (t transaction) transactionSetup(p transactionProcessingData) (transactionProcessingData, error) { +func (t transaction) transactionSetup(ctx context.Context, p transactionProcessingData) (transactionProcessingData, error) { // get user object - user, err := t.repos.User.GetById(*p.userId) + user, err := t.repos.User.GetById(ctx, *p.userId) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } email, err := t.repos.Contact.GetByUserIdAndType(user.Id, "email") if err != nil && errors.Cause(err).Error() != "not found" { - return p, common.StringError(err) + return p, libcommon.StringError(err) } user.Email = email.Data p.user = &user // Pull chain info needed for execution from repository - chain, err := ChainInfo(p.precisionSafeExecutionRequest.ChainId, t.repos.Network, t.repos.Asset) + chain, err := ChainInfo(ctx, p.precisionSafeExecutionRequest.ChainId, t.repos.Network, t.repos.Asset) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } p.chain = &chain // Create new Tx in repository, populate it with known info transactionModel, err := t.repos.Transaction.Create(model.Transaction{Status: "Created", NetworkId: chain.UUID, DeviceId: *p.deviceId, IPAddress: *p.ip, PlatformId: t.ids.StringPlatformId}) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } p.transactionModel = &transactionModel @@ -171,12 +176,12 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP processingFeeAsset, err := t.populateInitialTxModelData(*p.precisionSafeExecutionRequest, updateDB) p.processingFeeAsset = &processingFeeAsset if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.repos.Transaction.Update(transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, transactionModel.Id, updateDB) if err != nil { log.Err(err).Send() - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Dial the RPC and update model status @@ -184,36 +189,36 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP p.executor = &executor err = executor.Initialize(chain.RPC) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.updateTransactionStatus("RPC Dialed", transactionModel.Id) + err = t.updateTransactionStatus(ctx, "RPC Dialed", transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } return p, err } -func (t transaction) safetyCheck(p transactionProcessingData) (transactionProcessingData, error) { +func (t transaction) safetyCheck(ctx context.Context, p transactionProcessingData) (transactionProcessingData, error) { // Test the Tx and update model status estimateUSD, estimateETH, err := t.testTransaction(*p.executor, p.precisionSafeExecutionRequest.TransactionRequest, *p.chain, false) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.updateTransactionStatus("Tested and Estimated", p.transactionModel.Id) + err = t.updateTransactionStatus(ctx, "Tested and Estimated", p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Verify the Quote and update model status _, err = verifyQuote(*p.precisionSafeExecutionRequest, estimateUSD) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.updateTransactionStatus("Quote Verified", p.transactionModel.Id) + err = t.updateTransactionStatus(ctx, "Quote Verified", p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } *p.executionRequest = common.ExecutionRequestToImprecise(*p.precisionSafeExecutionRequest) @@ -221,28 +226,28 @@ func (t transaction) safetyCheck(p transactionProcessingData) (transactionProces preBalance, err := (*p.executor).GetBalance() p.preBalance = &preBalance if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } if preBalance < estimateETH { msg := fmt.Sprintf("STRING-API: %s balance is too low to execute %.2f transaction at %.2f", p.chain.OwlracleName, estimateETH, preBalance) MessageStaff(msg) - return p, common.StringError(errors.New("hot wallet ETH balance too low")) + return p, libcommon.StringError(errors.New("hot wallet ETH balance too low")) } // Authorize quoted cost on end-user CC and update model status - p, err = t.authCard(p) + p, err = t.authCard(ctx, p) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Validate Transaction through Real Time Rules engine - txModel, err := t.repos.Transaction.GetById(p.transactionModel.Id) + txModel, err := t.repos.Transaction.GetById(ctx, p.transactionModel.Id) if err != nil { log.Err(err).Msg("error getting tx model in unit21 Tx Evalute") - return p, common.StringError(err) + return p, libcommon.StringError(err) } - evaluation, err := t.unit21.Transaction.Evaluate(txModel) + evaluation, err := t.unit21.Transaction.Evaluate(ctx, txModel) if err != nil { // If Unit21 Evaluate fails, just log, but otherwise continue with the transaction @@ -251,28 +256,28 @@ func (t transaction) safetyCheck(p transactionProcessingData) (transactionProces } if !evaluation { - err = t.updateTransactionStatus("Failed", p.transactionModel.Id) + err = t.updateTransactionStatus(ctx, "Failed", p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.unit21CreateTransaction(p.transactionModel.Id) + err = t.unit21CreateTransaction(ctx, p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - return p, common.StringError(errors.New("risk: Transaction Failed Unit21 Real Time Rules Evaluation")) + return p, libcommon.StringError(errors.New("risk: Transaction Failed Unit21 Real Time Rules Evaluation")) } - err = t.updateTransactionStatus("Unit21 Authorized", p.transactionModel.Id) + err = t.updateTransactionStatus(ctx, "Unit21 Authorized", p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } return p, nil } -func (t transaction) initiateTransaction(p transactionProcessingData) (transactionProcessingData, error) { +func (t transaction) initiateTransaction(ctx context.Context, p transactionProcessingData) (transactionProcessingData, error) { call := ContractCall{ CxAddr: p.executionRequest.CxAddr, CxFunc: p.executionRequest.CxFunc, @@ -285,7 +290,7 @@ func (t transaction) initiateTransaction(p transactionProcessingData) (transacti txId, value, err := (*p.executor).Initiate(call) p.cumulativeValue = value if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } p.txId = &txId @@ -303,26 +308,26 @@ func (t transaction) initiateTransaction(p transactionProcessingData) (transacti } responseLeg, err = t.repos.TxLeg.Create(responseLeg) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } txLeg := model.TransactionUpdates{ResponseTxLegId: &responseLeg.Id} - err = t.repos.Transaction.Update(p.transactionModel.Id, txLeg) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, txLeg) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } status := "Transaction Initiated" txAmount := p.cumulativeValue.String() updateDB := &model.TransactionUpdates{Status: &status, TransactionHash: p.txId, TransactionAmount: &txAmount} - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } return p, nil } -func (t transaction) postProcess(p transactionProcessingData) { +func (t transaction) postProcess(ctx context.Context, p transactionProcessingData) { // Reinitialize Executor executor := NewExecutor() p.executor = &executor @@ -336,7 +341,7 @@ func (t transaction) postProcess(p transactionProcessingData) { updateDB := model.TransactionUpdates{} status := "Post Process RPC Dialed" updateDB.Status = &status - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { log.Err(err).Msg("Failed to update transaction repo with status 'Post Process RPC Dialed'") // TODO: Handle error instead of returning it @@ -355,7 +360,7 @@ func (t transaction) postProcess(p transactionProcessingData) { updateDB.Status = &status networkFee := strconv.FormatUint(trueGas, 10) updateDB.NetworkFee = &networkFee // geth uses uint64 for gas - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { log.Err(err).Msg("Failed to update transaction repo with status 'Tx Confirmed'") // TODO: Handle error instead of returning it @@ -386,7 +391,7 @@ func (t transaction) postProcess(p transactionProcessingData) { // compute profit // TODO: factor request.processingFeeAsset in the event of crypto-to-usd - profit, err := t.tenderTransaction(p) + profit, err := t.tenderTransaction(ctx, p) if err != nil { log.Err(err).Msg("Failed to tender transaction") // TODO: Handle error instead of returning it @@ -399,14 +404,14 @@ func (t transaction) postProcess(p transactionProcessingData) { updateDB.ProcessingFee = &processingFee status = "Profit Tendered" updateDB.Status = &status - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { log.Err(err).Msg("Failed to update transaction repo with status 'Profit Tendered'") // TODO: Handle error instead of returning it } // charge the users CC - err = t.chargeCard(p) + err = t.chargeCard(ctx, p) if err != nil { log.Err(err).Msg("failed to charge card") // TODO: Handle error instead of returning it @@ -417,7 +422,7 @@ func (t transaction) postProcess(p transactionProcessingData) { updateDB.Status = &status // TODO: Figure out how much we paid the CC payment processor and deduct it // and use it to populate processing_fee and processing_fee_asset in the table - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { log.Err(err).Msg("Failed to update transaction repo with status 'Card Charged'") // TODO: Handle error instead of returning it @@ -426,19 +431,19 @@ func (t transaction) postProcess(p transactionProcessingData) { // Transaction complete! Update status status = "Completed" updateDB.Status = &status - err = t.repos.Transaction.Update(p.transactionModel.Id, updateDB) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, updateDB) if err != nil { log.Err(err).Msg("Failed to update transaction repo with status 'Completed'") } // Create Transaction data in Unit21 - err = t.unit21CreateTransaction(p.transactionModel.Id) + err = t.unit21CreateTransaction(ctx, p.transactionModel.Id) if err != nil { log.Err(err).Msg("Error creating Unit21 transaction") } // send email receipt - err = t.sendEmailReceipt(p) + err = t.sendEmailReceipt(ctx, p) if err != nil { log.Err(err).Msg("Error sending email receipt to user") } @@ -460,7 +465,7 @@ func (t transaction) populateInitialTxModelData(e model.PrecisionSafeExecutionRe asset, err := t.repos.Asset.GetByName("USD") if err != nil { - return model.Asset{}, common.StringError(err) + return model.Asset{}, libcommon.StringError(err) } m.ProcessingFeeAsset = &asset.Id // Checkout processing asset return asset, nil @@ -480,7 +485,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio // Estimate value and gas of Tx request estimateEVM, err := executor.Estimate(call) if err != nil { - return res, 0, common.StringError(err) + return res, 0, libcommon.StringError(err) } // Calculate total eth estimate as float64 @@ -491,7 +496,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio chainId, err := executor.GetByChainId() if err != nil { - return res, eth, common.StringError(err) + return res, eth, libcommon.StringError(err) } cost := NewCost(t.redis) estimationParams := EstimationParams{ @@ -506,7 +511,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio // Estimate Cost in USD to execute Tx request estimateUSD, err := cost.EstimateTransaction(estimationParams, chain) if err != nil { - return res, eth, common.StringError(err) + return res, eth, libcommon.StringError(err) } res = estimateUSD return res, eth, nil @@ -519,35 +524,38 @@ func verifyQuote(e model.PrecisionSafeExecutionRequest, newEstimate model.Quote) dataToValidate.CardToken = "" bytesToValidate, err := json.Marshal(dataToValidate) if err != nil { - return false, common.StringError(err) + return false, libcommon.StringError(err) } valid, err := common.ValidateEVMSignature(e.Signature, bytesToValidate, true) if err != nil { - return false, common.StringError(err) + return false, libcommon.StringError(err) } if !valid { - return false, common.StringError(errors.New("verifyQuote: invalid signature")) + return false, libcommon.StringError(errors.New("verifyQuote: invalid signature")) } if newEstimate.Timestamp-e.Timestamp > 20 { - return false, common.StringError(errors.New("verifyQuote: quote expired")) + return false, libcommon.StringError(errors.New("verifyQuote: quote expired")) } quotedTotal, err := strconv.ParseFloat(e.TotalUSD, 64) if err != nil { - return false, common.StringError(err) + return false, libcommon.StringError(err) } if newEstimate.TotalUSD > quotedTotal { - return false, common.StringError(errors.New("verifyQuote: price too volatile")) + return false, libcommon.StringError(errors.New("verifyQuote: price too volatile")) } return true, nil } -func (t transaction) addCardInstrumentIdIfNew(p transactionProcessingData) (string, error) { +func (t transaction) addCardInstrumentIdIfNew(ctx context.Context, p transactionProcessingData) (string, error) { + // Create a new context since there are sub routines that run in background + ctx2 := context.Background() + instrument, err := t.repos.Instrument.GetCardByFingerprint(p.cardAuthorization.CheckoutFingerprint) if err != nil && !strings.Contains(err.Error(), "not found") { // because we are wrapping error and care about its value - return "", common.StringError(err) + return "", libcommon.StringError(err) } else if err == nil && instrument.UserId != "" { - go t.unit21.Instrument.Update(instrument) // if instrument already exists, update it anyways - return instrument.Id, nil // return if instrument already exists + go t.unit21.Instrument.Update(ctx2, 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 @@ -565,46 +573,49 @@ func (t transaction) addCardInstrumentIdIfNew(p transactionProcessingData) (stri } instrument, err = t.repos.Instrument.Create(instrument) if err != nil { - return "", common.StringError(err) + return "", libcommon.StringError(err) } - go t.unit21.Instrument.Create(instrument) + go t.unit21.Instrument.Create(ctx2, instrument) return instrument.Id, nil } -func (t transaction) addWalletInstrumentIdIfNew(address string, id string) (string, error) { +func (t transaction) addWalletInstrumentIdIfNew(ctx context.Context, address string, id string) (string, error) { + // Create a new context since this will run in background + ctx2 := context.Background() + instrument, err := t.repos.Instrument.GetWalletByAddr(address) if err != nil && !strings.Contains(err.Error(), "not found") { - return "", common.StringError(err) + return "", libcommon.StringError(err) } else if err == nil && instrument.PublicKey == address { - go t.unit21.Instrument.Update(instrument) // if instrument already exists, update it anyways - return instrument.Id, nil // return if instrument already exists + go t.unit21.Instrument.Update(ctx2, 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: "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) + return "", libcommon.StringError(err) } - go t.unit21.Instrument.Create(instrument) + go t.unit21.Instrument.Create(ctx2, instrument) return instrument.Id, nil } -func (t transaction) authCard(p transactionProcessingData) (transactionProcessingData, error) { +func (t transaction) authCard(ctx context.Context, p transactionProcessingData) (transactionProcessingData, error) { // auth their card p, err := AuthorizeCharge(p) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Add Checkout Instrument ID to our DB if it's not there already and associate it with the user - instrumentId, err := t.addCardInstrumentIdIfNew(p) + instrumentId, err := t.addCardInstrumentIdIfNew(ctx, p) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // Create Origin Tx leg @@ -619,23 +630,23 @@ func (t transaction) authCard(p transactionProcessingData) (transactionProcessin } origin, err = t.repos.TxLeg.Create(origin) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } txLegUpdates := model.TransactionUpdates{OriginTxLegId: &origin.Id} - err = t.repos.Transaction.Update(p.transactionModel.Id, txLegUpdates) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, txLegUpdates) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - err = t.updateTransactionStatus("Card "+p.cardAuthorization.Status, p.transactionModel.Id) + err = t.updateTransactionStatus(ctx, "Card "+p.cardAuthorization.Status, p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - recipientWalletId, err := t.addWalletInstrumentIdIfNew(p.executionRequest.UserAddress, *p.userId) + recipientWalletId, err := t.addWalletInstrumentIdIfNew(ctx, p.executionRequest.UserAddress, *p.userId) p.recipientWalletId = &recipientWalletId if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } // TODO: Determine the output of the transaction (destination leg) with Tracers @@ -650,23 +661,23 @@ func (t transaction) authCard(p transactionProcessingData) (transactionProcessin destinationLeg, err = t.repos.TxLeg.Create(destinationLeg) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } txLegUpdates = model.TransactionUpdates{DestinationTxLegId: &destinationLeg.Id} - err = t.repos.Transaction.Update(p.transactionModel.Id, txLegUpdates) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, txLegUpdates) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } if !p.cardAuthorization.Approved { - err := t.unit21CreateTransaction(p.transactionModel.Id) + err := t.unit21CreateTransaction(ctx, p.transactionModel.Id) if err != nil { - return p, common.StringError(err) + return p, libcommon.StringError(err) } - return p, common.StringError(errors.New("payment: Authorization Declined by Checkout")) + return p, libcommon.StringError(errors.New("payment: Authorization Declined by Checkout")) } return p, nil @@ -675,33 +686,33 @@ func (t transaction) authCard(p transactionProcessingData) (transactionProcessin func confirmTx(executor Executor, txId string) (uint64, error) { trueGas, err := executor.TxWait(txId) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } return trueGas, nil } // TODO: rewrite this transaction to reference the asset(s) received by the user, not what we paid -func (t transaction) tenderTransaction(p transactionProcessingData) (float64, error) { +func (t transaction) tenderTransaction(ctx context.Context, p transactionProcessingData) (float64, error) { cost := NewCost(t.redis) trueWei := big.NewInt(0).Add(p.cumulativeValue, big.NewInt(int64(*p.trueGas))) trueEth := common.WeiToEther(trueWei) trueUSD, err := cost.LookupUSD(p.chain.CoingeckoName, trueEth) if err != nil { - return 0, common.StringError(err) + return 0, libcommon.StringError(err) } profit := p.executionRequest.Quote.TotalUSD - trueUSD // Create Receive Tx leg - asset, err := t.repos.Asset.GetById(p.chain.GasTokenId) + asset, err := t.repos.Asset.GetById(ctx, p.chain.GasTokenId) if err != nil { - return profit, common.StringError(err) + return profit, libcommon.StringError(err) } wei := floatToFixedString(trueEth, int(asset.Decimals)) usd := floatToFixedString(p.executionRequest.Quote.TotalUSD, 6) - txModel, err := t.repos.Transaction.GetById(p.transactionModel.Id) + txModel, err := t.repos.Transaction.GetById(ctx, p.transactionModel.Id) if err != nil { - return profit, common.StringError(err) + return profit, libcommon.StringError(err) } now := time.Now() @@ -715,18 +726,18 @@ func (t transaction) tenderTransaction(p transactionProcessingData) (float64, er } // We now update the destination leg instead of creating it - err = t.repos.TxLeg.Update(txModel.DestinationTxLegId, destinationLeg) + err = t.repos.TxLeg.Update(ctx, txModel.DestinationTxLegId, destinationLeg) if err != nil { - return profit, common.StringError(err) + return profit, libcommon.StringError(err) } return profit, nil } -func (t transaction) chargeCard(p transactionProcessingData) error { +func (t transaction) chargeCard(ctx context.Context, p transactionProcessingData) error { p, err := CaptureCharge(p) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } // Create Receipt Tx leg @@ -741,27 +752,27 @@ func (t transaction) chargeCard(p transactionProcessingData) error { } receiptLeg, err = t.repos.TxLeg.Create(receiptLeg) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } txLeg := model.TransactionUpdates{ReceiptTxLegId: &receiptLeg.Id, PaymentCode: &p.cardCapture.Accepted.ActionID} - err = t.repos.Transaction.Update(p.transactionModel.Id, txLeg) + err = t.repos.Transaction.Update(ctx, p.transactionModel.Id, txLeg) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } return nil } -func (t transaction) sendEmailReceipt(p transactionProcessingData) error { - user, err := t.repos.User.GetById(*p.userId) +func (t transaction) sendEmailReceipt(ctx context.Context, p transactionProcessingData) error { + user, err := t.repos.User.GetById(ctx, *p.userId) if err != nil { log.Err(err).Msg("Error getting user from repo") - return common.StringError(err) + return libcommon.StringError(err) } - contact, err := t.repos.Contact.GetByUserId(user.Id) + contact, err := t.repos.Contact.GetByUserId(ctx, user.Id) if err != nil { log.Err(err).Msg("Error getting user contact from repo") - return common.StringError(err) + return libcommon.StringError(err) } name := user.FirstName // + " " + user.MiddleName + " " + user.LastName if name == "" { @@ -790,7 +801,7 @@ func (t transaction) sendEmailReceipt(p transactionProcessingData) error { err = common.EmailReceipt(contact.Data, receiptParams, receiptBody) if err != nil { log.Err(err).Msg("Error sending email receipt to user") - return common.StringError(err) + return libcommon.StringError(err) } return nil } @@ -799,27 +810,27 @@ func floatToFixedString(value float64, decimals int) string { return strconv.FormatUint(uint64(value*(math.Pow10(decimals))), 10) } -func (t transaction) unit21CreateTransaction(transactionId string) (err error) { - txModel, err := t.repos.Transaction.GetById(transactionId) +func (t transaction) unit21CreateTransaction(ctx context.Context, transactionId string) (err error) { + txModel, err := t.repos.Transaction.GetById(ctx, transactionId) if err != nil { log.Err(err).Msg("Error getting tx model in Unit21 in Tx Postprocess") - return common.StringError(err) + return libcommon.StringError(err) } - _, err = t.unit21.Transaction.Create(txModel) + _, err = t.unit21.Transaction.Create(ctx, txModel) if err != nil { log.Err(err).Msg("Error updating unit21 in Tx Postprocess") - return common.StringError(err) + return libcommon.StringError(err) } return nil } -func (t transaction) updateTransactionStatus(status string, transactionId string) (err error) { +func (t transaction) updateTransactionStatus(ctx context.Context, status string, transactionId string) (err error) { updateDB := &model.TransactionUpdates{Status: &status} - err = t.repos.Transaction.Update(transactionId, updateDB) + err = t.repos.Transaction.Update(ctx, transactionId, updateDB) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } return nil diff --git a/pkg/service/user.go b/pkg/service/user.go index 502b29be..28c02814 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -1,12 +1,15 @@ package service import ( + "context" "os" "time" + libcommon "github.com/String-xyz/go-lib/common" "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/pkg/errors" "github.com/rs/zerolog/log" ) @@ -21,16 +24,16 @@ type UserCreateResponse struct { type User interface { //GetStatus returns the onboarding status of an user - GetStatus(userId string) (model.UserOnboardingStatus, error) + GetStatus(ctx context.Context, userId string) (model.UserOnboardingStatus, error) // Create creates an user from a wallet signed payload // It associates the wallet to the user and also sets its status as verified // This payload usually comes from a previous requested one using (Auth.PayloadToSign) service - Create(request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) + Create(ctx context.Context, request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) //Update updates the user firstname lastname middlename. // It fetches the user using the walletAddress provided - Update(userId string, request UserUpdates) (model.User, error) + Update(ctx context.Context, userId string, request UserUpdates) (model.User, error) } type user struct { @@ -45,55 +48,55 @@ func NewUser(repos repository.Repositories, auth Auth, fprint Fingerprint, devic return &user{repos, auth, fprint, device, unit21} } -func (u user) GetStatus(userId string) (model.UserOnboardingStatus, error) { +func (u user) GetStatus(ctx context.Context, userId string) (model.UserOnboardingStatus, error) { res := model.UserOnboardingStatus{Status: "not found"} - user, err := u.repos.User.GetById(userId) + user, err := u.repos.User.GetById(ctx, userId) if err != nil { - return res, common.StringError(err) + return res, libcommon.StringError(err) } if user.Status != "" { res.Status = user.Status return res, nil } - return res, common.StringError(errors.New("not found")) + return res, libcommon.StringError(errors.New("not found")) } -func (u user) Create(request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) { +func (u user) Create(ctx context.Context, request model.WalletSignaturePayloadSigned) (UserCreateResponse, error) { resp := UserCreateResponse{} key := os.Getenv("STRING_ENCRYPTION_KEY") - payload, err := common.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) + payload, err := libcommon.Decrypt[model.WalletSignaturePayload](request.Nonce[len(walletAuthenticationPrefix):], key) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } addr := payload.Address if addr == "" { - return resp, common.StringError(errors.New("no wallet address provided")) + return resp, libcommon.StringError(errors.New("no wallet address provided")) } // Make sure wallet does not already exist exists, err := u.repos.Instrument.WalletAlreadyExists(addr) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } if exists { - return resp, common.StringError(errors.New("wallet already exists")) + return resp, libcommon.StringError(errors.New("wallet already exists")) } // Make sure address is a wallet and not a smart contract if !common.IsWallet(addr) { - return resp, common.StringError(errors.New("address provided is not a valid wallet")) + return resp, libcommon.StringError(errors.New("address provided is not a valid wallet")) } // Verify payload integrity if err := verifyWalletAuthentication(request); err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } - user, err := u.createUserData(addr) + user, err := u.createUserData(ctx, addr) if err != nil { return resp, err } @@ -101,13 +104,13 @@ func (u user) Create(request model.WalletSignaturePayloadSigned) (UserCreateResp // create device only if there is a visitor device, err := u.device.CreateDeviceIfNeeded(user.Id, request.Fingerprint.VisitorId, request.Fingerprint.RequestId) if err != nil && errors.Cause(err).Error() != "not found" { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } if device.Fingerprint != "" { // validate that device on user creation now := time.Now() - err = u.repos.Device.Update(device.Id, model.DeviceUpdates{ValidatedAt: &now}) + err = u.repos.Device.Update(ctx, device.Id, model.DeviceUpdates{ValidatedAt: &now}) if err == nil { log.Err(err).Msg("Failed to verify user device") } @@ -115,16 +118,18 @@ func (u user) Create(request model.WalletSignaturePayloadSigned) (UserCreateResp jwt, err := u.auth.GenerateJWT(user.Id, device) if err != nil { - return resp, common.StringError(err) + return resp, libcommon.StringError(err) } // deviceService.RegisterNewUserDevice() - go u.unit21.Entity.Create(user) + // Create a new context since this will run in background + ctx2 := context.Background() + go u.unit21.Entity.Create(ctx2, user) return UserCreateResponse{JWT: jwt, User: user}, nil } -func (u user) createUserData(addr string) (model.User, error) { +func (u user) createUserData(ctx context.Context, addr string) (model.User, error) { tx := u.repos.User.MustBegin() u.repos.Instrument.SetTx(tx) u.repos.Device.SetTx(tx) @@ -136,32 +141,36 @@ func (u user) createUserData(addr string) (model.User, error) { user, err := u.repos.User.Create(user) if err != nil { u.repos.User.Rollback() - return user, common.StringError(err) + return user, libcommon.StringError(err) } // Create a new wallet instrument and associate it with the new user instrument := model.Instrument{Type: "Crypto Wallet", Status: "verified", Network: "EVM", PublicKey: addr, UserId: user.Id} instrument, err = u.repos.Instrument.Create(instrument) if err != nil { u.repos.Instrument.Rollback() - return user, common.StringError(err) + return user, libcommon.StringError(err) } if err := u.repos.User.Commit(); err != nil { - return user, common.StringError(errors.New("error commiting transaction")) + return user, libcommon.StringError(errors.New("error commiting transaction")) } - go u.unit21.Instrument.Create(instrument) + // Create a new context since this will run in background + ctx2 := context.Background() + go u.unit21.Instrument.Create(ctx2, instrument) return user, nil } -func (u user) Update(userId string, request UserUpdates) (model.User, error) { +func (u user) Update(ctx context.Context, userId string, request UserUpdates) (model.User, error) { updates := model.UpdateUserName{FirstName: request.FirstName, MiddleName: request.MiddleName, LastName: request.LastName} - user, err := u.repos.User.Update(userId, updates) + user, err := u.repos.User.Update(ctx, userId, updates) if err != nil { - return user, common.StringError(err) + return user, libcommon.StringError(err) } - go u.unit21.Entity.Update(user) + // Create a new context since this will run in background + ctx2 := context.Background() + go u.unit21.Entity.Update(ctx2, user) return user, nil } diff --git a/pkg/service/verification.go b/pkg/service/verification.go index 65be5f73..36a4bb4b 100644 --- a/pkg/service/verification.go +++ b/pkg/service/verification.go @@ -1,12 +1,15 @@ package service import ( + "context" "fmt" "net/url" "os" "time" + libcommon "github.com/String-xyz/go-lib/common" "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/pkg/errors" @@ -29,10 +32,10 @@ type DeviceVerification struct { type Verification interface { // SendEmailVerification sends a link to the provided email for verification purpose, link expires in 15 minutes - SendEmailVerification(userId string, email string) error + SendEmailVerification(ctx context.Context, userId string, email string) error // VerifyEmail verifies the provided email and creates a contact - VerifyEmail(encrypted string) error + VerifyEmail(ctx context.Context, encrypted string) error SendDeviceVerification(userId, email string, deviceId string, deviceDescription string) error } @@ -46,26 +49,26 @@ func NewVerification(repos repository.Repositories, unit21 Unit21) Verification return &verification{repos, unit21} } -func (v verification) SendEmailVerification(userId, email string) error { +func (v verification) SendEmailVerification(ctx context.Context, userId, email string) error { if !validEmail(email) { - return common.StringError(errors.New("missing or invalid email")) + return libcommon.StringError(errors.New("missing or invalid email")) } - user, err := v.repos.User.GetById(userId) + user, err := v.repos.User.GetById(ctx, userId) if err != nil || user.Id != userId { - return common.StringError(errors.New("invalid user")) // JWT expiration will not be hit here + return libcommon.StringError(errors.New("invalid user")) // JWT expiration will not be hit here } contact, _ := v.repos.Contact.GetByData(email) if contact.Status == "validated" { - return common.StringError(errors.New("email already verified")) + return libcommon.StringError(errors.New("email already verified")) } // Encrypt required data to Base64 string and insert it in an email hyperlink key := os.Getenv("STRING_ENCRYPTION_KEY") - code, err := common.Encrypt(EmailVerification{Timestamp: time.Now().Unix(), Email: email, UserId: userId}, key) + code, err := libcommon.Encrypt(EmailVerification{Timestamp: time.Now().Unix(), Email: email, UserId: userId}, key) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } code = url.QueryEscape(code) // make sure special characters are browser friendly @@ -80,7 +83,7 @@ func (v verification) SendEmailVerification(userId, email string) error { client := sendgrid.NewSendClient(os.Getenv("SENDGRID_API_KEY")) _, err = client.Send(message) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } // Wait for up to 15 minutes, final timeout TBD now, lastPolled := time.Now().Unix(), time.Now().Unix() @@ -93,28 +96,28 @@ func (v verification) SendEmailVerification(userId, email string) error { lastPolled = now contact, err := v.repos.Contact.GetByData(email) if err != nil && errors.Cause(err).Error() != "not found" { - return common.StringError(err) + return libcommon.StringError(err) } else if err == nil && contact.Data == email { // success // update user status user, err := v.repos.User.UpdateStatus(userId, "email_verified") if err != nil { - return common.StringError(errors.New("User email verify error - userId: " + user.Id)) + return libcommon.StringError(errors.New("User email verify error - userId: " + user.Id)) } return nil } } // timed out - return common.StringError(errors.New("link expired")) + return libcommon.StringError(errors.New("link expired")) } func (v verification) SendDeviceVerification(userId, email, deviceId, deviceDescription string) error { log.Info().Str("email", email) key := os.Getenv("STRING_ENCRYPTION_KEY") - code, err := common.Encrypt(DeviceVerification{Timestamp: time.Now().Unix(), DeviceId: deviceId, UserId: userId}, key) + code, err := libcommon.Encrypt(DeviceVerification{Timestamp: time.Now().Unix(), DeviceId: deviceId, UserId: userId}, key) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } code = url.QueryEscape(code) @@ -134,36 +137,38 @@ func (v verification) SendDeviceVerification(userId, email, deviceId, deviceDesc _, err = client.Send(message) if err != nil { log.Err(err).Msg("error sending device validation") - return common.StringError(err) + return libcommon.StringError(err) } return nil } -func (v verification) VerifyEmail(encrypted string) error { +func (v verification) VerifyEmail(ctx context.Context, encrypted string) error { key := os.Getenv("STRING_ENCRYPTION_KEY") - received, err := common.Decrypt[EmailVerification](encrypted, key) + received, err := libcommon.Decrypt[EmailVerification](encrypted, key) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } // Wait for up to 15 minutes, final timeout TBD now := time.Now() if now.Unix()-received.Timestamp > (60 * 15) { - return common.StringError(errors.New("link expired")) + return libcommon.StringError(errors.New("link expired")) } contact := model.Contact{UserId: received.UserId, Type: "email", Status: "validated", Data: received.Email, ValidatedAt: &now} contact, err = v.repos.Contact.Create(contact) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } // update user status user, err := v.repos.User.UpdateStatus(received.UserId, "email_verified") if err != nil { - return common.StringError(errors.New("User email verify error - userId: " + user.Id)) + return libcommon.StringError(errors.New("User email verify error - userId: " + user.Id)) } - go v.unit21.Entity.Update(user) + // Create a new context since this will run in background + ctx2 := context.Background() + go v.unit21.Entity.Update(ctx2, user) return nil } diff --git a/pkg/store/pg.go b/pkg/store/pg.go index 98697e69..b00bcaca 100644 --- a/pkg/store/pg.go +++ b/pkg/store/pg.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" "github.com/jmoiron/sqlx" "github.com/lib/pq" sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql" @@ -25,7 +25,7 @@ func strConnection() string { var SSLMode string - if common.IsLocalEnv() { + if libcommon.IsLocalEnv() { SSLMode = "disable" } else { SSLMode = "require" diff --git a/pkg/store/redis.go b/pkg/store/redis.go index eb24569f..9d69de59 100644 --- a/pkg/store/redis.go +++ b/pkg/store/redis.go @@ -1,156 +1,18 @@ package store import ( - "context" - "crypto/tls" - "log" "os" - "time" - "github.com/String-xyz/string-api/pkg/internal/common" - "github.com/go-redis/redis/v8" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" ) -type RedisRepresentable interface { - Ping(ctx context.Context) *redis.StatusCmd - Get(ctx context.Context, key string) *redis.StringCmd - Del(ctx context.Context, keys ...string) *redis.IntCmd - Set(ctx context.Context, key string, value interface{}, duration time.Duration) *redis.StatusCmd - HSet(ctx context.Context, key string, values ...interface{}) *redis.IntCmd - HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd - HLen(ctx context.Context, key string) *redis.IntCmd - HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd -} - -type RedisStore interface { - Get(id string) ([]byte, error) - Set(string, any, time.Duration) error - HSet(string, map[string]interface{}) error - HGetAll(string) (map[string]string, error) - HDel(string, string) int64 - HMLen(string) int64 - Delete(string) error -} - -type redisStore struct { - client RedisRepresentable -} - -const REDIS_NOT_FOUND_ERROR = "redis: nil" - -func redisConf() *tls.Config { - var tlsCf *tls.Config - if !common.IsLocalEnv() { - tlsCf = &tls.Config{ - MinVersion: tls.VersionTLS12, - } - } - - return tlsCf -} - -func redisOptions() *redis.Options { - url := os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT") - var tlsCf *tls.Config - if !common.IsLocalEnv() { - tlsCf = &tls.Config{ - MinVersion: tls.VersionTLS12, - } - } - - op := &redis.Options{ - Addr: url, - TLSConfig: tlsCf, - Password: os.Getenv("REDIS_PASSWORD"), - DB: 0, - } - return op -} - -func cluster() *redis.ClusterClient { - url := os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT") - return redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: []string{url}, - Password: os.Getenv("REDIS_PASSWORD"), - PoolSize: 10, - MinIdleConns: 10, - TLSConfig: redisConf(), - ReadOnly: false, - RouteRandomly: false, - RouteByLatency: false, - }) -} - -func NewRedisStore() RedisStore { - ctx := context.Background() - var client RedisRepresentable - if common.IsLocalEnv() { - client = redis.NewClient(redisOptions()) - } else { - client = cluster() - } - _, err := client.Ping(ctx).Result() - if err != nil { - log.Fatalf("Failed to ping Redis: %v", err) - } - - return &redisStore{ - client: client, +func NewRedis() database.RedisStore { + opts := database.RedisConfigOptions{ + Host: os.Getenv("REDIS_HOST"), + Port: os.Getenv("REDIS_PORT"), + Password: os.Getenv("REDIS_PASSWORD"), + ClusterMode: !libcommon.IsLocalEnv(), } -} - -func (r redisStore) Delete(id string) error { - ctx := context.Background() - _, err := r.client.Del(ctx, id).Result() - if err != nil { - return common.StringError(err) - } - return nil -} - -func (r redisStore) Get(id string) ([]byte, error) { - ctx := context.Background() - bytes, err := r.client.Get(ctx, id).Bytes() - if err != nil { - return nil, common.StringError(err) - } - return bytes, nil -} - -func (r redisStore) Set(id string, value any, expire time.Duration) error { - ctx := context.Background() - if err := r.client.Set(ctx, id, value, expire).Err(); err != nil { - return common.StringError(err) - } - return nil -} - -func (r redisStore) HSet(key string, data map[string]interface{}) error { - ctx := context.Background() - if err := r.client.HSet(ctx, key, data).Err(); err != nil { - return common.StringError(err, "failed to save array to redis") - } - - return nil -} - -func (r redisStore) HGetAll(key string) (map[string]string, error) { - ctx := context.Background() - data, err := r.client.HGetAll(ctx, key).Result() - if err != nil { - return data, common.StringError(err) - } - return data, nil -} - -func (r redisStore) HMLen(key string) int64 { - ctx := context.Background() - data := r.client.HLen(ctx, key) - return data.Val() -} - -func (r redisStore) HDel(key, val string) int64 { - ctx := context.Background() - data := r.client.HDel(ctx, key, val) - return data.Val() + return database.NewRedisStore(opts) } diff --git a/pkg/store/redis_helpers.go b/pkg/store/redis_helpers.go index 54c87be9..0ff16cb8 100644 --- a/pkg/store/redis_helpers.go +++ b/pkg/store/redis_helpers.go @@ -5,32 +5,34 @@ import ( "reflect" "time" - "github.com/String-xyz/string-api/pkg/internal/common" + libcommon "github.com/String-xyz/go-lib/common" + "github.com/String-xyz/go-lib/database" + serror "github.com/String-xyz/go-lib/stringerror" "github.com/pkg/errors" ) -func GetObjectFromCache[T any](redis RedisStore, key string) (T, error) { +func GetObjectFromCache[T any](redis database.RedisStore, key string) (T, error) { var result *T = new(T) bytes, err := redis.Get(key) - if err != nil && errors.Cause(err).Error() == "redis: nil" && len(bytes) == 0 { + if err != nil && serror.IsError(err, serror.NOT_FOUND) && len(bytes) == 0 { return *result, nil // object doesn't exist yet, create it down the stack } else if err != nil { // Work around the way that redis go api scopes error - return *result, common.StringError(errors.New(err.Error())) + return *result, libcommon.StringError(errors.New(err.Error())) } err = json.Unmarshal(bytes, &result) if err != nil { - return *result, common.StringError(err) + return *result, libcommon.StringError(err) } return *result, nil } -func PutObjectInCache(redis RedisStore, key string, object any, optionalTimeout ...time.Duration) error { +func PutObjectInCache(redis database.RedisStore, key string, object any, optionalTimeout ...time.Duration) error { // Safeguard against missing tags val := reflect.ValueOf(object) for i := 0; i < val.Type().NumField(); i++ { if val.Type().Field(i).Tag.Get("json") == "" { - return common.StringError(errors.New("object missing json tags")) + return libcommon.StringError(errors.New("object missing json tags")) } } @@ -41,13 +43,13 @@ func PutObjectInCache(redis RedisStore, key string, object any, optionalTimeout bytes, err := json.Marshal(object) if err != nil { - return common.StringError(err) + return libcommon.StringError(err) } err = redis.Set(key, bytes, timeout) if err != nil { // Work around the way that redis go API scopes error - return common.StringError(errors.New(err.Error())) + return libcommon.StringError(errors.New(err.Error())) } return nil } diff --git a/pkg/test/stubs/service.go b/pkg/test/stubs/service.go index 0aa50694..12a0681d 100644 --- a/pkg/test/stubs/service.go +++ b/pkg/test/stubs/service.go @@ -1,6 +1,8 @@ package stubs import ( + "context" + "github.com/String-xyz/string-api/pkg/model" "github.com/String-xyz/string-api/pkg/service" ) @@ -14,15 +16,15 @@ func (v *Verification) SetError(e error) { v.Error = e } -func (v Verification) SendEmailVerification(userId string, email string) error { +func (v Verification) SendEmailVerification(ctx context.Context, userId string, email string) error { return v.Error } -func (v Verification) VerifyEmail(encrypted string) error { +func (v Verification) VerifyEmail(ctx context.Context, encrypted string) error { return v.Error } -func (v Verification) SendDeviceVerification(userId string, deviceId string, deviceDescription string) error { +func (v Verification) SendDeviceVerification(userId string, email string, deviceId string, deviceDescription string) error { return v.Error } @@ -50,15 +52,15 @@ func (u *User) SetUser(user model.User) { u.User = user } -func (u User) GetStatus(id string) (model.UserOnboardingStatus, error) { +func (u User) GetStatus(ctx context.Context, id string) (model.UserOnboardingStatus, error) { return u.UserOnboardingStatus, u.Error } -func (u User) Create(request model.WalletSignaturePayloadSigned) (service.UserCreateResponse, error) { +func (u User) Create(ctx context.Context, request model.WalletSignaturePayloadSigned) (service.UserCreateResponse, error) { return u.UserCreateResponse, u.Error } -func (u User) Update(userId string, request service.UserUpdates) (model.User, error) { +func (u User) Update(ctx context.Context, userId string, request service.UserUpdates) (model.User, error) { return u.User, u.Error } @@ -90,11 +92,11 @@ func (a Auth) PayloadToSign(walletAdress string) (service.SignablePayload, error return a.SignablePayload, a.Error } -func (a Auth) VerifySignedPayload(model.WalletSignaturePayloadSigned) (service.UserCreateResponse, error) { +func (a Auth) VerifySignedPayload(ctx context.Context, signature model.WalletSignaturePayloadSigned) (service.UserCreateResponse, error) { return a.UserCreateResponse, a.Error } -func (a Auth) GenerateJWT(model.Device) (service.JWT, error) { +func (a Auth) GenerateJWT(string, ...model.Device) (service.JWT, error) { return a.JWT, a.Error } @@ -102,6 +104,35 @@ func (a Auth) ValidateAPIKey(key string) bool { return true } -func (a Auth) RefreshToken(token string) (service.JWT, error) { - return a.JWT, a.Error +func (a Auth) RefreshToken(ctx context.Context, token string, walletAddress string) (service.UserCreateResponse, error) { + return service.UserCreateResponse{}, a.Error +} + +func (a Auth) InvalidateRefreshToken(token string) error { + return a.Error +} + +type Device struct { + Device model.Device + Error error +} + +func (d Device) VerifyDevice(ctx context.Context, encrypted string) error { + return d.Error +} + +func (d Device) UpsertDeviceIP(ctx context.Context, deviceId string, ip string) error { + return d.Error +} + +func (d Device) InvalidateUnknownDevice(ctx context.Context, device model.Device) error { + return d.Error +} + +func (d Device) CreateDeviceIfNeeded(userId, visitorId, requestId string) (model.Device, error) { + return d.Device, d.Error +} + +func (d Device) CreateUnknownDevice(userId string) (model.Device, error) { + return d.Device, d.Error } diff --git a/scripts/data_seeding.go b/scripts/data_seeding.go index ae9c757b..817e3703 100644 --- a/scripts/data_seeding.go +++ b/scripts/data_seeding.go @@ -1,6 +1,7 @@ package scripts import ( + "context" "database/sql" "fmt" "os" @@ -33,6 +34,7 @@ func DataSeeding() { } repos := api.NewRepos(config) + ctx := context.Background() // api.Start(config) // Write to repos @@ -95,35 +97,35 @@ func DataSeeding() { } // Update Networks with GasTokenIds - err = repos.Network.Update(networkPolygon.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) + err = repos.Network.Update(ctx, networkPolygon.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkMumbai.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) + err = repos.Network.Update(ctx, networkMumbai.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkGoerli.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkGoerli.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkEthereum.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkEthereum.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkFuji.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) + err = repos.Network.Update(ctx, networkFuji.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkAvalanche.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) + err = repos.Network.Update(ctx, networkAvalanche.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkNitroGoerli.Id, model.NetworkUpdates{GasTokenId: &assetGoerliEth.Id}) + err = repos.Network.Update(ctx, networkNitroGoerli.Id, model.NetworkUpdates{GasTokenId: &assetGoerliEth.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkArbitrumNova.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkArbitrumNova.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } @@ -145,7 +147,7 @@ func DataSeeding() { } updateId := UpdateId{Id: internalId} - userString, err = repos.User.Update(userString.Id, updateId) + userString, err = repos.User.Update(ctx, userString.Id, updateId) if err != nil { panic(err) } @@ -162,7 +164,7 @@ func DataSeeding() { } updateId = UpdateId{Id: bankId} - err = repos.Instrument.Update(bankString.Id, updateId) + err = repos.Instrument.Update(ctx, bankString.Id, updateId) if err != nil { panic(err) } @@ -179,7 +181,7 @@ func DataSeeding() { } updateId = UpdateId{Id: walletId} - err = repos.Instrument.Update(walletString.Id, updateId) + err = repos.Instrument.Update(ctx, walletString.Id, updateId) if err != nil { panic(err) } @@ -198,7 +200,7 @@ func DataSeeding() { } updateId = UpdateId{Id: platformId} - err = repos.Platform.Update(placeholderPlatform.Id, updateId) + err = repos.Platform.Update(ctx, placeholderPlatform.Id, updateId) if err != nil { panic(err) } @@ -221,6 +223,8 @@ func MockSeeding() { } repos := api.NewRepos(config) + ctx := context.Background() + // api.Start(config) // Write to repos @@ -283,35 +287,35 @@ func MockSeeding() { } // Update Networks with GasTokenIds - err = repos.Network.Update(networkPolygon.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) + err = repos.Network.Update(ctx, networkPolygon.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkMumbai.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) + err = repos.Network.Update(ctx, networkMumbai.Id, model.NetworkUpdates{GasTokenId: &assetMatic.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkGoerli.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkGoerli.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkEthereum.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkEthereum.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkFuji.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) + err = repos.Network.Update(ctx, networkFuji.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkAvalanche.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) + err = repos.Network.Update(ctx, networkAvalanche.Id, model.NetworkUpdates{GasTokenId: &assetAvalanche.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkNitroGoerli.Id, model.NetworkUpdates{GasTokenId: &assetGoerliEth.Id}) + err = repos.Network.Update(ctx, networkNitroGoerli.Id, model.NetworkUpdates{GasTokenId: &assetGoerliEth.Id}) if err != nil { panic(err) } - err = repos.Network.Update(networkArbitrumNova.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) + err = repos.Network.Update(ctx, networkArbitrumNova.Id, model.NetworkUpdates{GasTokenId: &assetEthereum.Id}) if err != nil { panic(err) } @@ -333,7 +337,7 @@ func MockSeeding() { } updateId := UpdateId{Id: internalId} - userString, err = repos.User.Update(userString.Id, updateId) + userString, err = repos.User.Update(ctx, userString.Id, updateId) if err != nil { panic(err) } @@ -354,7 +358,7 @@ func MockSeeding() { } updateId = UpdateId{Id: bankId} - err = repos.Instrument.Update(bankString.Id, updateId) + err = repos.Instrument.Update(ctx, bankString.Id, updateId) if err != nil { panic(err) } @@ -371,7 +375,7 @@ func MockSeeding() { } updateId = UpdateId{Id: walletId} - err = repos.Instrument.Update(walletString.Id, updateId) + err = repos.Instrument.Update(ctx, walletString.Id, updateId) if err != nil { panic(err) } @@ -389,7 +393,7 @@ func MockSeeding() { } updateId = UpdateId{Id: platformId} - err = repos.Platform.Update(placeholderPlatform.Id, updateId) + err = repos.Platform.Update(ctx, placeholderPlatform.Id, updateId) if err != nil { panic(err) }