From 44879f1a8f4aa130c9286fccec11e67df5c01841 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Wed, 18 Jan 2023 01:38:00 +0530 Subject: [PATCH] feat: add couchbase provider --- server/constants/db_types.go | 2 + server/constants/env.go | 4 + server/db/db.go | 2 +- .../db/providers/couchbase/email_template.go | 164 ++++++++++++++ server/db/providers/couchbase/env.go | 67 ++++++ server/db/providers/couchbase/otp.go | 84 ++++++++ server/db/providers/couchbase/provider.go | 151 +++++++++++++ server/db/providers/couchbase/session.go | 34 +++ server/db/providers/couchbase/shared.go | 65 ++++++ server/db/providers/couchbase/user.go | 203 ++++++++++++++++++ .../couchbase/verification_requests.go | 128 +++++++++++ server/db/providers/couchbase/webhook.go | 187 ++++++++++++++++ server/db/providers/couchbase/webhook_log.go | 83 +++++++ server/go.mod | 1 + server/go.sum | 8 + server/test/resolvers_test.go | 11 +- 16 files changed, 1188 insertions(+), 6 deletions(-) create mode 100644 server/db/providers/couchbase/email_template.go create mode 100644 server/db/providers/couchbase/env.go create mode 100644 server/db/providers/couchbase/otp.go create mode 100644 server/db/providers/couchbase/provider.go create mode 100644 server/db/providers/couchbase/session.go create mode 100644 server/db/providers/couchbase/shared.go create mode 100644 server/db/providers/couchbase/user.go create mode 100644 server/db/providers/couchbase/verification_requests.go create mode 100644 server/db/providers/couchbase/webhook.go create mode 100644 server/db/providers/couchbase/webhook_log.go diff --git a/server/constants/db_types.go b/server/constants/db_types.go index ead7fc6..94f2faa 100644 --- a/server/constants/db_types.go +++ b/server/constants/db_types.go @@ -27,4 +27,6 @@ const ( DbTypePlanetScaleDB = "planetscale" // DbTypeDynamoDB is the Dynamo database type DbTypeDynamoDB = "dynamodb" + // DbTypeCouchbaseDB is the Couchbase database type + DbTypeCouchbaseDB = "couchbase" ) diff --git a/server/constants/env.go b/server/constants/env.go index a91ad21..872a382 100644 --- a/server/constants/env.go +++ b/server/constants/env.go @@ -43,6 +43,10 @@ const ( EnvKeyDatabaseCertKey = "DATABASE_CERT_KEY" // EnvKeyDatabaseCACert key for env variable DATABASE_CA_CERT EnvKeyDatabaseCACert = "DATABASE_CA_CERT" + // EnvCouchbaseBucket key for env variable COUCHBASE_BUCKET + EnvCouchbaseBucket = "COUCHBASE_BUCKET" + // EnvCouchbaseBucket key for env variable COUCHBASE_SCOPE + EnvCouchbaseScope = "COUCHBASE_SCOPE" // EnvKeySmtpHost key for env variable SMTP_HOST EnvKeySmtpHost = "SMTP_HOST" // EnvKeySmtpPort key for env variable SMTP_PORT diff --git a/server/db/db.go b/server/db/db.go index 5230306..0579448 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -21,7 +21,7 @@ func InitDB() error { envs := memorystore.RequiredEnvStoreObj.GetRequiredEnv() - isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB + isSQL := envs.DatabaseType != constants.DbTypeArangodb && envs.DatabaseType != constants.DbTypeMongodb && envs.DatabaseType != constants.DbTypeCassandraDB && envs.DatabaseType != constants.DbTypeScyllaDB && envs.DatabaseType != constants.DbTypeDynamoDB && envs.DatabaseType != constants.DbTypeCouchbaseDB isArangoDB := envs.DatabaseType == constants.DbTypeArangodb isMongoDB := envs.DatabaseType == constants.DbTypeMongodb isCassandra := envs.DatabaseType == constants.DbTypeCassandraDB || envs.DatabaseType == constants.DbTypeScyllaDB diff --git a/server/db/providers/couchbase/email_template.go b/server/db/providers/couchbase/email_template.go new file mode 100644 index 0000000..796ca0f --- /dev/null +++ b/server/db/providers/couchbase/email_template.go @@ -0,0 +1,164 @@ +package couchbase + +import ( + "context" + "encoding/json" + "fmt" + "log" + "strings" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddEmailTemplate to add EmailTemplate +func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + + if emailTemplate.ID == "" { + emailTemplate.ID = uuid.New().String() + } + + emailTemplate.Key = emailTemplate.ID + emailTemplate.CreatedAt = time.Now().Unix() + emailTemplate.UpdatedAt = time.Now().Unix() + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + + _, err := p.db.Collection(models.Collections.EmailTemplate).Insert(emailTemplate.ID, emailTemplate, &insertOpt) + if err != nil { + return emailTemplate.AsAPIEmailTemplate(), err + } + + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + bytes, err := json.Marshal(emailTemplate) + if err != nil { + return nil, err + } + // use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling + decoder := json.NewDecoder(strings.NewReader(string(bytes))) + decoder.UseNumber() + emailTemplateMap := map[string]interface{}{} + err = decoder.Decode(&emailTemplateMap) + if err != nil { + return nil, err + } + + updateFields, params := GetSetFields(emailTemplateMap) + params["emailId"] = emailTemplate.ID + + query := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $emailId", p.scopeName, models.Collections.EmailTemplate, updateFields) + + _, err = p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + NamedParameters: params, + }) + if err != nil { + return nil, err + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// ListEmailTemplates to list EmailTemplate +func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) { + emailTemplates := []*model.EmailTemplate{} + paginationClone := pagination + + _, paginationClone.Total = p.GetTotalDocs(ctx, models.Collections.EmailTemplate) + + userQuery := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s ORDER BY _id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.EmailTemplate) + + queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit}, + }) + + if err != nil { + return nil, err + } + + for queryResult.Next() { + emailTemplate := models.EmailTemplate{} + err := queryResult.Row(&emailTemplate) + if err != nil { + log.Fatal(err) + } + emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate()) + } + + if err := queryResult.Err(); err != nil { + return nil, err + + } + + return &model.EmailTemplates{ + Pagination: &paginationClone, + EmailTemplates: emailTemplates, + }, nil +} + +// GetEmailTemplateByID to get EmailTemplate by id +func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) { + emailTemplate := models.EmailTemplate{} + + query := fmt.Sprintf(`SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1`, p.scopeName, models.Collections.EmailTemplate) + q, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{emailTemplateID}, + }) + + if err != nil { + return nil, err + } + err = q.One(&emailTemplate) + + if err != nil { + return nil, err + } + + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// GetEmailTemplateByEventName to get EmailTemplate by event_name +func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) { + emailTemplate := models.EmailTemplate{} + + query := fmt.Sprintf("SELECT _id, event_name, subject, design, template, created_at, updated_at FROM %s.%s WHERE event_name=$1 LIMIT 1", p.scopeName, models.Collections.EmailTemplate) + q, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{eventName}, + }) + + if err != nil { + return nil, err + } + err = q.One(&emailTemplate) + + if err != nil { + return nil, err + } + + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// DeleteEmailTemplate to delete EmailTemplate +func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error { + removeOpt := gocb.RemoveOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.EmailTemplate).Remove(emailTemplate.ID, &removeOpt) + if err != nil { + return err + } + return nil +} diff --git a/server/db/providers/couchbase/env.go b/server/db/providers/couchbase/env.go new file mode 100644 index 0000000..50b2e84 --- /dev/null +++ b/server/db/providers/couchbase/env.go @@ -0,0 +1,67 @@ +package couchbase + +import ( + "context" + "fmt" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddEnv to save environment information in database +func (p *provider) AddEnv(ctx context.Context, env models.Env) (models.Env, error) { + if env.ID == "" { + env.ID = uuid.New().String() + } + env.CreatedAt = time.Now().Unix() + env.UpdatedAt = time.Now().Unix() + env.Key = env.ID + + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt) + if err != nil { + return env, err + } + return env, nil +} + +// UpdateEnv to update environment information in database +func (p *provider) UpdateEnv(ctx context.Context, env models.Env) (models.Env, error) { + env.UpdatedAt = time.Now().Unix() + + updateEnvQuery := fmt.Sprintf("UPDATE %s.%s SET env = $1, updated_at = $2 WHERE _id = $3", p.scopeName, models.Collections.Env) + _, err := p.db.Query(updateEnvQuery, &gocb.QueryOptions{ + Context: ctx, + PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID}, + }) + + if err != nil { + return env, err + } + + return env, nil +} + +// GetEnv to get environment information from database +func (p *provider) GetEnv(ctx context.Context) (models.Env, error) { + var env models.Env + + query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s LIMIT 1", p.scopeName, models.Collections.Env) + q, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + }) + if err != nil { + return env, err + } + err = q.One(&env) + + if err != nil { + return env, err + } + return env, nil +} diff --git a/server/db/providers/couchbase/otp.go b/server/db/providers/couchbase/otp.go new file mode 100644 index 0000000..cdcfde9 --- /dev/null +++ b/server/db/providers/couchbase/otp.go @@ -0,0 +1,84 @@ +package couchbase + +import ( + "context" + "fmt" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// UpsertOTP to add or update otp +func (p *provider) UpsertOTP(ctx context.Context, otpParam *models.OTP) (*models.OTP, error) { + otp, _ := p.GetOTPByEmail(ctx, otpParam.Email) + + shouldCreate := false + if otp == nil { + shouldCreate = true + otp = &models.OTP{ + ID: uuid.NewString(), + Otp: otpParam.Otp, + Email: otpParam.Email, + ExpiresAt: otpParam.ExpiresAt, + CreatedAt: time.Now().Unix(), + UpdatedAt: time.Now().Unix(), + } + } else { + otp.Otp = otpParam.Otp + otp.ExpiresAt = otpParam.ExpiresAt + } + + otp.UpdatedAt = time.Now().Unix() + if shouldCreate { + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.OTP).Insert(otp.ID, otp, &insertOpt) + if err != nil { + return otp, err + } + } else { + query := fmt.Sprintf(`UPDATE %s.%s SET otp=$1, expires_at=$2, updated_at=$3 WHERE _id=$4`, p.scopeName, models.Collections.OTP) + _, err := p.db.Query(query, &gocb.QueryOptions{ + PositionalParameters: []interface{}{otp.Otp, otp.ExpiresAt, otp.UpdatedAt, otp.ID}, + }) + if err != nil { + return otp, err + } + } + return otp, nil +} + +// GetOTPByEmail to get otp for a given email address +func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) { + otp := models.OTP{} + query := fmt.Sprintf(`SELECT _id, email, otp, expires_at, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1`, p.scopeName, models.Collections.OTP) + q, err := p.db.Query(query, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{emailAddress}, + }) + if err != nil { + return nil, err + } + err = q.One(&otp) + + if err != nil { + return nil, err + } + + return &otp, nil +} + +// DeleteOTP to delete otp +func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error { + removeOpt := gocb.RemoveOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.OTP).Remove(otp.ID, &removeOpt) + if err != nil { + return err + } + return nil +} diff --git a/server/db/providers/couchbase/provider.go b/server/db/providers/couchbase/provider.go new file mode 100644 index 0000000..4f3181e --- /dev/null +++ b/server/db/providers/couchbase/provider.go @@ -0,0 +1,151 @@ +package couchbase + +import ( + "context" + "errors" + "fmt" + "os" + "reflect" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/couchbase/gocb/v2" +) + +// TODO change following provider to new db provider +type provider struct { + db *gocb.Scope + scopeName string +} + +// NewProvider returns a new SQL provider +// TODO change following provider to new db provider +func NewProvider() (*provider, error) { + // scopeName := os.Getenv(constants.EnvCouchbaseScope) + bucketName := os.Getenv(constants.EnvCouchbaseBucket) + scopeName := os.Getenv(constants.EnvCouchbaseScope) + + dbURL := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseURL + userName := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabaseUsername + password := memorystore.RequiredEnvStoreObj.GetRequiredEnv().DatabasePassword + + opts := gocb.ClusterOptions{ + Username: userName, + Password: password, + } + + cluster, err := gocb.Connect(dbURL, opts) + + if err != nil { + return nil, err + } + + // To create the bucket and scope if not exist + bucket, err := CreateBucketAndScope(cluster, bucketName, scopeName) + + if err != nil { + return nil, err + } + + scope := bucket.Scope(scopeName) + + scopeIdentifier := fmt.Sprintf("%s.%s", bucketName, scopeName) + + v := reflect.ValueOf(models.Collections) + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + user := gocb.CollectionSpec{ + Name: field.String(), + ScopeName: scopeName, + } + collectionOpts := gocb.CreateCollectionOptions{ + Context: context.TODO(), + } + _ = bucket.Collections().CreateCollection(user, &collectionOpts) + // if err != nil && !errors.Is(err, gocb.ErrCollectionExists) { + // return nil, err + // } + indexQuery := fmt.Sprintf("CREATE PRIMARY INDEX ON %s.%s", scopeIdentifier, field.String()) + scope.Query(indexQuery, nil) + } + + indices := GetIndex(scopeIdentifier) + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + for _, indexQuery := range indices[field.String()] { + scope.Query(indexQuery, nil) + } + } + return &provider{ + db: scope, + scopeName: scopeIdentifier, + }, nil +} + +func CreateBucketAndScope(cluster *gocb.Cluster, bucketName string, scopeName string) (*gocb.Bucket, error) { + settings := gocb.BucketSettings{ + Name: bucketName, + RAMQuotaMB: 1000, + NumReplicas: 1, + BucketType: gocb.CouchbaseBucketType, + EvictionPolicy: gocb.EvictionPolicyTypeValueOnly, + FlushEnabled: true, + CompressionMode: gocb.CompressionModeActive, + } + + err := cluster.Buckets().CreateBucket(gocb.CreateBucketSettings{ + BucketSettings: settings, + ConflictResolutionType: gocb.ConflictResolutionTypeSequenceNumber, + }, nil) + + bucket := cluster.Bucket(bucketName) + + if err != nil && !errors.Is(err, gocb.ErrBucketExists) { + return bucket, err + } + + err = bucket.Collections().CreateScope(scopeName, nil) + + if err != nil && !errors.Is(err, gocb.ErrScopeExists) { + return bucket, err + } + + return bucket, nil +} + +func GetIndex(scopeName string) map[string][]string { + indices := make(map[string][]string) + + // User Index + userIndex1 := fmt.Sprintf("CREATE INDEX userEmailIndex ON %s.%s(email)", scopeName, models.Collections.User) + userIndex2 := fmt.Sprintf("CREATE INDEX userPhoneIndex ON %s.%s(phone_number)", scopeName, models.Collections.User) + indices[models.Collections.User] = []string{userIndex1, userIndex2} + + // VerificationRequest + verificationIndex1 := fmt.Sprintf("CREATE INDEX verificationRequestTokenIndex ON %s.%s(token)", scopeName, models.Collections.VerificationRequest) + verificationIndex2 := fmt.Sprintf("CREATE INDEX verificationRequestEmailAndIdentifierIndex ON %s.%s(email,identifier)", scopeName, models.Collections.VerificationRequest) + indices[models.Collections.VerificationRequest] = []string{verificationIndex1, verificationIndex2} + + // Session index + sessionIndex1 := fmt.Sprintf("CREATE INDEX SessionUserIdIndex ON %s.%s(user_id)", scopeName, models.Collections.Session) + indices[models.Collections.Session] = []string{sessionIndex1} + + // Webhook index + webhookIndex1 := fmt.Sprintf("CREATE INDEX webhookEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.Webhook) + indices[models.Collections.Webhook] = []string{webhookIndex1} + + // WebhookLog index + webhookLogIndex1 := fmt.Sprintf("CREATE INDEX webhookLogIdIndex ON %s.%s(webhook_id)", scopeName, models.Collections.WebhookLog) + indices[models.Collections.Webhook] = []string{webhookLogIndex1} + + // WebhookLog index + emailTempIndex1 := fmt.Sprintf("CREATE INDEX EmailTemplateEventNameIndex ON %s.%s(event_name)", scopeName, models.Collections.EmailTemplate) + indices[models.Collections.EmailTemplate] = []string{emailTempIndex1} + + // OTP index + otpIndex1 := fmt.Sprintf("CREATE INDEX OTPEmailIndex ON %s.%s(email)", scopeName, models.Collections.OTP) + indices[models.Collections.OTP] = []string{otpIndex1} + + return indices +} diff --git a/server/db/providers/couchbase/session.go b/server/db/providers/couchbase/session.go new file mode 100644 index 0000000..6f0d84f --- /dev/null +++ b/server/db/providers/couchbase/session.go @@ -0,0 +1,34 @@ +package couchbase + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddSession to save session information in database +func (p *provider) AddSession(ctx context.Context, session models.Session) error { + if session.ID == "" { + session.ID = uuid.New().String() + } + + session.CreatedAt = time.Now().Unix() + session.UpdatedAt = time.Now().Unix() + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.Session).Insert(session.ID, session, &insertOpt) + if err != nil { + return err + } + + return nil +} + +// DeleteSession to delete session information from database +func (p *provider) DeleteSession(ctx context.Context, userId string) error { + return nil +} diff --git a/server/db/providers/couchbase/shared.go b/server/db/providers/couchbase/shared.go new file mode 100644 index 0000000..80b7723 --- /dev/null +++ b/server/db/providers/couchbase/shared.go @@ -0,0 +1,65 @@ +package couchbase + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/couchbase/gocb/v2" +) + +func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) { + params := make(map[string]interface{}, 1) + + updateFields := "" + + for key, value := range webhookMap { + if key == "_id" { + continue + } + + if key == "_key" { + continue + } + + if value == nil { + updateFields += fmt.Sprintf("%s=$%s,", key, key) + params[key] = "null" + continue + } + + valueType := reflect.TypeOf(value) + if valueType.Name() == "string" { + updateFields += fmt.Sprintf("%s = $%s, ", key, key) + params[key] = value.(string) + + } else { + updateFields += fmt.Sprintf("%s = $%s, ", key, key) + params[key] = value + } + } + updateFields = strings.Trim(updateFields, " ") + updateFields = strings.TrimSuffix(updateFields, ",") + return updateFields, params +} + +func (p *provider) GetTotalDocs(ctx context.Context, collection string) (error, int64) { + totalDocs := TotalDocs{} + + countQuery := fmt.Sprintf("SELECT COUNT(*) as Total FROM %s.%s", p.scopeName, collection) + queryRes, err := p.db.Query(countQuery, &gocb.QueryOptions{ + Context: ctx, + }) + + queryRes.One(&totalDocs) + + if err != nil { + return err, totalDocs.Total + } + return nil, totalDocs.Total +} + +type TotalDocs struct { + Total int64 +} diff --git a/server/db/providers/couchbase/user.go b/server/db/providers/couchbase/user.go new file mode 100644 index 0000000..3746977 --- /dev/null +++ b/server/db/providers/couchbase/user.go @@ -0,0 +1,203 @@ +package couchbase + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddUser to save user information in database +func (p *provider) AddUser(ctx context.Context, user models.User) (models.User, error) { + if user.ID == "" { + user.ID = uuid.New().String() + } + + if user.Roles == "" { + defaultRoles, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultRoles) + if err != nil { + return user, err + } + user.Roles = defaultRoles + } + + user.CreatedAt = time.Now().Unix() + user.UpdatedAt = time.Now().Unix() + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.User).Insert(user.ID, user, &insertOpt) + if err != nil { + return user, err + } + return user, nil +} + +// UpdateUser to update user information in database +func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.User, error) { + user.UpdatedAt = time.Now().Unix() + unsertOpt := gocb.UpsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.User).Upsert(user.ID, user, &unsertOpt) + if err != nil { + return user, err + } + return user, nil +} + +// DeleteUser to delete user information from database +func (p *provider) DeleteUser(ctx context.Context, user models.User) error { + removeOpt := gocb.RemoveOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.User).Remove(user.ID, &removeOpt) + if err != nil { + return err + } + return nil +} + +// ListUsers to get list of users from database +func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (*model.Users, error) { + users := []*model.User{} + paginationClone := pagination + + userQuery := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s ORDER BY id OFFSET $1 LIMIT $2", p.scopeName, models.Collections.User) + + queryResult, err := p.db.Query(userQuery, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit}, + }) + + _, paginationClone.Total = p.GetTotalDocs(ctx, models.Collections.User) + + if err != nil { + return nil, err + } + + for queryResult.Next() { + var user models.User + err := queryResult.Row(&user) + if err != nil { + log.Fatal(err) + } + users = append(users, user.AsAPIUser()) + } + + if err := queryResult.Err(); err != nil { + return nil, err + + } + + return &model.Users{ + Pagination: &paginationClone, + Users: users, + }, nil +} + +// GetUserByEmail to get user information from database using email address +func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) { + user := models.User{} + query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE email = $1 LIMIT 1", p.scopeName, models.Collections.User) + q, err := p.db.Query(query, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + PositionalParameters: []interface{}{email}, + }) + + if err != nil { + return user, err + } + err = q.One(&user) + if err != nil { + return user, err + } + + return user, nil +} + +// GetUserByID to get user information from database using user ID +func (p *provider) GetUserByID(ctx context.Context, id string) (models.User, error) { + user := models.User{} + query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE _id = $1 LIMIT 1", p.scopeName, models.Collections.User) + q, err := p.db.Query(query, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + PositionalParameters: []interface{}{id}, + }) + if err != nil { + return user, err + } + err = q.One(&user) + if err != nil { + return user, err + } + + return user, nil +} + +// UpdateUsers to update multiple users, with parameters of user IDs slice +// If ids set to nil / empty all the users will be updated +func (p *provider) UpdateUsers(ctx context.Context, data map[string]interface{}, ids []string) error { + // set updated_at time for all users + data["updated_at"] = time.Now().Unix() + + updateFields, params := GetSetFields(data) + + if ids != nil && len(ids) > 0 { + for _, id := range ids { + params["id"] = id + userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id = $id", p.scopeName, models.Collections.User, updateFields) + + _, err := p.db.Query(userQuery, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + NamedParameters: params, + }) + if err != nil { + return err + } + } + } else { + userQuery := fmt.Sprintf("UPDATE %s.%s SET %s WHERE _id IS NOT NULL", p.scopeName, models.Collections.User, updateFields) + _, err := p.db.Query(userQuery, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + NamedParameters: params, + }) + if err != nil { + return err + } + } + + return nil +} + +// GetUserByPhoneNumber to get user information from database using phone number +func (p *provider) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*models.User, error) { + var user *models.User + query := fmt.Sprintf("SELECT _id, email, email_verified_at, `password`, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, is_multi_factor_auth_enabled, created_at, updated_at FROM %s.%s WHERE phone_number = $1 LIMIT 1", p.scopeName, models.Collections.User) + q, err := p.db.Query(query, &gocb.QueryOptions{ + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + Context: ctx, + PositionalParameters: []interface{}{phoneNumber}, + }) + if err != nil { + return user, err + } + err = q.One(&user) + if err != nil { + return user, err + } + + return user, nil +} diff --git a/server/db/providers/couchbase/verification_requests.go b/server/db/providers/couchbase/verification_requests.go new file mode 100644 index 0000000..1bc368f --- /dev/null +++ b/server/db/providers/couchbase/verification_requests.go @@ -0,0 +1,128 @@ +package couchbase + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddVerification to save verification request in database +func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) (models.VerificationRequest, error) { + if verificationRequest.ID == "" { + verificationRequest.ID = uuid.New().String() + } + + verificationRequest.Key = verificationRequest.ID + verificationRequest.CreatedAt = time.Now().Unix() + verificationRequest.UpdatedAt = time.Now().Unix() + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.VerificationRequest).Insert(verificationRequest.ID, verificationRequest, &insertOpt) + if err != nil { + return verificationRequest, err + } + + return verificationRequest, nil +} + +// GetVerificationRequestByToken to get verification request from database using token +func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (models.VerificationRequest, error) { + verificationRequest := models.VerificationRequest{} + params := make(map[string]interface{}, 1) + params["token"] = token + query := fmt.Sprintf("SELECT _id, token, identifier, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE token=$1 LIMIT 1", p.scopeName, models.Collections.VerificationRequest) + + queryResult, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + PositionalParameters: []interface{}{token}, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + }) + + if err != nil { + return verificationRequest, err + } + err = queryResult.One(&verificationRequest) + + if err != nil { + return verificationRequest, err + } + return verificationRequest, nil +} + +// GetVerificationRequestByEmail to get verification request by email from database +func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (models.VerificationRequest, error) { + + query := fmt.Sprintf("SELECT _id, identifier, token, expires_at, email, nonce, redirect_uri, created_at, updated_at FROM %s.%s WHERE email=$1 AND identifier=$2 LIMIT 1", p.scopeName, models.Collections.VerificationRequest) + queryResult, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + PositionalParameters: []interface{}{email, identifier}, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + }) + verificationRequest := models.VerificationRequest{} + + if err != nil { + return verificationRequest, err + } + + err = queryResult.One(&verificationRequest) + + if err != nil { + return verificationRequest, err + } + return verificationRequest, nil +} + +// ListVerificationRequests to get list of verification requests from database +func (p *provider) ListVerificationRequests(ctx context.Context, pagination model.Pagination) (*model.VerificationRequests, error) { + var verificationRequests []*model.VerificationRequest + paginationClone := pagination + + _, paginationClone.Total = p.GetTotalDocs(ctx, models.Collections.VerificationRequest) + + query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $1 LIMIT $2", p.scopeName, models.Collections.VerificationRequest) + queryResult, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + PositionalParameters: []interface{}{paginationClone.Offset, paginationClone.Limit}, + }) + + if err != nil { + return nil, err + } + for queryResult.Next() { + var verificationRequest models.VerificationRequest + err := queryResult.Row(&verificationRequest) + if err != nil { + log.Fatal(err) + } + verificationRequests = append(verificationRequests, verificationRequest.AsAPIVerificationRequest()) + } + + if err := queryResult.Err(); err != nil { + return nil, err + + } + return &model.VerificationRequests{ + VerificationRequests: verificationRequests, + Pagination: &paginationClone, + }, nil +} + +// DeleteVerificationRequest to delete verification request from database +func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest models.VerificationRequest) error { + removeOpt := gocb.RemoveOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.VerificationRequest).Remove(verificationRequest.ID, &removeOpt) + if err != nil { + return err + } + return nil +} diff --git a/server/db/providers/couchbase/webhook.go b/server/db/providers/couchbase/webhook.go new file mode 100644 index 0000000..18c8347 --- /dev/null +++ b/server/db/providers/couchbase/webhook.go @@ -0,0 +1,187 @@ +package couchbase + +import ( + "context" + "encoding/json" + "fmt" + "log" + "strings" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddWebhook to add webhook +func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { + if webhook.ID == "" { + webhook.ID = uuid.New().String() + } + + webhook.Key = webhook.ID + webhook.CreatedAt = time.Now().Unix() + webhook.UpdatedAt = time.Now().Unix() + + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.Webhook).Insert(webhook.ID, webhook, &insertOpt) + if err != nil { + return webhook.AsAPIWebhook(), err + } + return webhook.AsAPIWebhook(), nil +} + +// UpdateWebhook to update webhook +func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { + webhook.UpdatedAt = time.Now().Unix() + + bytes, err := json.Marshal(webhook) + if err != nil { + return nil, err + } + // use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling + decoder := json.NewDecoder(strings.NewReader(string(bytes))) + decoder.UseNumber() + webhookMap := map[string]interface{}{} + err = decoder.Decode(&webhookMap) + if err != nil { + return nil, err + } + + updateFields, params := GetSetFields(webhookMap) + + query := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE _id='%s'`, p.scopeName, models.Collections.Webhook, updateFields, webhook.ID) + + _, err = p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + + if err != nil { + return nil, err + } + + return webhook.AsAPIWebhook(), nil +} + +// ListWebhooks to list webhook +func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { + webhooks := []*model.Webhook{} + paginationClone := pagination + + params := make(map[string]interface{}, 1) + params["offset"] = paginationClone.Offset + params["limit"] = paginationClone.Limit + + query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.Webhook) + + _, paginationClone.Total = p.GetTotalDocs(ctx, models.Collections.Webhook) + + queryResult, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + + if err != nil { + return nil, err + } + for queryResult.Next() { + var webhook models.Webhook + err := queryResult.Row(&webhook) + if err != nil { + log.Fatal(err) + } + webhooks = append(webhooks, webhook.AsAPIWebhook()) + } + + if err := queryResult.Err(); err != nil { + return nil, err + + } + return &model.Webhooks{ + Pagination: &paginationClone, + Webhooks: webhooks, + }, nil +} + +// GetWebhookByID to get webhook by id +func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) { + var webhook models.Webhook + + params := make(map[string]interface{}, 1) + params["_id"] = webhookID + + query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE _id=$_id LIMIT 1`, p.scopeName, models.Collections.Webhook) + q, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + if err != nil { + return nil, err + } + err = q.One(&webhook) + + if err != nil { + return nil, err + } + + return webhook.AsAPIWebhook(), nil +} + +// GetWebhookByEventName to get webhook by event_name +func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { + var webhook models.Webhook + params := make(map[string]interface{}, 1) + params["event_name"] = eventName + + query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name=$event_name LIMIT 1`, p.scopeName, models.Collections.Webhook) + q, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + + if err != nil { + return nil, err + } + err = q.One(&webhook) + + if err != nil { + return nil, err + } + + return webhook.AsAPIWebhook(), nil +} + +// DeleteWebhook to delete webhook +func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error { + + params := make(map[string]interface{}, 1) + params["webhook_id"] = webhook.ID + removeOpt := gocb.RemoveOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.Webhook).Remove(webhook.ID, &removeOpt) + + if err != nil { + return err + } + + query := fmt.Sprintf(`DELETE FROM %s.%s WHERE webhook_id=$webhook_id`, p.scopeName, models.Collections.WebhookLog) + _, err = p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + if err != nil { + return err + } + + return nil +} diff --git a/server/db/providers/couchbase/webhook_log.go b/server/db/providers/couchbase/webhook_log.go new file mode 100644 index 0000000..6957359 --- /dev/null +++ b/server/db/providers/couchbase/webhook_log.go @@ -0,0 +1,83 @@ +package couchbase + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/couchbase/gocb/v2" + "github.com/google/uuid" +) + +// AddWebhookLog to add webhook log +func (p *provider) AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) { + if webhookLog.ID == "" { + webhookLog.ID = uuid.New().String() + } + + webhookLog.Key = webhookLog.ID + webhookLog.CreatedAt = time.Now().Unix() + webhookLog.UpdatedAt = time.Now().Unix() + + insertOpt := gocb.InsertOptions{ + Context: ctx, + } + _, err := p.db.Collection(models.Collections.WebhookLog).Insert(webhookLog.ID, webhookLog, &insertOpt) + if err != nil { + return webhookLog.AsAPIWebhookLog(), err + } + + return webhookLog.AsAPIWebhookLog(), nil +} + +// ListWebhookLogs to list webhook logs +func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) { + var query string + var err error + + webhookLogs := []*model.WebhookLog{} + params := make(map[string]interface{}, 1) + paginationClone := pagination + + params["webhookID"] = webhookID + params["offset"] = paginationClone.Offset + params["limit"] = paginationClone.Limit + + _, paginationClone.Total = p.GetTotalDocs(ctx, models.Collections.WebhookLog) + + if webhookID != "" { + query = fmt.Sprintf(`SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s WHERE webhook_id=$webhookID`, p.scopeName, models.Collections.WebhookLog) + } else { + query = fmt.Sprintf("SELECT _id, http_status, response, request, webhook_id, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.WebhookLog) + } + + queryResult, err := p.db.Query(query, &gocb.QueryOptions{ + Context: ctx, + ScanConsistency: gocb.QueryScanConsistencyRequestPlus, + NamedParameters: params, + }) + + if err != nil { + return nil, err + } + for queryResult.Next() { + var webhookLog models.WebhookLog + err := queryResult.Row(&webhookLog) + if err != nil { + log.Fatal(err) + } + webhookLogs = append(webhookLogs, webhookLog.AsAPIWebhookLog()) + } + + if err := queryResult.Err(); err != nil { + return nil, err + + } + return &model.WebhookLogs{ + Pagination: &paginationClone, + WebhookLogs: webhookLogs, + }, nil +} diff --git a/server/go.mod b/server/go.mod index fa5dba2..f663495 100644 --- a/server/go.mod +++ b/server/go.mod @@ -7,6 +7,7 @@ require ( github.com/arangodb/go-driver v1.2.1 github.com/aws/aws-sdk-go v1.44.109 github.com/coreos/go-oidc/v3 v3.1.0 + github.com/couchbase/gocb/v2 v2.6.0 // indirect github.com/gin-gonic/gin v1.8.1 github.com/glebarez/sqlite v1.5.0 github.com/go-playground/validator/v10 v10.11.1 // indirect diff --git a/server/go.sum b/server/go.sum index 316bcce..fa4e2e3 100644 --- a/server/go.sum +++ b/server/go.sum @@ -77,6 +77,11 @@ github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbr github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/couchbase/gocb/v2 v2.6.0 h1:DhkLNatDcddCcS411D6kNwZspSEAWVeI/N3abzt/HLc= +github.com/couchbase/gocb/v2 v2.6.0/go.mod h1:5su8b1gBF3V4j07SiGw+CA0bK9a84YWEb6UH7up0MEs= +github.com/couchbase/gocbcore/v10 v10.2.0 h1:ZoSBLtcmt+lXbxVVT4SAhXDVNR+D48iSOZWNzHucVVk= +github.com/couchbase/gocbcore/v10 v10.2.0/go.mod h1:qkPnOBziCs0guMEEvd0cRFo+AjOW0yEL99cU3I4n3Ao= +github.com/couchbaselabs/gocaves/client v0.0.0-20220223122017-22859b310bd2/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -174,6 +179,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -377,6 +384,7 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 015bce9..90a5706 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -16,11 +16,12 @@ import ( func TestResolvers(t *testing.T) { databases := map[string]string{ - constants.DbTypeSqlite: "../../test.db", - constants.DbTypeArangodb: "http://localhost:8529", - constants.DbTypeMongodb: "mongodb://localhost:27017", - constants.DbTypeScyllaDB: "127.0.0.1:9042", - constants.DbTypeDynamoDB: "http://0.0.0.0:8000", + constants.DbTypeSqlite: "../../test.db", + constants.DbTypeArangodb: "http://localhost:8529", + constants.DbTypeMongodb: "mongodb://localhost:27017", + constants.DbTypeScyllaDB: "127.0.0.1:9042", + constants.DbTypeDynamoDB: "http://0.0.0.0:8000", + constants.DbTypeCouchbaseDB: "couchbase://127.0.0.1", } testDBs := strings.Split(os.Getenv("TEST_DBS"), ",")