Skip to content
This repository was archived by the owner on Sep 2, 2024. It is now read-only.

Commit 2b9e331

Browse files
committed
send mail abstraction created fix #5
1 parent b1bac68 commit 2b9e331

File tree

10 files changed

+161
-212
lines changed

10 files changed

+161
-212
lines changed

account.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
emailFuncs "staticbackend/email"
1314
"staticbackend/internal"
1415
"staticbackend/middleware"
1516

@@ -157,6 +158,7 @@ func (a *accounts) create(w http.ResponseWriter, r *http.Request) {
157158

158159
rootToken := fmt.Sprintf("%s|%s|%s", token.ID.Hex(), token.AccountID.Hex(), token.Token)
159160

161+
//TODO: Have html template for those
160162
body := fmt.Sprintf(`
161163
<p>Hey there,</p>
162164
<p>Thanks for creating your account.</p>
@@ -175,7 +177,17 @@ func (a *accounts) create(w http.ResponseWriter, r *http.Request) {
175177
<p>Dominic<br />Founder</p>
176178
`, base.ID.Hex(), email, pw, rootToken)
177179

178-
err = sendMail(email, "", FromEmail, FromName, "Your StaticBackend account", body, "")
180+
ed := internal.SendMailData{
181+
From: FromEmail,
182+
FromName: FromName,
183+
To: email,
184+
ToName: "",
185+
Subject: "Your StaticBackend account",
186+
HTMLBody: body,
187+
TextBody: emailFuncs.StripHTML(body),
188+
}
189+
190+
err = emailer.Send(ed)
179191
if err != nil {
180192
log.Println("error sending email", err)
181193
http.Error(w, err.Error(), http.StatusInternalServerError)

cmd/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package main
22

33
import (
4+
"flag"
45
backend "staticbackend"
56
)
67

78
func main() {
8-
backend.Start()
9+
dbHost := flag.String("host", "localhost", "Hostname for mongodb")
10+
port := flag.String("port", "8099", "HTTP port to listen on")
11+
flag.Parse()
12+
13+
backend.Start(*dbHost, *port)
914
}

email/ses.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"github.com/aws/aws-sdk-go/aws"
1010
"github.com/aws/aws-sdk-go/aws/session"
1111
"github.com/aws/aws-sdk-go/service/ses"
12-
13-
"staticbackend/internal"
1412
)
1513

1614
type AWSSES struct{}
@@ -60,7 +58,7 @@ func (AWSSES) Send(data internal.SendMailData) error {
6058
Data: aws.String(data.Subject),
6159
},
6260
},
63-
Source: aws.String(fromEmail),
61+
Source: aws.String(data.From),
6462
ReplyToAddresses: aws.StringSlice([]string{data.ReplyTo}),
6563
// Uncomment to use a configuration set
6664
//ConfigurationSetName: aws.String(ConfigurationSet),

email/striphtml.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package email
2+
3+
import (
4+
"bytes"
5+
"html"
6+
"strings"
7+
"text/template"
8+
)
9+
10+
// StripHTML returns a version of a string with no HTML tags.
11+
func StripHTML(s string) string {
12+
output := ""
13+
14+
// if we have a full html page we only need the body
15+
startBody := strings.Index(s, "<body")
16+
if startBody > -1 {
17+
endBody := strings.Index(s, "</body>")
18+
// try to find the end of the <body tag
19+
for i := startBody; i < endBody; i++ {
20+
if s[i] == '>' {
21+
startBody = i
22+
break
23+
}
24+
}
25+
26+
if startBody < endBody {
27+
s = s[startBody:endBody]
28+
}
29+
}
30+
31+
// Shortcut strings with no tags in them
32+
if !strings.ContainsAny(s, "<>") {
33+
output = s
34+
} else {
35+
// Removing line feeds
36+
s = strings.Replace(s, "\n", "", -1)
37+
38+
// Then replace line breaks with newlines, to preserve that formatting
39+
s = strings.Replace(s, "</h1>", "\n\n", -1)
40+
s = strings.Replace(s, "</h2>", "\n\n", -1)
41+
s = strings.Replace(s, "</h3>", "\n\n", -1)
42+
s = strings.Replace(s, "</h4>", "\n\n", -1)
43+
s = strings.Replace(s, "</h5>", "\n\n", -1)
44+
s = strings.Replace(s, "</h6>", "\n\n", -1)
45+
s = strings.Replace(s, "</p>", "\n", -1)
46+
s = strings.Replace(s, "<br>", "\n", -1)
47+
s = strings.Replace(s, "<br/>", "\n", -1)
48+
s = strings.Replace(s, "<br />", "\n", -1)
49+
50+
// Walk through the string removing all tags
51+
b := bytes.NewBufferString("")
52+
inTag := false
53+
for _, r := range s {
54+
switch r {
55+
case '<':
56+
inTag = true
57+
case '>':
58+
inTag = false
59+
default:
60+
if !inTag {
61+
b.WriteRune(r)
62+
}
63+
}
64+
}
65+
output = b.String()
66+
}
67+
68+
// Remove a few common harmless entities, to arrive at something more like plain text
69+
output = strings.Replace(output, "&#8216;", "'", -1)
70+
output = strings.Replace(output, "&#8217;", "'", -1)
71+
output = strings.Replace(output, "&#8220;", "\"", -1)
72+
output = strings.Replace(output, "&#8221;", "\"", -1)
73+
output = strings.Replace(output, "&nbsp;", " ", -1)
74+
output = strings.Replace(output, "&quot;", "\"", -1)
75+
output = strings.Replace(output, "&apos;", "'", -1)
76+
77+
// Translate some entities into their plain text equivalent (for example accents, if encoded as entities)
78+
output = html.UnescapeString(output)
79+
80+
// In case we have missed any tags above, escape the text - removes <, >, &, ' and ".
81+
output = template.HTMLEscapeString(output)
82+
83+
// After processing, remove some harmless entities &, ' and " which are encoded by HTMLEscapeString
84+
output = strings.Replace(output, "&#34;", "\"", -1)
85+
output = strings.Replace(output, "&#39;", "'", -1)
86+
output = strings.Replace(output, "&amp; ", "& ", -1) // NB space after
87+
output = strings.Replace(output, "&amp;amp; ", "& ", -1) // NB space after
88+
89+
return output
90+
}

internal/mailer.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package internal
22

3+
const (
4+
MailProviderDev = "dev"
5+
MailProviderSES = "ses"
6+
)
7+
38
// SendMailData contains necessary fields to send an email
49
type SendMailData struct {
5-
From string
6-
FromName string
7-
To string
8-
ToName string
9-
Subject string
10-
HTMLBody string
11-
TextBody string
12-
ReplyTo string
10+
From string `json:"from"`
11+
FromName string `json:"fromName"`
12+
To string `json:"to"`
13+
ToName string `json:"toName"`
14+
Subject string `json:"subject"`
15+
HTMLBody string `json:"htmlBody"`
16+
TextBody string `json:"textBody"`
17+
ReplyTo string `json:"replyTo"`
1318
}
1419

1520
// Mailer is used to have different implementation for sending email

main_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919

2020
const (
2121
dbName = "unittest"
22-
email = "unit@test.com"
22+
admEmail = "unit@test.com"
2323
password = "my_unittest_pw"
2424
userEmail = "user@test.com"
2525
userPassword = "another_fake_password"
@@ -70,7 +70,7 @@ func deleteAndSetupTestAccount() {
7070

7171
sysDB := client.Database("sbsys")
7272

73-
if _, err := sysDB.Collection("accounts").DeleteMany(ctx, bson.M{"email": email}); err != nil {
73+
if _, err := sysDB.Collection("accounts").DeleteMany(ctx, bson.M{"email": admEmail}); err != nil {
7474
log.Fatal(err)
7575
}
7676

@@ -81,7 +81,7 @@ func deleteAndSetupTestAccount() {
8181
acctID := primitive.NewObjectID()
8282
cus := internal.Customer{
8383
ID: acctID,
84-
Email: email,
84+
Email: admEmail,
8585
}
8686

8787
if _, err := sysDB.Collection("accounts").InsertOne(ctx, cus); err != nil {
@@ -103,7 +103,7 @@ func deleteAndSetupTestAccount() {
103103
pubKey = base.ID.Hex()
104104

105105
db := client.Database(dbName)
106-
token, dbToken, err := createAccountAndUser(db, email, password, 100)
106+
token, dbToken, err := createAccountAndUser(db, admEmail, password, 100)
107107
if err != nil {
108108
log.Fatal(err)
109109
}

membership.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"staticbackend/email"
1314
"staticbackend/internal"
1415
"staticbackend/middleware"
1516

@@ -309,8 +310,18 @@ func resetPassword(w http.ResponseWriter, r *http.Request) {
309310
return
310311
}
311312

313+
//TODO: have HTML template for those
312314
body := fmt.Sprintf(`Your reset code is: %s`, code)
313-
if err := sendMail(data.Email, "", FromEmail, FromName, "Your password reset code", body, ""); err != nil {
315+
316+
ed := internal.SendMailData{
317+
From: FromEmail,
318+
FromName: FromName,
319+
To: data.Email,
320+
Subject: "Your password reset code",
321+
HTMLBody: body,
322+
TextBody: email.StripHTML(body),
323+
}
324+
if err := emailer.Send(ed); err != nil {
314325
http.Error(w, err.Error(), http.StatusInternalServerError)
315326
return
316327
}

0 commit comments

Comments
 (0)