forked from mirrors/homebox
Initial commit
This commit is contained in:
commit
29f583e936
135 changed files with 18463 additions and 0 deletions
51
backend/pkgs/mailer/mailer.go
Normal file
51
backend/pkgs/mailer/mailer.go
Normal 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),
|
||||
)
|
||||
}
|
66
backend/pkgs/mailer/mailer_test.go
Normal file
66
backend/pkgs/mailer/mailer_test.go
Normal 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)
|
||||
}
|
56
backend/pkgs/mailer/message.go
Normal file
56
backend/pkgs/mailer/message.go
Normal 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
|
||||
}
|
26
backend/pkgs/mailer/message_test.go
Normal file
26
backend/pkgs/mailer/message_test.go
Normal 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)
|
||||
}
|
62
backend/pkgs/mailer/templates.go
Normal file
62
backend/pkgs/mailer/templates.go
Normal 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())
|
||||
}
|
444
backend/pkgs/mailer/templates/welcome.html
Normal file
444
backend/pkgs/mailer/templates/welcome.html
Normal 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"
|
||||
>
|
||||
|
||||
</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"
|
||||
>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
7
backend/pkgs/mailer/test-mailer-template.json
Normal file
7
backend/pkgs/mailer/test-mailer-template.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"host": "",
|
||||
"port": 465,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"from": ""
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue