diff --git a/Makefile b/Makefile index c883a9c..b5539f7 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,16 @@ build-dashboard: clean: rm -rf build test: - rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && go test -p 1 -v ./test + rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test +test-all-db: + rm -rf server/test/test.db && rm -rf test.db + docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla + docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15 + docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4 + cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test + docker rm -vf authorizer_mongodb_db + docker rm -vf authorizer_scylla_db + docker rm -vf authorizer_arangodb generate: cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate \ No newline at end of file diff --git a/server/constants/oauth_info_urls.go b/server/constants/oauth_info_urls.go index 875add5..1dcec3a 100644 --- a/server/constants/oauth_info_urls.go +++ b/server/constants/oauth_info_urls.go @@ -8,6 +8,9 @@ const ( FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token=" // Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token GithubUserInfoURL = "https://api.github.com/user" + // Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123 + GithubUserEmails = "https://api/github.com/user/emails" + // Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))" LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))" diff --git a/server/db/models/email_templates.go b/server/db/models/email_templates.go new file mode 100644 index 0000000..23ac7fa --- /dev/null +++ b/server/db/models/email_templates.go @@ -0,0 +1,33 @@ +package models + +import ( + "strings" + + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/refs" +) + +// EmailTemplate model for database +type EmailTemplate struct { + Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb + ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"` + EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"` + Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"` + CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` +} + +// AsAPIEmailTemplate to return email template as graphql response object +func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate { + id := e.ID + if strings.Contains(id, Collections.EmailTemplate+"/") { + id = strings.TrimPrefix(id, Collections.EmailTemplate+"/") + } + return &model.EmailTemplate{ + ID: id, + EventName: e.EventName, + Template: e.Template, + CreatedAt: refs.NewInt64Ref(e.CreatedAt), + UpdatedAt: refs.NewInt64Ref(e.UpdatedAt), + } +} diff --git a/server/db/models/model.go b/server/db/models/model.go index 4dbda36..bdbdaa3 100644 --- a/server/db/models/model.go +++ b/server/db/models/model.go @@ -8,6 +8,7 @@ type CollectionList struct { Env string Webhook string WebhookLog string + EmailTemplate string } var ( @@ -21,5 +22,6 @@ var ( Env: Prefix + "env", Webhook: Prefix + "webhook", WebhookLog: Prefix + "webhook_log", + EmailTemplate: Prefix + "email_template", } ) diff --git a/server/db/models/verification_requests.go b/server/db/models/verification_requests.go index c3ca117..3e13a7f 100644 --- a/server/db/models/verification_requests.go +++ b/server/db/models/verification_requests.go @@ -32,7 +32,7 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ return &model.VerificationRequest{ ID: id, Token: refs.NewStringRef(v.Token), - Identifier: refs.NewStringRef(v.Identifier), + Identifier: refs.NewStringRef(v.Identifier), Expires: refs.NewInt64Ref(v.ExpiresAt), Email: refs.NewStringRef(v.Email), Nonce: refs.NewStringRef(v.Nonce), diff --git a/server/db/models/webhook.go b/server/db/models/webhook.go index 36572ba..cf7c460 100644 --- a/server/db/models/webhook.go +++ b/server/db/models/webhook.go @@ -22,6 +22,7 @@ type Webhook struct { UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` } +// AsAPIWebhook to return webhook as graphql response object func (w *Webhook) AsAPIWebhook() *model.Webhook { headersMap := make(map[string]interface{}) json.Unmarshal([]byte(w.Headers), &headersMap) diff --git a/server/db/models/webhook_log.go b/server/db/models/webhook_log.go index b7a758b..7305a1d 100644 --- a/server/db/models/webhook_log.go +++ b/server/db/models/webhook_log.go @@ -21,6 +21,7 @@ type WebhookLog struct { UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"` } +// AsAPIWebhookLog to return webhook log as graphql response object func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog { id := w.ID if strings.Contains(id, Collections.WebhookLog+"/") { diff --git a/server/db/providers/arangodb/email_template.go b/server/db/providers/arangodb/email_template.go new file mode 100644 index 0000000..4e64762 --- /dev/null +++ b/server/db/providers/arangodb/email_template.go @@ -0,0 +1,151 @@ +package arangodb + +import ( + "context" + "fmt" + "time" + + "github.com/arangodb/go-driver" + arangoDriver "github.com/arangodb/go-driver" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "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() + + emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate) + _, err := emailTemplateCollection.CreateDocument(ctx, emailTemplate) + if err != nil { + return nil, err + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + emailTemplate.UpdatedAt = time.Now().Unix() + + emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate) + meta, err := emailTemplateCollection.UpdateDocument(ctx, emailTemplate.Key, emailTemplate) + if err != nil { + return nil, err + } + + emailTemplate.Key = meta.Key + emailTemplate.ID = meta.ID.String() + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// ListEmailTemplates to list EmailTemplate +func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) { + emailTemplates := []*model.EmailTemplate{} + + query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit) + + sctx := driver.WithQueryFullCount(ctx) + cursor, err := p.db.Query(sctx, query, nil) + if err != nil { + return nil, err + } + defer cursor.Close() + + paginationClone := pagination + paginationClone.Total = cursor.Statistics().FullCount() + + for { + var emailTemplate models.EmailTemplate + meta, err := cursor.ReadDocument(ctx, &emailTemplate) + + if arangoDriver.IsNoMoreDocuments(err) { + break + } else if err != nil { + return nil, err + } + + if meta.Key != "" { + emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate()) + } + } + + 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) { + var emailTemplate models.EmailTemplate + query := fmt.Sprintf("FOR d in %s FILTER d._key == @email_template_id RETURN d", models.Collections.EmailTemplate) + bindVars := map[string]interface{}{ + "email_template_id": emailTemplateID, + } + + cursor, err := p.db.Query(ctx, query, bindVars) + if err != nil { + return nil, err + } + defer cursor.Close() + + for { + if !cursor.HasMore() { + if emailTemplate.Key == "" { + return nil, fmt.Errorf("email template not found") + } + break + } + _, err := cursor.ReadDocument(ctx, &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) { + var emailTemplate models.EmailTemplate + query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.EmailTemplate) + bindVars := map[string]interface{}{ + "event_name": eventName, + } + + cursor, err := p.db.Query(ctx, query, bindVars) + if err != nil { + return nil, err + } + defer cursor.Close() + + for { + if !cursor.HasMore() { + if emailTemplate.Key == "" { + return nil, fmt.Errorf("email template not found") + } + break + } + _, err := cursor.ReadDocument(ctx, &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 { + eventTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate) + _, err := eventTemplateCollection.RemoveDocument(ctx, emailTemplate.ID) + if err != nil { + return err + } + return nil +} diff --git a/server/db/providers/arangodb/provider.go b/server/db/providers/arangodb/provider.go index ac8aa4f..a228cf8 100644 --- a/server/db/providers/arangodb/provider.go +++ b/server/db/providers/arangodb/provider.go @@ -134,6 +134,20 @@ func NewProvider() (*provider, error) { Sparse: true, }) + emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate) + if !emailTemplateCollectionExists { + _, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil) + if err != nil { + return nil, err + } + } + + emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate) + emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{ + Unique: true, + Sparse: true, + }) + return &provider{ db: arangodb, }, err diff --git a/server/db/providers/cassandradb/email_template.go b/server/db/providers/cassandradb/email_template.go new file mode 100644 index 0000000..4fa9109 --- /dev/null +++ b/server/db/providers/cassandradb/email_template.go @@ -0,0 +1,159 @@ +package cassandradb + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/gocql/gocql" + "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() + + existingEmailTemplate, _ := p.GetEmailTemplateByEventName(ctx, emailTemplate.EventName) + if existingEmailTemplate != nil { + return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName) + } + + insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, template, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt) + err := p.db.Query(insertQuery).Exec() + if err != nil { + return nil, err + } + + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + emailTemplate.UpdatedAt = time.Now().Unix() + + 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 := "" + for key, value := range emailTemplateMap { + if key == "_id" { + continue + } + + if key == "_key" { + continue + } + + if value == nil { + updateFields += fmt.Sprintf("%s = null,", key) + continue + } + + valueType := reflect.TypeOf(value) + if valueType.Name() == "string" { + updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string)) + } else { + updateFields += fmt.Sprintf("%s = %v, ", key, value) + } + } + updateFields = strings.Trim(updateFields, " ") + updateFields = strings.TrimSuffix(updateFields, ",") + + query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, updateFields, emailTemplate.ID) + err = p.db.Query(query).Exec() + 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 + + totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.EmailTemplate) + err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total) + if err != nil { + return nil, err + } + + // there is no offset in cassandra + // so we fetch till limit + offset + // and return the results from offset to limit + query := fmt.Sprintf("SELECT id, event_name, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset) + + scanner := p.db.Query(query).Iter().Scanner() + counter := int64(0) + for scanner.Next() { + if counter >= pagination.Offset { + var emailTemplate models.EmailTemplate + err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt) + if err != nil { + return nil, err + } + emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate()) + } + counter++ + } + + 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) { + var emailTemplate models.EmailTemplate + query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID) + err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt) + 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) { + var emailTemplate models.EmailTemplate + query := fmt.Sprintf(`SELECT id, event_name, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName) + err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt) + 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 { + query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID) + err := p.db.Query(query).Exec() + if err != nil { + return err + } + + return nil +} diff --git a/server/db/providers/cassandradb/provider.go b/server/db/providers/cassandradb/provider.go index b47c324..e5a0469 100644 --- a/server/db/providers/cassandradb/provider.go +++ b/server/db/providers/cassandradb/provider.go @@ -204,6 +204,17 @@ func NewProvider() (*provider, error) { return nil, err } + emailTemplateCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, template text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.EmailTemplate) + err = session.Query(emailTemplateCollectionQuery).Exec() + if err != nil { + return nil, err + } + emailTemplateIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_email_template_event_name ON %s.%s (event_name)", KeySpace, models.Collections.EmailTemplate) + err = session.Query(emailTemplateIndexQuery).Exec() + if err != nil { + return nil, err + } + return &provider{ db: session, }, err diff --git a/server/db/providers/mongodb/email_template.go b/server/db/providers/mongodb/email_template.go new file mode 100644 index 0000000..0a0d1d9 --- /dev/null +++ b/server/db/providers/mongodb/email_template.go @@ -0,0 +1,115 @@ +package mongodb + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// 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() + + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + _, err := emailTemplateCollection.InsertOne(ctx, emailTemplate) + if err != nil { + return nil, err + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + emailTemplate.UpdatedAt = time.Now().Unix() + + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + _, err := emailTemplateCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": emailTemplate.ID}}, bson.M{"$set": emailTemplate}, options.MergeUpdateOptions()) + 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) { + var emailTemplates []*model.EmailTemplate + opts := options.Find() + opts.SetLimit(pagination.Limit) + opts.SetSkip(pagination.Offset) + opts.SetSort(bson.M{"created_at": -1}) + + paginationClone := pagination + + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + count, err := emailTemplateCollection.CountDocuments(ctx, bson.M{}, options.Count()) + if err != nil { + return nil, err + } + + paginationClone.Total = count + + cursor, err := emailTemplateCollection.Find(ctx, bson.M{}, opts) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + for cursor.Next(ctx) { + var emailTemplate models.EmailTemplate + err := cursor.Decode(&emailTemplate) + if err != nil { + return nil, err + } + emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate()) + } + + 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) { + var emailTemplate models.EmailTemplate + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + err := emailTemplateCollection.FindOne(ctx, bson.M{"_id": emailTemplateID}).Decode(&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) { + var emailTemplate models.EmailTemplate + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + err := emailTemplateCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&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 { + emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection()) + _, err := emailTemplateCollection.DeleteOne(nil, bson.M{"_id": emailTemplate.ID}, options.Delete()) + if err != nil { + return err + } + + return nil +} diff --git a/server/db/providers/mongodb/provider.go b/server/db/providers/mongodb/provider.go index 303ec75..dd79284 100644 --- a/server/db/providers/mongodb/provider.go +++ b/server/db/providers/mongodb/provider.go @@ -101,6 +101,15 @@ func NewProvider() (*provider, error) { }, }, options.CreateIndexes()) + mongodb.CreateCollection(ctx, models.Collections.EmailTemplate, options.CreateCollection()) + emailTemplateCollection := mongodb.Collection(models.Collections.EmailTemplate, options.Collection()) + emailTemplateCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ + { + Keys: bson.M{"event_name": 1}, + Options: options.Index().SetUnique(true).SetSparse(true), + }, + }, options.CreateIndexes()) + return &provider{ db: mongodb, }, nil diff --git a/server/db/providers/provider_template/email_template.go b/server/db/providers/provider_template/email_template.go new file mode 100644 index 0000000..e6a1f50 --- /dev/null +++ b/server/db/providers/provider_template/email_template.go @@ -0,0 +1,48 @@ +package provider_template + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "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() + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + emailTemplate.UpdatedAt = time.Now().Unix() + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// ListEmailTemplates to list EmailTemplate +func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) { + return nil, nil +} + +// GetEmailTemplateByID to get EmailTemplate by id +func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) { + return nil, nil +} + +// GetEmailTemplateByEventName to get EmailTemplate by event_name +func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) { + return nil, nil +} + +// DeleteEmailTemplate to delete EmailTemplate +func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error { + return nil +} diff --git a/server/db/providers/providers.go b/server/db/providers/providers.go index bd116ee..6f8c074 100644 --- a/server/db/providers/providers.go +++ b/server/db/providers/providers.go @@ -59,4 +59,17 @@ type Provider interface { AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error) // ListWebhookLogs to list webhook logs ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error) + + // AddEmailTemplate to add EmailTemplate + AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) + // UpdateEmailTemplate to update EmailTemplate + UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) + // ListEmailTemplates to list EmailTemplate + ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) + // GetEmailTemplateByID to get EmailTemplate by id + GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) + // GetEmailTemplateByEventName to get EmailTemplate by event_name + GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) + // DeleteEmailTemplate to delete EmailTemplate + DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error } diff --git a/server/db/providers/sql/email_template.go b/server/db/providers/sql/email_template.go new file mode 100644 index 0000000..1a8e0d2 --- /dev/null +++ b/server/db/providers/sql/email_template.go @@ -0,0 +1,100 @@ +package sql + +import ( + "context" + "time" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "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() + + res := p.db.Create(&emailTemplate) + if res.Error != nil { + return nil, res.Error + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// UpdateEmailTemplate to update EmailTemplate +func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) { + emailTemplate.UpdatedAt = time.Now().Unix() + + res := p.db.Save(&emailTemplate) + if res.Error != nil { + return nil, res.Error + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// ListEmailTemplates to list EmailTemplate +func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) { + var emailTemplates []models.EmailTemplate + + result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&emailTemplates) + if result.Error != nil { + return nil, result.Error + } + + var total int64 + totalRes := p.db.Model(&models.EmailTemplate{}).Count(&total) + if totalRes.Error != nil { + return nil, totalRes.Error + } + + paginationClone := pagination + paginationClone.Total = total + + responseEmailTemplates := []*model.EmailTemplate{} + for _, w := range emailTemplates { + responseEmailTemplates = append(responseEmailTemplates, w.AsAPIEmailTemplate()) + } + return &model.EmailTemplates{ + Pagination: &paginationClone, + EmailTemplates: responseEmailTemplates, + }, nil +} + +// GetEmailTemplateByID to get EmailTemplate by id +func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) { + var emailTemplate models.EmailTemplate + + result := p.db.Where("id = ?", emailTemplateID).First(&emailTemplate) + if result.Error != nil { + return nil, result.Error + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// GetEmailTemplateByEventName to get EmailTemplate by event_name +func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) { + var emailTemplate models.EmailTemplate + + result := p.db.Where("event_name = ?", eventName).First(&emailTemplate) + if result.Error != nil { + return nil, result.Error + } + return emailTemplate.AsAPIEmailTemplate(), nil +} + +// DeleteEmailTemplate to delete EmailTemplate +func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error { + result := p.db.Delete(&models.EmailTemplate{ + ID: emailTemplate.ID, + }) + if result.Error != nil { + return result.Error + } + + return nil +} diff --git a/server/db/providers/sql/provider.go b/server/db/providers/sql/provider.go index 62e734c..70dc669 100644 --- a/server/db/providers/sql/provider.go +++ b/server/db/providers/sql/provider.go @@ -60,7 +60,7 @@ func NewProvider() (*provider, error) { return nil, err } - err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}) + err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{}) if err != nil { return nil, err } diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index 736f0f1..fec93e1 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -52,6 +52,19 @@ type ComplexityRoot struct { User func(childComplexity int) int } + EmailTemplate struct { + CreatedAt func(childComplexity int) int + EventName func(childComplexity int) int + ID func(childComplexity int) int + Template func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + EmailTemplates struct { + EmailTemplates func(childComplexity int) int + Pagination func(childComplexity int) int + } + Env struct { AccessTokenExpiryTime func(childComplexity int) int AdminSecret func(childComplexity int) int @@ -130,30 +143,33 @@ type ComplexityRoot struct { } Mutation struct { - AddWebhook func(childComplexity int, params model.AddWebhookRequest) int - AdminLogin func(childComplexity int, params model.AdminLoginInput) int - AdminLogout func(childComplexity int) int - AdminSignup func(childComplexity int, params model.AdminSignupInput) int - DeleteUser func(childComplexity int, params model.DeleteUserInput) int - DeleteWebhook func(childComplexity int, params model.WebhookRequest) int - EnableAccess func(childComplexity int, param model.UpdateAccessInput) int - ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int - GenerateJwtKeys func(childComplexity int, params model.GenerateJWTKeysInput) int - InviteMembers func(childComplexity int, params model.InviteMemberInput) int - Login func(childComplexity int, params model.LoginInput) int - Logout func(childComplexity int) int - MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int - ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int - ResetPassword func(childComplexity int, params model.ResetPasswordInput) int - Revoke func(childComplexity int, params model.OAuthRevokeInput) int - RevokeAccess func(childComplexity int, param model.UpdateAccessInput) int - Signup func(childComplexity int, params model.SignUpInput) int - TestEndpoint func(childComplexity int, params model.TestEndpointRequest) int - UpdateEnv func(childComplexity int, params model.UpdateEnvInput) int - UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int - UpdateUser func(childComplexity int, params model.UpdateUserInput) int - UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int - VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int + AddEmailTemplate func(childComplexity int, params model.AddEmailTemplateRequest) int + AddWebhook func(childComplexity int, params model.AddWebhookRequest) int + AdminLogin func(childComplexity int, params model.AdminLoginInput) int + AdminLogout func(childComplexity int) int + AdminSignup func(childComplexity int, params model.AdminSignupInput) int + DeleteEmailTemplate func(childComplexity int, params model.DeleteEmailTemplateRequest) int + DeleteUser func(childComplexity int, params model.DeleteUserInput) int + DeleteWebhook func(childComplexity int, params model.WebhookRequest) int + EnableAccess func(childComplexity int, param model.UpdateAccessInput) int + ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int + GenerateJwtKeys func(childComplexity int, params model.GenerateJWTKeysInput) int + InviteMembers func(childComplexity int, params model.InviteMemberInput) int + Login func(childComplexity int, params model.LoginInput) int + Logout func(childComplexity int) int + MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int + ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int + ResetPassword func(childComplexity int, params model.ResetPasswordInput) int + Revoke func(childComplexity int, params model.OAuthRevokeInput) int + RevokeAccess func(childComplexity int, param model.UpdateAccessInput) int + Signup func(childComplexity int, params model.SignUpInput) int + TestEndpoint func(childComplexity int, params model.TestEndpointRequest) int + UpdateEmailTemplate func(childComplexity int, params model.UpdateEmailTemplateRequest) int + UpdateEnv func(childComplexity int, params model.UpdateEnvInput) int + UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int + UpdateUser func(childComplexity int, params model.UpdateUserInput) int + UpdateWebhook func(childComplexity int, params model.UpdateWebhookRequest) int + VerifyEmail func(childComplexity int, params model.VerifyEmailInput) int } Pagination struct { @@ -165,6 +181,7 @@ type ComplexityRoot struct { Query struct { AdminSession func(childComplexity int) int + EmailTemplates func(childComplexity int, params *model.PaginatedInput) int Env func(childComplexity int) int Meta func(childComplexity int) int Profile func(childComplexity int) int @@ -289,6 +306,9 @@ type MutationResolver interface { UpdateWebhook(ctx context.Context, params model.UpdateWebhookRequest) (*model.Response, error) DeleteWebhook(ctx context.Context, params model.WebhookRequest) (*model.Response, error) TestEndpoint(ctx context.Context, params model.TestEndpointRequest) (*model.TestEndpointResponse, error) + AddEmailTemplate(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) + UpdateEmailTemplate(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) + DeleteEmailTemplate(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) } type QueryResolver interface { Meta(ctx context.Context) (*model.Meta, error) @@ -302,6 +322,7 @@ type QueryResolver interface { Webhook(ctx context.Context, params model.WebhookRequest) (*model.Webhook, error) Webhooks(ctx context.Context, params *model.PaginatedInput) (*model.Webhooks, error) WebhookLogs(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) + EmailTemplates(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) } type executableSchema struct { @@ -361,6 +382,55 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthResponse.User(childComplexity), true + case "EmailTemplate.created_at": + if e.complexity.EmailTemplate.CreatedAt == nil { + break + } + + return e.complexity.EmailTemplate.CreatedAt(childComplexity), true + + case "EmailTemplate.event_name": + if e.complexity.EmailTemplate.EventName == nil { + break + } + + return e.complexity.EmailTemplate.EventName(childComplexity), true + + case "EmailTemplate.id": + if e.complexity.EmailTemplate.ID == nil { + break + } + + return e.complexity.EmailTemplate.ID(childComplexity), true + + case "EmailTemplate.template": + if e.complexity.EmailTemplate.Template == nil { + break + } + + return e.complexity.EmailTemplate.Template(childComplexity), true + + case "EmailTemplate.updated_at": + if e.complexity.EmailTemplate.UpdatedAt == nil { + break + } + + return e.complexity.EmailTemplate.UpdatedAt(childComplexity), true + + case "EmailTemplates.EmailTemplates": + if e.complexity.EmailTemplates.EmailTemplates == nil { + break + } + + return e.complexity.EmailTemplates.EmailTemplates(childComplexity), true + + case "EmailTemplates.pagination": + if e.complexity.EmailTemplates.Pagination == nil { + break + } + + return e.complexity.EmailTemplates.Pagination(childComplexity), true + case "Env.ACCESS_TOKEN_EXPIRY_TIME": if e.complexity.Env.AccessTokenExpiryTime == nil { break @@ -816,6 +886,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Meta.Version(childComplexity), true + case "Mutation._add_email_template": + if e.complexity.Mutation.AddEmailTemplate == nil { + break + } + + args, err := ec.field_Mutation__add_email_template_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AddEmailTemplate(childComplexity, args["params"].(model.AddEmailTemplateRequest)), true + case "Mutation._add_webhook": if e.complexity.Mutation.AddWebhook == nil { break @@ -859,6 +941,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.AdminSignup(childComplexity, args["params"].(model.AdminSignupInput)), true + case "Mutation._delete_email_template": + if e.complexity.Mutation.DeleteEmailTemplate == nil { + break + } + + args, err := ec.field_Mutation__delete_email_template_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteEmailTemplate(childComplexity, args["params"].(model.DeleteEmailTemplateRequest)), true + case "Mutation._delete_user": if e.complexity.Mutation.DeleteUser == nil { break @@ -1034,6 +1128,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.TestEndpoint(childComplexity, args["params"].(model.TestEndpointRequest)), true + case "Mutation._update_email_template": + if e.complexity.Mutation.UpdateEmailTemplate == nil { + break + } + + args, err := ec.field_Mutation__update_email_template_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateEmailTemplate(childComplexity, args["params"].(model.UpdateEmailTemplateRequest)), true + case "Mutation._update_env": if e.complexity.Mutation.UpdateEnv == nil { break @@ -1129,6 +1235,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.AdminSession(childComplexity), true + case "Query._email_templates": + if e.complexity.Query.EmailTemplates == nil { + break + } + + args, err := ec.field_Query__email_templates_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.EmailTemplates(childComplexity, args["params"].(*model.PaginatedInput)), true + case "Query._env": if e.complexity.Query.Env == nil { break @@ -1821,6 +1939,54 @@ type GenerateJWTKeysResponse { private_key: String } +type Webhook { + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map + created_at: Int64 + updated_at: Int64 +} + +type Webhooks { + pagination: Pagination! + webhooks: [Webhook!]! +} + +type WebhookLog { + id: ID! + http_status: Int64 + response: String + request: String + webhook_id: ID + created_at: Int64 + updated_at: Int64 +} + +type TestEndpointResponse { + http_status: Int64 + response: Map +} + +type WebhookLogs { + pagination: Pagination! + webhook_logs: [WebhookLog!]! +} + +type EmailTemplate { + id: ID! + event_name: String! + template: String! + created_at: Int64 + updated_at: Int64 +} + +type EmailTemplates { + pagination: Pagination! + EmailTemplates: [EmailTemplate!]! +} + input UpdateEnvInput { ACCESS_TOKEN_EXPIRY_TIME: String ADMIN_SECRET: String @@ -1995,36 +2161,6 @@ input GenerateJWTKeysInput { type: String! } -type Webhook { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map - created_at: Int64 - updated_at: Int64 -} - -type Webhooks { - pagination: Pagination! - webhooks: [Webhook!]! -} - -type WebhookLog { - id: ID! - http_status: Int64 - response: String - request: String - webhook_id: ID - created_at: Int64 - updated_at: Int64 -} - -type TestEndpointResponse { - http_status: Int64 - response: Map -} - input ListWebhookLogRequest { pagination: PaginationInput webhook_id: String @@ -2055,9 +2191,19 @@ input TestEndpointRequest { headers: Map } -type WebhookLogs { - pagination: Pagination! - webhook_logs: [WebhookLog!]! +input AddEmailTemplateRequest { + event_name: String! + template: String! +} + +input UpdateEmailTemplateRequest { + id: ID! + event_name: String + template: String +} + +input DeleteEmailTemplateRequest { + id: ID! } type Mutation { @@ -2086,6 +2232,9 @@ type Mutation { _update_webhook(params: UpdateWebhookRequest!): Response! _delete_webhook(params: WebhookRequest!): Response! _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! + _add_email_template(params: AddEmailTemplateRequest!): Response! + _update_email_template(params: UpdateEmailTemplateRequest!): Response! + _delete_email_template(params: DeleteEmailTemplateRequest!): Response! } type Query { @@ -2101,6 +2250,7 @@ type Query { _webhook(params: WebhookRequest!): Webhook! _webhooks(params: PaginatedInput): Webhooks! _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! + _email_templates(params: PaginatedInput): EmailTemplates! } `, BuiltIn: false}, } @@ -2110,6 +2260,21 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) field_Mutation__add_email_template_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.AddEmailTemplateRequest + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNAddEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAddEmailTemplateRequest(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__add_webhook_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2155,6 +2320,21 @@ func (ec *executionContext) field_Mutation__admin_signup_args(ctx context.Contex return args, nil } +func (ec *executionContext) field_Mutation__delete_email_template_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.DeleteEmailTemplateRequest + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNDeleteEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐDeleteEmailTemplateRequest(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__delete_user_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2260,6 +2440,21 @@ func (ec *executionContext) field_Mutation__test_endpoint_args(ctx context.Conte return args, nil } +func (ec *executionContext) field_Mutation__update_email_template_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.UpdateEmailTemplateRequest + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalNUpdateEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateEmailTemplateRequest(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation__update_env_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2455,6 +2650,21 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query__email_templates_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *model.PaginatedInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query__users_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2793,6 +3003,245 @@ func (ec *executionContext) _AuthResponse_user(ctx context.Context, field graphq return ec.marshalOUser2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } +func (ec *executionContext) _EmailTemplate_id(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplate", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplate_event_name(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplate", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EventName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplate_template(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplate", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Template, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplate_created_at(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplate", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*int64) + fc.Result = res + return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplate_updated_at(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplate) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplate", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.UpdatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*int64) + fc.Result = res + return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplates_pagination(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplates) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplates", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Pagination, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Pagination) + fc.Result = res + return ec.marshalNPagination2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPagination(ctx, field.Selections, res) +} + +func (ec *executionContext) _EmailTemplates_EmailTemplates(ctx context.Context, field graphql.CollectedField, obj *model.EmailTemplates) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "EmailTemplates", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.EmailTemplates, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.EmailTemplate) + fc.Result = res + return ec.marshalNEmailTemplate2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplateᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _Env_ACCESS_TOKEN_EXPIRY_TIME(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5936,6 +6385,132 @@ func (ec *executionContext) _Mutation__test_endpoint(ctx context.Context, field return ec.marshalNTestEndpointResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐTestEndpointResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation__add_email_template(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__add_email_template_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AddEmailTemplate(rctx, args["params"].(model.AddEmailTemplateRequest)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation__update_email_template(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__update_email_template_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateEmailTemplate(rctx, args["params"].(model.UpdateEmailTemplateRequest)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation__delete_email_template(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation__delete_email_template_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().DeleteEmailTemplate(rctx, args["params"].(model.DeleteEmailTemplateRequest)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Response) + fc.Result = res + return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Pagination_limit(ctx context.Context, field graphql.CollectedField, obj *model.Pagination) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6510,6 +7085,48 @@ func (ec *executionContext) _Query__webhook_logs(ctx context.Context, field grap return ec.marshalNWebhookLogs2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐWebhookLogs(ctx, field.Selections, res) } +func (ec *executionContext) _Query__email_templates(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query__email_templates_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().EmailTemplates(rctx, args["params"].(*model.PaginatedInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.EmailTemplates) + fc.Result = res + return ec.marshalNEmailTemplates2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplates(ctx, field.Selections, res) +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -9453,6 +10070,37 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputAddEmailTemplateRequest(ctx context.Context, obj interface{}) (model.AddEmailTemplateRequest, error) { + var it model.AddEmailTemplateRequest + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "event_name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("event_name")) + it.EventName, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + case "template": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("template")) + it.Template, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputAddWebhookRequest(ctx context.Context, obj interface{}) (model.AddWebhookRequest, error) { var it model.AddWebhookRequest asMap := map[string]interface{}{} @@ -9546,6 +10194,29 @@ func (ec *executionContext) unmarshalInputAdminSignupInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputDeleteEmailTemplateRequest(ctx context.Context, obj interface{}) (model.DeleteEmailTemplateRequest, error) { + var it model.DeleteEmailTemplateRequest + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "id": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + it.ID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteUserInput(ctx context.Context, obj interface{}) (model.DeleteUserInput, error) { var it model.DeleteUserInput asMap := map[string]interface{}{} @@ -10162,6 +10833,45 @@ func (ec *executionContext) unmarshalInputUpdateAccessInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputUpdateEmailTemplateRequest(ctx context.Context, obj interface{}) (model.UpdateEmailTemplateRequest, error) { + var it model.UpdateEmailTemplateRequest + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "id": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + it.ID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + case "event_name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("event_name")) + it.EventName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "template": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("template")) + it.Template, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, obj interface{}) (model.UpdateEnvInput, error) { var it model.UpdateEnvInput asMap := map[string]interface{}{} @@ -10896,6 +11606,79 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection return out } +var emailTemplateImplementors = []string{"EmailTemplate"} + +func (ec *executionContext) _EmailTemplate(ctx context.Context, sel ast.SelectionSet, obj *model.EmailTemplate) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, emailTemplateImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EmailTemplate") + case "id": + out.Values[i] = ec._EmailTemplate_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "event_name": + out.Values[i] = ec._EmailTemplate_event_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "template": + out.Values[i] = ec._EmailTemplate_template(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "created_at": + out.Values[i] = ec._EmailTemplate_created_at(ctx, field, obj) + case "updated_at": + out.Values[i] = ec._EmailTemplate_updated_at(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var emailTemplatesImplementors = []string{"EmailTemplates"} + +func (ec *executionContext) _EmailTemplates(ctx context.Context, sel ast.SelectionSet, obj *model.EmailTemplates) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, emailTemplatesImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EmailTemplates") + case "pagination": + out.Values[i] = ec._EmailTemplates_pagination(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "EmailTemplates": + out.Values[i] = ec._EmailTemplates_EmailTemplates(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var envImplementors = []string{"Env"} func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj *model.Env) graphql.Marshaler { @@ -11318,6 +12101,21 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "_add_email_template": + out.Values[i] = ec._Mutation__add_email_template(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "_update_email_template": + out.Values[i] = ec._Mutation__update_email_template(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "_delete_email_template": + out.Values[i] = ec._Mutation__delete_email_template(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -11540,6 +12338,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "_email_templates": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query__email_templates(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -12207,6 +13019,11 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o // region ***************************** type.gotpl ***************************** +func (ec *executionContext) unmarshalNAddEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAddEmailTemplateRequest(ctx context.Context, v interface{}) (model.AddEmailTemplateRequest, error) { + res, err := ec.unmarshalInputAddEmailTemplateRequest(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNAddWebhookRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAddWebhookRequest(ctx context.Context, v interface{}) (model.AddWebhookRequest, error) { res, err := ec.unmarshalInputAddWebhookRequest(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -12251,11 +13068,84 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) unmarshalNDeleteEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐDeleteEmailTemplateRequest(ctx context.Context, v interface{}) (model.DeleteEmailTemplateRequest, error) { + res, err := ec.unmarshalInputDeleteEmailTemplateRequest(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNDeleteUserInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐDeleteUserInput(ctx context.Context, v interface{}) (model.DeleteUserInput, error) { res, err := ec.unmarshalInputDeleteUserInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalNEmailTemplate2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplateᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.EmailTemplate) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNEmailTemplate2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplate(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNEmailTemplate2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplate(ctx context.Context, sel ast.SelectionSet, v *model.EmailTemplate) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._EmailTemplate(ctx, sel, v) +} + +func (ec *executionContext) marshalNEmailTemplates2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplates(ctx context.Context, sel ast.SelectionSet, v model.EmailTemplates) graphql.Marshaler { + return ec._EmailTemplates(ctx, sel, &v) +} + +func (ec *executionContext) marshalNEmailTemplates2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEmailTemplates(ctx context.Context, sel ast.SelectionSet, v *model.EmailTemplates) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._EmailTemplates(ctx, sel, v) +} + func (ec *executionContext) marshalNEnv2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐEnv(ctx context.Context, sel ast.SelectionSet, v model.Env) graphql.Marshaler { return ec._Env(ctx, sel, &v) } @@ -12472,6 +13362,11 @@ func (ec *executionContext) unmarshalNUpdateAccessInput2githubᚗcomᚋauthorize return res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalNUpdateEmailTemplateRequest2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateEmailTemplateRequest(ctx context.Context, v interface{}) (model.UpdateEmailTemplateRequest, error) { + res, err := ec.unmarshalInputUpdateEmailTemplateRequest(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalNUpdateEnvInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUpdateEnvInput(ctx context.Context, v interface{}) (model.UpdateEnvInput, error) { res, err := ec.unmarshalInputUpdateEnvInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index 7e2c0ed..195f428 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -2,6 +2,11 @@ package model +type AddEmailTemplateRequest struct { + EventName string `json:"event_name"` + Template string `json:"template"` +} + type AddWebhookRequest struct { EventName string `json:"event_name"` Endpoint string `json:"endpoint"` @@ -26,10 +31,27 @@ type AuthResponse struct { User *User `json:"user"` } +type DeleteEmailTemplateRequest struct { + ID string `json:"id"` +} + type DeleteUserInput struct { Email string `json:"email"` } +type EmailTemplate struct { + ID string `json:"id"` + EventName string `json:"event_name"` + Template string `json:"template"` + CreatedAt *int64 `json:"created_at"` + UpdatedAt *int64 `json:"updated_at"` +} + +type EmailTemplates struct { + Pagination *Pagination `json:"pagination"` + EmailTemplates []*EmailTemplate `json:"EmailTemplates"` +} + type Env struct { AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"` AdminSecret *string `json:"ADMIN_SECRET"` @@ -214,6 +236,12 @@ type UpdateAccessInput struct { UserID string `json:"user_id"` } +type UpdateEmailTemplateRequest struct { + ID string `json:"id"` + EventName *string `json:"event_name"` + Template *string `json:"template"` +} + type UpdateEnvInput struct { AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"` AdminSecret *string `json:"ADMIN_SECRET"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 3930f51..bd5845a 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -150,6 +150,54 @@ type GenerateJWTKeysResponse { private_key: String } +type Webhook { + id: ID! + event_name: String + endpoint: String + enabled: Boolean + headers: Map + created_at: Int64 + updated_at: Int64 +} + +type Webhooks { + pagination: Pagination! + webhooks: [Webhook!]! +} + +type WebhookLog { + id: ID! + http_status: Int64 + response: String + request: String + webhook_id: ID + created_at: Int64 + updated_at: Int64 +} + +type TestEndpointResponse { + http_status: Int64 + response: Map +} + +type WebhookLogs { + pagination: Pagination! + webhook_logs: [WebhookLog!]! +} + +type EmailTemplate { + id: ID! + event_name: String! + template: String! + created_at: Int64 + updated_at: Int64 +} + +type EmailTemplates { + pagination: Pagination! + EmailTemplates: [EmailTemplate!]! +} + input UpdateEnvInput { ACCESS_TOKEN_EXPIRY_TIME: String ADMIN_SECRET: String @@ -324,36 +372,6 @@ input GenerateJWTKeysInput { type: String! } -type Webhook { - id: ID! - event_name: String - endpoint: String - enabled: Boolean - headers: Map - created_at: Int64 - updated_at: Int64 -} - -type Webhooks { - pagination: Pagination! - webhooks: [Webhook!]! -} - -type WebhookLog { - id: ID! - http_status: Int64 - response: String - request: String - webhook_id: ID - created_at: Int64 - updated_at: Int64 -} - -type TestEndpointResponse { - http_status: Int64 - response: Map -} - input ListWebhookLogRequest { pagination: PaginationInput webhook_id: String @@ -384,9 +402,19 @@ input TestEndpointRequest { headers: Map } -type WebhookLogs { - pagination: Pagination! - webhook_logs: [WebhookLog!]! +input AddEmailTemplateRequest { + event_name: String! + template: String! +} + +input UpdateEmailTemplateRequest { + id: ID! + event_name: String + template: String +} + +input DeleteEmailTemplateRequest { + id: ID! } type Mutation { @@ -415,6 +443,9 @@ type Mutation { _update_webhook(params: UpdateWebhookRequest!): Response! _delete_webhook(params: WebhookRequest!): Response! _test_endpoint(params: TestEndpointRequest!): TestEndpointResponse! + _add_email_template(params: AddEmailTemplateRequest!): Response! + _update_email_template(params: UpdateEmailTemplateRequest!): Response! + _delete_email_template(params: DeleteEmailTemplateRequest!): Response! } type Query { @@ -430,4 +461,5 @@ type Query { _webhook(params: WebhookRequest!): Webhook! _webhooks(params: PaginatedInput): Webhooks! _webhook_logs(params: ListWebhookLogRequest): WebhookLogs! + _email_templates(params: PaginatedInput): EmailTemplates! } diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index 59398f9..a0fea39 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -107,6 +107,18 @@ func (r *mutationResolver) TestEndpoint(ctx context.Context, params model.TestEn return resolvers.TestEndpointResolver(ctx, params) } +func (r *mutationResolver) AddEmailTemplate(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) { + return resolvers.AddEmailTemplateResolver(ctx, params) +} + +func (r *mutationResolver) UpdateEmailTemplate(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) { + return resolvers.UpdateEmailTemplateResolver(ctx, params) +} + +func (r *mutationResolver) DeleteEmailTemplate(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) { + return resolvers.DeleteEmailTemplateResolver(ctx, params) +} + func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { return resolvers.MetaResolver(ctx) } @@ -151,11 +163,17 @@ func (r *queryResolver) WebhookLogs(ctx context.Context, params *model.ListWebho return resolvers.WebhookLogsResolver(ctx, params) } +func (r *queryResolver) EmailTemplates(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) { + return resolvers.EmailTemplatesResolver(ctx, params) +} + // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -type mutationResolver struct{ *Resolver } -type queryResolver struct{ *Resolver } +type ( + mutationResolver struct{ *Resolver } + queryResolver struct{ *Resolver } +) diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go index a19fa46..7e316a3 100644 --- a/server/handlers/oauth_callback.go +++ b/server/handlers/oauth_callback.go @@ -320,12 +320,60 @@ func processGithubUserInfo(code string) (models.User, error) { } picture := userRawData["avatar_url"] + email := userRawData["email"] + + if email == "" { + type GithubUserEmails struct { + Email string `json:"email"` + Primary bool `json:"primary"` + } + + // fetch using /users/email endpoint + req, err := http.NewRequest("GET", constants.GithubUserEmails, nil) + if err != nil { + log.Debug("Failed to create github emails request: ", err) + return user, fmt.Errorf("error creating github user info request: %s", err.Error()) + } + req.Header = http.Header{ + "Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)}, + } + + response, err := client.Do(req) + if err != nil { + log.Debug("Failed to request github user email: ", err) + return user, err + } + + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Debug("Failed to read github user email response body: ", err) + return user, fmt.Errorf("failed to read github response body: %s", err.Error()) + } + if response.StatusCode >= 400 { + log.Debug("Failed to request github user email: ", string(body)) + return user, fmt.Errorf("failed to request github user info: %s", string(body)) + } + + emailData := []GithubUserEmails{} + err = json.Unmarshal(body, &emailData) + if err != nil { + log.Debug("Failed to parse github user email: ", err) + } + + for _, userEmail := range emailData { + email = userEmail.Email + if userEmail.Primary { + break + } + } + } user = models.User{ GivenName: &firstName, FamilyName: &lastName, Picture: &picture, - Email: userRawData["email"], + Email: email, } return user, nil diff --git a/server/resolvers/add_email_template.go b/server/resolvers/add_email_template.go new file mode 100644 index 0000000..1cba02f --- /dev/null +++ b/server/resolvers/add_email_template.go @@ -0,0 +1,53 @@ +package resolvers + +import ( + "context" + "fmt" + "strings" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" + "github.com/authorizerdev/authorizer/server/validators" + log "github.com/sirupsen/logrus" +) + +// TODO add template validator + +// AddEmailTemplateResolver resolver for add email template mutation +func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + log.Debug("Failed to get GinContext: ", err) + return nil, err + } + + if !token.IsSuperAdmin(gc) { + log.Debug("Not logged in as super admin") + return nil, fmt.Errorf("unauthorized") + } + + if !validators.IsValidEmailTemplateEventName(params.EventName) { + log.Debug("Invalid Event Name: ", params.EventName) + return nil, fmt.Errorf("invalid event name %s", params.EventName) + } + + if strings.TrimSpace(params.Template) == "" { + return nil, fmt.Errorf("empty template not allowed") + } + + _, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{ + EventName: params.EventName, + Template: params.Template, + }) + if err != nil { + log.Debug("Failed to add email template: ", err) + return nil, err + } + + return &model.Response{ + Message: `Email template added successfully`, + }, nil +} diff --git a/server/resolvers/add_webhook.go b/server/resolvers/add_webhook.go index fd80578..d8c768a 100644 --- a/server/resolvers/add_webhook.go +++ b/server/resolvers/add_webhook.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" @@ -32,6 +33,11 @@ func AddWebhookResolver(ctx context.Context, params model.AddWebhookRequest) (*m return nil, fmt.Errorf("invalid event name %s", params.EventName) } + if strings.TrimSpace(params.Endpoint) == "" { + log.Debug("empty endpoint not allowed") + return nil, fmt.Errorf("empty endpoint not allowed") + } + headerBytes, err := json.Marshal(params.Headers) if err != nil { return nil, err diff --git a/server/resolvers/delete_email_template.go b/server/resolvers/delete_email_template.go new file mode 100644 index 0000000..92a6172 --- /dev/null +++ b/server/resolvers/delete_email_template.go @@ -0,0 +1,49 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" + log "github.com/sirupsen/logrus" +) + +// DeleteEmailTemplateResolver resolver to delete email template and its relevant logs +func DeleteEmailTemplateResolver(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + log.Debug("Failed to get GinContext: ", err) + return nil, err + } + + if !token.IsSuperAdmin(gc) { + log.Debug("Not logged in as super admin") + return nil, fmt.Errorf("unauthorized") + } + + if params.ID == "" { + log.Debug("email template is required") + return nil, fmt.Errorf("email template ID required") + } + + log := log.WithField("email_template_id", params.ID) + + emailTemplate, err := db.Provider.GetEmailTemplateByID(ctx, params.ID) + if err != nil { + log.Debug("failed to get email template: ", err) + return nil, err + } + + err = db.Provider.DeleteEmailTemplate(ctx, emailTemplate) + if err != nil { + log.Debug("failed to delete email template: ", err) + return nil, err + } + + return &model.Response{ + Message: "Email templated deleted successfully", + }, nil +} diff --git a/server/resolvers/email_templates.go b/server/resolvers/email_templates.go new file mode 100644 index 0000000..7230400 --- /dev/null +++ b/server/resolvers/email_templates.go @@ -0,0 +1,35 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" + log "github.com/sirupsen/logrus" +) + +// EmailTemplatesResolver resolver for getting the list of email templates based on pagination +func EmailTemplatesResolver(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) { + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + log.Debug("Failed to get GinContext: ", err) + return nil, err + } + + if !token.IsSuperAdmin(gc) { + log.Debug("Not logged in as super admin") + return nil, fmt.Errorf("unauthorized") + } + + pagination := utils.GetPagination(params) + + emailTemplates, err := db.Provider.ListEmailTemplate(ctx, pagination) + if err != nil { + log.Debug("failed to get email templates: ", err) + return nil, err + } + return emailTemplates, nil +} diff --git a/server/resolvers/update_email_template.go b/server/resolvers/update_email_template.go new file mode 100644 index 0000000..95362c0 --- /dev/null +++ b/server/resolvers/update_email_template.go @@ -0,0 +1,70 @@ +package resolvers + +import ( + "context" + "fmt" + "strings" + + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" + "github.com/authorizerdev/authorizer/server/validators" + log "github.com/sirupsen/logrus" +) + +// TODO add template validator + +// UpdateEmailTemplateResolver resolver for update email template mutation +func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) { + gc, err := utils.GinContextFromContext(ctx) + if err != nil { + log.Debug("Failed to get GinContext: ", err) + return nil, err + } + + if !token.IsSuperAdmin(gc) { + log.Debug("Not logged in as super admin") + return nil, fmt.Errorf("unauthorized") + } + + emailTemplate, err := db.Provider.GetEmailTemplateByID(ctx, params.ID) + if err != nil { + log.Debug("failed to get email template: ", err) + return nil, err + } + + emailTemplateDetails := models.EmailTemplate{ + ID: emailTemplate.ID, + Key: emailTemplate.ID, + EventName: emailTemplate.EventName, + CreatedAt: refs.Int64Value(emailTemplate.CreatedAt), + } + + if params.EventName != nil && emailTemplateDetails.EventName != refs.StringValue(params.EventName) { + if isValid := validators.IsValidEmailTemplateEventName(refs.StringValue(params.EventName)); !isValid { + log.Debug("invalid event name: ", refs.StringValue(params.EventName)) + return nil, fmt.Errorf("invalid event name %s", refs.StringValue(params.EventName)) + } + emailTemplateDetails.EventName = refs.StringValue(params.EventName) + } + + if params.Template != nil && emailTemplateDetails.Template != refs.StringValue(params.Template) { + if strings.TrimSpace(refs.StringValue(params.Template)) == "" { + log.Debug("empty template not allowed") + return nil, fmt.Errorf("empty template not allowed") + } + emailTemplateDetails.Template = refs.StringValue(params.Template) + } + + _, err = db.Provider.UpdateEmailTemplate(ctx, emailTemplateDetails) + if err != nil { + return nil, err + } + + return &model.Response{ + Message: `Email template updated successfully.`, + }, nil +} diff --git a/server/resolvers/update_webhook.go b/server/resolvers/update_webhook.go index d9ea6b7..f1d1009 100644 --- a/server/resolvers/update_webhook.go +++ b/server/resolvers/update_webhook.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db/models" @@ -50,7 +51,7 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques EndPoint: refs.StringValue(webhook.Endpoint), Enabled: refs.BoolValue(webhook.Enabled), Headers: headersString, - CreatedAt: *webhook.CreatedAt, + CreatedAt: refs.Int64Value(webhook.CreatedAt), } if params.EventName != nil && webhookDetails.EventName != refs.StringValue(params.EventName) { @@ -62,7 +63,11 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques } if params.Endpoint != nil && webhookDetails.EndPoint != refs.StringValue(params.Endpoint) { - webhookDetails.EventName = refs.StringValue(params.EventName) + if strings.TrimSpace(refs.StringValue(params.Endpoint)) == "" { + log.Debug("empty endpoint not allowed") + return nil, fmt.Errorf("empty endpoint not allowed") + } + webhookDetails.EndPoint = refs.StringValue(params.Endpoint) } if params.Enabled != nil && webhookDetails.Enabled != refs.BoolValue(params.Enabled) { diff --git a/server/resolvers/webhooks.go b/server/resolvers/webhooks.go index df6dd30..5a6ccbb 100644 --- a/server/resolvers/webhooks.go +++ b/server/resolvers/webhooks.go @@ -28,7 +28,7 @@ func WebhooksResolver(ctx context.Context, params *model.PaginatedInput) (*model webhooks, err := db.Provider.ListWebhook(ctx, pagination) if err != nil { - log.Debug("failed to get webhook logs: ", err) + log.Debug("failed to get webhooks: ", err) return nil, err } return webhooks, nil diff --git a/server/test/add_email_template_test.go b/server/test/add_email_template_test.go new file mode 100644 index 0000000..743ed12 --- /dev/null +++ b/server/test/add_email_template_test.go @@ -0,0 +1,58 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/stretchr/testify/assert" +) + +func addEmailTemplateTest(t *testing.T, s TestSetup) { + t.Helper() + t.Run("should add email templates", func(t *testing.T) { + req, ctx := createContext(s) + adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + assert.NoError(t, err) + h, err := crypto.EncryptPassword(adminSecret) + assert.NoError(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) + + t.Run("should not add email template for invalid event type", func(t *testing.T) { + emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{ + EventName: "test", + }) + assert.Error(t, err) + assert.Nil(t, emailTemplate) + }) + + t.Run("should not add email template for empty template", func(t *testing.T) { + emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{ + EventName: s.TestInfo.TestEmailTemplateEventTypes[0], + Template: " ", + }) + assert.Error(t, err) + assert.Nil(t, emailTemplate) + }) + for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes { + t.Run("should add email template for "+eventType, func(t *testing.T) { + emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{ + EventName: eventType, + Template: `Test email`, + }) + assert.NoError(t, err) + assert.NotNil(t, emailTemplate) + assert.NotEmpty(t, emailTemplate.Message) + + et, err := db.Provider.GetEmailTemplateByEventName(ctx, eventType) + assert.NoError(t, err) + assert.Equal(t, et.EventName, eventType) + }) + } + }) +} diff --git a/server/test/add_webhook_test.go b/server/test/add_webhook_test.go index 7b99a54..3068740 100644 --- a/server/test/add_webhook_test.go +++ b/server/test/add_webhook_test.go @@ -22,7 +22,7 @@ func addWebhookTest(t *testing.T, s TestSetup) { assert.NoError(t, err) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) - for _, eventType := range s.TestInfo.TestEventTypes { + for _, eventType := range s.TestInfo.TestWebhookEventTypes { webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{ EventName: eventType, Endpoint: s.TestInfo.WebhookEndpoint, diff --git a/server/test/delete_email_template_test.go b/server/test/delete_email_template_test.go new file mode 100644 index 0000000..ef79db9 --- /dev/null +++ b/server/test/delete_email_template_test.go @@ -0,0 +1,52 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/stretchr/testify/assert" +) + +func deleteEmailTemplateTest(t *testing.T, s TestSetup) { + t.Helper() + t.Run("should delete email templates", func(t *testing.T) { + req, ctx := createContext(s) + adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + assert.NoError(t, err) + h, err := crypto.EncryptPassword(adminSecret) + assert.NoError(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) + + // get all email templates + emailTemplates, err := db.Provider.ListEmailTemplate(ctx, model.Pagination{ + Limit: 10, + Page: 1, + Offset: 0, + }) + assert.NoError(t, err) + + for _, e := range emailTemplates.EmailTemplates { + res, err := resolvers.DeleteEmailTemplateResolver(ctx, model.DeleteEmailTemplateRequest{ + ID: e.ID, + }) + + assert.NoError(t, err) + assert.NotNil(t, res) + assert.NotEmpty(t, res.Message) + } + + emailTemplates, err = db.Provider.ListEmailTemplate(ctx, model.Pagination{ + Limit: 10, + Page: 1, + Offset: 0, + }) + assert.NoError(t, err) + assert.Len(t, emailTemplates.EmailTemplates, 0) + }) +} diff --git a/server/test/email_templates_test.go b/server/test/email_templates_test.go new file mode 100644 index 0000000..b5c9083 --- /dev/null +++ b/server/test/email_templates_test.go @@ -0,0 +1,29 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/stretchr/testify/assert" +) + +func emailTemplatesTest(t *testing.T, s TestSetup) { + t.Helper() + t.Run("should get email templates", func(t *testing.T) { + req, ctx := createContext(s) + adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + assert.NoError(t, err) + h, err := crypto.EncryptPassword(adminSecret) + assert.NoError(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) + + emailTemplates, err := resolvers.EmailTemplatesResolver(ctx, nil) + assert.NoError(t, err) + assert.NotEmpty(t, emailTemplates) + assert.Len(t, emailTemplates.EmailTemplates, len(s.TestInfo.TestEmailTemplateEventTypes)) + }) +} diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 44feaff..1a49632 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -3,20 +3,39 @@ package test import ( "context" "os" + "strings" "testing" + "time" "github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/env" "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/utils" ) 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.DbTypeSqlite: "../../test.db", + constants.DbTypeArangodb: "http://localhost:8529", + constants.DbTypeMongodb: "mongodb://localhost:27017", + constants.DbTypeScyllaDB: "127.0.0.1:9042", + } + + testDBs := strings.Split(os.Getenv("TEST_DBS"), ",") + t.Log("Running tests for following dbs: ", testDBs) + for dbType := range databases { + if !utils.StringSliceContains(testDBs, dbType) { + delete(databases, dbType) + } + } + + if utils.StringSliceContains(testDBs, constants.DbTypeSqlite) && len(testDBs) == 1 { + // do nothing + } else { + t.Log("waiting for docker containers to spun up") + // wait for docker containers to spun up + time.Sleep(30 * time.Second) } testDb := "authorizer_test" @@ -75,6 +94,10 @@ func TestResolvers(t *testing.T) { revokeAccessTest(t, s) enableAccessTest(t, s) generateJWTkeyTest(t, s) + addEmailTemplateTest(t, s) + updateEmailTemplateTest(t, s) + emailTemplatesTest(t, s) + deleteEmailTemplateTest(t, s) // user resolvers tests loginTests(t, s) diff --git a/server/test/test.go b/server/test/test.go index af27fb4..b2288ff 100644 --- a/server/test/test.go +++ b/server/test/test.go @@ -21,10 +21,11 @@ import ( // common user data to share across tests type TestData struct { - Email string - Password string - WebhookEndpoint string - TestEventTypes []string + Email string + Password string + WebhookEndpoint string + TestWebhookEventTypes []string + TestEmailTemplateEventTypes []string } type TestSetup struct { @@ -76,10 +77,11 @@ func createContext(s TestSetup) (*http.Request, context.Context) { func testSetup() TestSetup { testData := TestData{ - Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()), - Password: "Test@123", - WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook", - TestEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent}, + Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()), + Password: "Test@123", + WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook", + TestWebhookEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent}, + TestEmailTemplateEventTypes: []string{constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeForgotPassword, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail}, } err := os.Setenv(constants.EnvKeyEnvPath, "../../.env.test") diff --git a/server/test/update_email_template_test.go b/server/test/update_email_template_test.go new file mode 100644 index 0000000..1f23ff4 --- /dev/null +++ b/server/test/update_email_template_test.go @@ -0,0 +1,46 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/crypto" + "github.com/authorizerdev/authorizer/server/db" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/memorystore" + "github.com/authorizerdev/authorizer/server/refs" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/stretchr/testify/assert" +) + +func updateEmailTemplateTest(t *testing.T, s TestSetup) { + t.Helper() + t.Run("should update email template", func(t *testing.T) { + req, ctx := createContext(s) + adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) + assert.NoError(t, err) + h, err := crypto.EncryptPassword(adminSecret) + assert.NoError(t, err) + req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) + // get email template + emailTemplate, err := db.Provider.GetEmailTemplateByEventName(ctx, constants.VerificationTypeBasicAuthSignup) + assert.NoError(t, err) + assert.NotNil(t, emailTemplate) + + res, err := resolvers.UpdateEmailTemplateResolver(ctx, model.UpdateEmailTemplateRequest{ + ID: emailTemplate.ID, + Template: refs.NewStringRef("Updated test template"), + }) + + assert.NoError(t, err) + assert.NotEmpty(t, res) + assert.NotEmpty(t, res.Message) + + updatedEmailTemplate, err := db.Provider.GetEmailTemplateByEventName(ctx, constants.VerificationTypeBasicAuthSignup) + assert.NoError(t, err) + assert.NotNil(t, updatedEmailTemplate) + assert.Equal(t, emailTemplate.ID, updatedEmailTemplate.ID) + assert.Equal(t, updatedEmailTemplate.Template, "Updated test template") + }) +} diff --git a/server/test/update_webhook_test.go b/server/test/update_webhook_test.go index cb96b72..07f658c 100644 --- a/server/test/update_webhook_test.go +++ b/server/test/update_webhook_test.go @@ -33,9 +33,10 @@ func updateWebhookTest(t *testing.T, s TestSetup) { } res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{ - ID: webhook.ID, - Headers: webhook.Headers, - Enabled: refs.NewBoolRef(false), + ID: webhook.ID, + Headers: webhook.Headers, + Enabled: refs.NewBoolRef(false), + Endpoint: refs.NewStringRef("https://sometest.com"), }) assert.NoError(t, err) @@ -47,17 +48,18 @@ func updateWebhookTest(t *testing.T, s TestSetup) { assert.NotNil(t, updatedWebhook) assert.Equal(t, webhook.ID, updatedWebhook.ID) assert.Equal(t, refs.StringValue(webhook.EventName), refs.StringValue(updatedWebhook.EventName)) - assert.Equal(t, refs.StringValue(webhook.Endpoint), refs.StringValue(updatedWebhook.Endpoint)) assert.Len(t, updatedWebhook.Headers, 1) assert.False(t, refs.BoolValue(updatedWebhook.Enabled)) for key, val := range updatedWebhook.Headers { assert.Equal(t, val, webhook.Headers[key]) } + assert.Equal(t, refs.StringValue(updatedWebhook.Endpoint), "https://sometest.com") res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{ - ID: webhook.ID, - Headers: webhook.Headers, - Enabled: refs.NewBoolRef(true), + ID: webhook.ID, + Headers: webhook.Headers, + Enabled: refs.NewBoolRef(true), + Endpoint: refs.NewStringRef(s.TestInfo.WebhookEndpoint), }) assert.NoError(t, err) assert.NotEmpty(t, res) diff --git a/server/test/webhooks_test.go b/server/test/webhooks_test.go index 7b64bd7..b4ec561 100644 --- a/server/test/webhooks_test.go +++ b/server/test/webhooks_test.go @@ -24,6 +24,6 @@ func webhooksTest(t *testing.T, s TestSetup) { webhooks, err := resolvers.WebhooksResolver(ctx, nil) assert.NoError(t, err) assert.NotEmpty(t, webhooks) - assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestEventTypes)) + assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes)) }) } diff --git a/server/validators/email_template.go b/server/validators/email_template.go new file mode 100644 index 0000000..1dd0766 --- /dev/null +++ b/server/validators/email_template.go @@ -0,0 +1,12 @@ +package validators + +import "github.com/authorizerdev/authorizer/server/constants" + +// IsValidEmailTemplateEventName function to validate email template events +func IsValidEmailTemplateEventName(eventName string) bool { + if eventName != constants.VerificationTypeBasicAuthSignup && eventName != constants.VerificationTypeForgotPassword && eventName != constants.VerificationTypeMagicLinkLogin && eventName != constants.VerificationTypeUpdateEmail { + return false + } + + return true +}