Skip to content

Commit 41468b5

Browse files
authored
Merge pull request #201 from authorizerdev/feat/add-email-template-apis
feat: add email template apis
2 parents 2cce1c4 + a102924 commit 41468b5

39 files changed

+2319
-187
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,16 @@ build-dashboard:
1010
clean:
1111
rm -rf build
1212
test:
13-
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && go test -p 1 -v ./test
13+
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
14+
test-all-db:
15+
rm -rf server/test/test.db && rm -rf test.db
16+
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
17+
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
18+
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
19+
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
20+
docker rm -vf authorizer_mongodb_db
21+
docker rm -vf authorizer_scylla_db
22+
docker rm -vf authorizer_arangodb
1423
generate:
1524
cd server && go get github.com/99designs/gqlgen/[email protected] && go run github.com/99designs/gqlgen generate
1625

server/constants/oauth_info_urls.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const (
88
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
99
// 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
1010
GithubUserInfoURL = "https://api.github.com/user"
11+
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
12+
GithubUserEmails = "https://api/github.com/user/emails"
13+
1114
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
1215
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
1316
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package models
2+
3+
import (
4+
"strings"
5+
6+
"github.com/authorizerdev/authorizer/server/graph/model"
7+
"github.com/authorizerdev/authorizer/server/refs"
8+
)
9+
10+
// EmailTemplate model for database
11+
type EmailTemplate struct {
12+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
13+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
14+
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
15+
Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"`
16+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
17+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
18+
}
19+
20+
// AsAPIEmailTemplate to return email template as graphql response object
21+
func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
22+
id := e.ID
23+
if strings.Contains(id, Collections.EmailTemplate+"/") {
24+
id = strings.TrimPrefix(id, Collections.EmailTemplate+"/")
25+
}
26+
return &model.EmailTemplate{
27+
ID: id,
28+
EventName: e.EventName,
29+
Template: e.Template,
30+
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
31+
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
32+
}
33+
}

server/db/models/model.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type CollectionList struct {
88
Env string
99
Webhook string
1010
WebhookLog string
11+
EmailTemplate string
1112
}
1213

1314
var (
@@ -21,5 +22,6 @@ var (
2122
Env: Prefix + "env",
2223
Webhook: Prefix + "webhook",
2324
WebhookLog: Prefix + "webhook_log",
25+
EmailTemplate: Prefix + "email_template",
2426
}
2527
)

server/db/models/verification_requests.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ
3232
return &model.VerificationRequest{
3333
ID: id,
3434
Token: refs.NewStringRef(v.Token),
35-
Identifier: refs.NewStringRef(v.Identifier),
35+
Identifier: refs.NewStringRef(v.Identifier),
3636
Expires: refs.NewInt64Ref(v.ExpiresAt),
3737
Email: refs.NewStringRef(v.Email),
3838
Nonce: refs.NewStringRef(v.Nonce),

server/db/models/webhook.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type Webhook struct {
2222
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
2323
}
2424

25+
// AsAPIWebhook to return webhook as graphql response object
2526
func (w *Webhook) AsAPIWebhook() *model.Webhook {
2627
headersMap := make(map[string]interface{})
2728
json.Unmarshal([]byte(w.Headers), &headersMap)

server/db/models/webhook_log.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type WebhookLog struct {
2121
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
2222
}
2323

24+
// AsAPIWebhookLog to return webhook log as graphql response object
2425
func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
2526
id := w.ID
2627
if strings.Contains(id, Collections.WebhookLog+"/") {
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package arangodb
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/arangodb/go-driver"
9+
arangoDriver "github.com/arangodb/go-driver"
10+
"github.com/authorizerdev/authorizer/server/db/models"
11+
"github.com/authorizerdev/authorizer/server/graph/model"
12+
"github.com/google/uuid"
13+
)
14+
15+
// AddEmailTemplate to add EmailTemplate
16+
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
17+
if emailTemplate.ID == "" {
18+
emailTemplate.ID = uuid.New().String()
19+
}
20+
21+
emailTemplate.Key = emailTemplate.ID
22+
emailTemplate.CreatedAt = time.Now().Unix()
23+
emailTemplate.UpdatedAt = time.Now().Unix()
24+
25+
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
26+
_, err := emailTemplateCollection.CreateDocument(ctx, emailTemplate)
27+
if err != nil {
28+
return nil, err
29+
}
30+
return emailTemplate.AsAPIEmailTemplate(), nil
31+
}
32+
33+
// UpdateEmailTemplate to update EmailTemplate
34+
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
35+
emailTemplate.UpdatedAt = time.Now().Unix()
36+
37+
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
38+
meta, err := emailTemplateCollection.UpdateDocument(ctx, emailTemplate.Key, emailTemplate)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
emailTemplate.Key = meta.Key
44+
emailTemplate.ID = meta.ID.String()
45+
return emailTemplate.AsAPIEmailTemplate(), nil
46+
}
47+
48+
// ListEmailTemplates to list EmailTemplate
49+
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
50+
emailTemplates := []*model.EmailTemplate{}
51+
52+
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit)
53+
54+
sctx := driver.WithQueryFullCount(ctx)
55+
cursor, err := p.db.Query(sctx, query, nil)
56+
if err != nil {
57+
return nil, err
58+
}
59+
defer cursor.Close()
60+
61+
paginationClone := pagination
62+
paginationClone.Total = cursor.Statistics().FullCount()
63+
64+
for {
65+
var emailTemplate models.EmailTemplate
66+
meta, err := cursor.ReadDocument(ctx, &emailTemplate)
67+
68+
if arangoDriver.IsNoMoreDocuments(err) {
69+
break
70+
} else if err != nil {
71+
return nil, err
72+
}
73+
74+
if meta.Key != "" {
75+
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
76+
}
77+
}
78+
79+
return &model.EmailTemplates{
80+
Pagination: &paginationClone,
81+
EmailTemplates: emailTemplates,
82+
}, nil
83+
}
84+
85+
// GetEmailTemplateByID to get EmailTemplate by id
86+
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
87+
var emailTemplate models.EmailTemplate
88+
query := fmt.Sprintf("FOR d in %s FILTER d._key == @email_template_id RETURN d", models.Collections.EmailTemplate)
89+
bindVars := map[string]interface{}{
90+
"email_template_id": emailTemplateID,
91+
}
92+
93+
cursor, err := p.db.Query(ctx, query, bindVars)
94+
if err != nil {
95+
return nil, err
96+
}
97+
defer cursor.Close()
98+
99+
for {
100+
if !cursor.HasMore() {
101+
if emailTemplate.Key == "" {
102+
return nil, fmt.Errorf("email template not found")
103+
}
104+
break
105+
}
106+
_, err := cursor.ReadDocument(ctx, &emailTemplate)
107+
if err != nil {
108+
return nil, err
109+
}
110+
}
111+
return emailTemplate.AsAPIEmailTemplate(), nil
112+
}
113+
114+
// GetEmailTemplateByEventName to get EmailTemplate by event_name
115+
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
116+
var emailTemplate models.EmailTemplate
117+
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.EmailTemplate)
118+
bindVars := map[string]interface{}{
119+
"event_name": eventName,
120+
}
121+
122+
cursor, err := p.db.Query(ctx, query, bindVars)
123+
if err != nil {
124+
return nil, err
125+
}
126+
defer cursor.Close()
127+
128+
for {
129+
if !cursor.HasMore() {
130+
if emailTemplate.Key == "" {
131+
return nil, fmt.Errorf("email template not found")
132+
}
133+
break
134+
}
135+
_, err := cursor.ReadDocument(ctx, &emailTemplate)
136+
if err != nil {
137+
return nil, err
138+
}
139+
}
140+
return emailTemplate.AsAPIEmailTemplate(), nil
141+
}
142+
143+
// DeleteEmailTemplate to delete EmailTemplate
144+
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
145+
eventTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
146+
_, err := eventTemplateCollection.RemoveDocument(ctx, emailTemplate.ID)
147+
if err != nil {
148+
return err
149+
}
150+
return nil
151+
}

server/db/providers/arangodb/provider.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,20 @@ func NewProvider() (*provider, error) {
134134
Sparse: true,
135135
})
136136

137+
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
138+
if !emailTemplateCollectionExists {
139+
_, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil)
140+
if err != nil {
141+
return nil, err
142+
}
143+
}
144+
145+
emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate)
146+
emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
147+
Unique: true,
148+
Sparse: true,
149+
})
150+
137151
return &provider{
138152
db: arangodb,
139153
}, err

0 commit comments

Comments
 (0)