Initial commit

This commit is contained in:
Hayden 2022-08-29 18:30:36 -08:00
commit 29f583e936
135 changed files with 18463 additions and 0 deletions

View file

@ -0,0 +1,51 @@
package mailer
import (
"encoding/base64"
"fmt"
"mime"
"net/smtp"
"strconv"
)
type Mailer struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
From string `json:"from,omitempty"`
}
func (m *Mailer) Ready() bool {
return m.Host != "" && m.Port != 0 && m.Username != "" && m.Password != "" && m.From != ""
}
func (m *Mailer) server() string {
return m.Host + ":" + strconv.Itoa(m.Port)
}
func (m *Mailer) Send(msg *Message) error {
server := m.server()
header := make(map[string]string)
header["From"] = msg.From.String()
header["To"] = msg.To.String()
header["Subject"] = mime.QEncoding.Encode("UTF-8", msg.Subject)
header["MIME-Version"] = "1.0"
header["Content-Type"] = "text/html; charset=\"utf-8\""
header["Content-Transfer-Encoding"] = "base64"
message := ""
for k, v := range header {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(msg.Body))
return smtp.SendMail(
server,
smtp.PlainAuth("", m.Username, m.Password, m.Host),
m.From,
[]string{msg.To.Address},
[]byte(message),
)
}

View file

@ -0,0 +1,66 @@
package mailer
import (
"encoding/json"
"io/ioutil"
"testing"
"github.com/stretchr/testify/assert"
)
const (
TestMailerConfig = "test-mailer.json"
)
func GetTestMailer() (*Mailer, error) {
// Read JSON File
bytes, err := ioutil.ReadFile(TestMailerConfig)
mailer := &Mailer{}
if err != nil {
return nil, err
}
// Unmarshal JSON
err = json.Unmarshal(bytes, mailer)
if err != nil {
return nil, err
}
return mailer, nil
}
func Test_Mailer(t *testing.T) {
t.Parallel()
mailer, err := GetTestMailer()
if err != nil {
t.Skip("Error Reading Test Mailer Config - Skipping")
}
if !mailer.Ready() {
t.Skip("Mailer not ready - Skipping")
}
message, err := RenderWelcome()
if err != nil {
t.Error(err)
}
mb := NewMessageBuilder().
SetBody(message).
SetSubject("Hello").
SetTo("John Doe", "john@doe.com").
SetFrom("Jane Doe", "jane@doe.com")
msg := mb.Build()
err = mailer.Send(msg)
assert.Nil(t, err)
}

View file

@ -0,0 +1,56 @@
package mailer
import "net/mail"
type Message struct {
Subject string
To mail.Address
From mail.Address
Body string
}
type MessageBuilder struct {
subject string
to mail.Address
from mail.Address
body string
}
func NewMessageBuilder() *MessageBuilder {
return &MessageBuilder{}
}
func (mb *MessageBuilder) Build() *Message {
return &Message{
Subject: mb.subject,
To: mb.to,
From: mb.from,
Body: mb.body,
}
}
func (mb *MessageBuilder) SetSubject(subject string) *MessageBuilder {
mb.subject = subject
return mb
}
func (mb *MessageBuilder) SetTo(name, to string) *MessageBuilder {
mb.to = mail.Address{
Name: name,
Address: to,
}
return mb
}
func (mb *MessageBuilder) SetFrom(name, from string) *MessageBuilder {
mb.from = mail.Address{
Name: name,
Address: from,
}
return mb
}
func (mb *MessageBuilder) SetBody(body string) *MessageBuilder {
mb.body = body
return mb
}

View file

@ -0,0 +1,26 @@
package mailer
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_MessageBuilder(t *testing.T) {
t.Parallel()
mb := NewMessageBuilder().
SetBody("Hello World!").
SetSubject("Hello").
SetTo("John Doe", "john@doe.com").
SetFrom("Jane Doe", "jane@doe.com")
msg := mb.Build()
assert.Equal(t, "Hello", msg.Subject)
assert.Equal(t, "Hello World!", msg.Body)
assert.Equal(t, "John Doe", msg.To.Name)
assert.Equal(t, "john@doe.com", msg.To.Address)
assert.Equal(t, "Jane Doe", msg.From.Name)
assert.Equal(t, "jane@doe.com", msg.From.Address)
}

View file

@ -0,0 +1,62 @@
package mailer
import (
"bytes"
_ "embed"
"html/template"
)
//go:embed templates/welcome.html
var templatesWelcome string
type TemplateDefaults struct {
CompanyName string
CompanyAddress string
CompanyURL string
ActivateAccountURL string
UnsubscribeURL string
}
type TemplateProps struct {
Defaults TemplateDefaults
Data map[string]string
}
func (tp *TemplateProps) Set(key, value string) {
tp.Data[key] = value
}
func DefaultTemplateData() TemplateProps {
return TemplateProps{
Defaults: TemplateDefaults{
CompanyName: "Haybytes.com",
CompanyAddress: "123 Main St, Anytown, CA 12345",
CompanyURL: "https://haybytes.com",
ActivateAccountURL: "https://google.com",
UnsubscribeURL: "https://google.com",
},
Data: make(map[string]string),
}
}
func render(tpl string, data TemplateProps) (string, error) {
tmpl, err := template.New("name").Parse(tpl)
if err != nil {
return "", err
}
var tplBuffer bytes.Buffer
err = tmpl.Execute(&tplBuffer, data)
if err != nil {
return "", err
}
return tplBuffer.String(), nil
}
func RenderWelcome() (string, error) {
return render(templatesWelcome, DefaultTemplateData())
}

View file

@ -0,0 +1,444 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Welcome!</title>
<style>
@media only screen and (max-width: 620px) {
table.body h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table.body p,
table.body ul,
table.body ol,
table.body td,
table.body span,
table.body a {
font-size: 16px !important;
}
table.body .wrapper,
table.body .article {
padding: 10px !important;
}
table.body .content {
padding: 0 !important;
}
table.body .container {
padding: 0 !important;
width: 100% !important;
}
table.body .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table.body .btn table {
width: 100% !important;
}
table.body .btn a {
width: 100% !important;
}
table.body .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
</style>
</head>
<body
style="
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
"
>
<span
class="preheader"
style="
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
mso-hide: all;
visibility: hidden;
width: 0;
"
>This is preheader text. Some clients will show this text as a
preview.</span
>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
class="body"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
background-color: #f6f6f6;
width: 100%;
"
width="100%"
bgcolor="#f6f6f6"
>
<tr>
<td
style="font-family: sans-serif; font-size: 14px; vertical-align: top"
valign="top"
>
&nbsp;
</td>
<td
class="container"
style="
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
display: block;
max-width: 580px;
padding: 10px;
width: 580px;
margin: 0 auto;
"
width="580"
valign="top"
>
<div
class="content"
style="
box-sizing: border-box;
display: block;
margin: 0 auto;
max-width: 580px;
padding: 10px;
"
>
<!-- START CENTERED WHITE CONTAINER -->
<table
role="presentation"
class="main"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
background: #ffffff;
border-radius: 3px;
width: 100%;
"
width="100%"
>
<!-- START MAIN CONTENT AREA -->
<tr>
<td
class="wrapper"
style="
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
box-sizing: border-box;
padding: 20px;
"
valign="top"
>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%;
"
width="100%"
>
<tr>
<td
style="
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
"
valign="top"
>
<p
style="
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
"
>
Welcome to {{ .Defaults.CompanyName }}
</p>
<p
style="
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
"
>
Your account has been created, but is not yet
activated. Please click the link below to activate
your account.
</p>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
class="btn btn-primary"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
box-sizing: border-box;
width: 100%;
"
width="100%"
>
<tbody>
<tr>
<td
align="left"
style="
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
padding-bottom: 15px;
"
valign="top"
>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: auto;
"
>
<tbody>
<tr>
<td
style="
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
border-radius: 5px;
text-align: center;
background-color: #3498db;
"
valign="top"
align="center"
bgcolor="#3498db"
>
<a
href="{{ .Defaults.ActivateAccountURL }}"
target="_blank"
style="
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
background-color: #3498db;
border-color: #3498db;
color: #ffffff;
"
>
Activate Account
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p
style="
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
"
>
If you did not create this account you can ignore this
email.
</p>
<p
style="
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
margin-bottom: 15px;
"
>
Thanks for using {{ .Defaults.CompanyName }}!
</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
<!-- START FOOTER -->
<div
class="footer"
style="
clear: both;
margin-top: 10px;
text-align: center;
width: 100%;
"
>
<table
role="presentation"
border="0"
cellpadding="0"
cellspacing="0"
style="
border-collapse: separate;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
width: 100%;
"
width="100%"
>
<tr>
<td
class="content-block"
style="
font-family: sans-serif;
vertical-align: top;
padding-bottom: 10px;
padding-top: 10px;
color: #999999;
font-size: 12px;
text-align: center;
"
valign="top"
align="center"
>
<span
class="apple-link"
style="
color: #999999;
font-size: 12px;
text-align: center;
"
>{{ .Defaults.CompanyName }}, {{ .Defaults.CompanyAddress
}}</span
>
<br />
Don't like these emails?
<a
href="{{ .Defaults.UnsubscribeURL }}"
style="
text-decoration: underline;
color: #999999;
font-size: 12px;
text-align: center;
"
>Unsubscribe</a
>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td
style="font-family: sans-serif; font-size: 14px; vertical-align: top"
valign="top"
>
&nbsp;
</td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,7 @@
{
"host": "",
"port": 465,
"username": "",
"password": "",
"from": ""
}