[#275] added support to customize the default user email templates from the Admin UI

This commit is contained in:
Gani Georgiev
2022-08-14 19:30:45 +03:00
parent 1de56d3d9e
commit 7d10d20de1
47 changed files with 1648 additions and 1188 deletions
-10
View File
@@ -5,18 +5,12 @@ import (
"io"
"net/mail"
"net/smtp"
"regexp"
"strings"
"github.com/domodwyer/mailyak/v3"
"github.com/microcosm-cc/bluemonday"
)
var _ Mailer = (*SmtpClient)(nil)
// regex to select all tabs
var tabsRegex = regexp.MustCompile(`\t+`)
// NewSmtpClient creates new `SmtpClient` with the provided configuration.
func NewSmtpClient(
host string,
@@ -74,10 +68,6 @@ func (m *SmtpClient) Send(
yak.Subject(subject)
yak.HTML().Set(htmlBody)
// set also plain text content
policy := bluemonday.StrictPolicy() // strips all tags
yak.Plain().Set(strings.TrimSpace(tabsRegex.ReplaceAllString(policy.Sanitize(htmlBody), "")))
for name, data := range attachments {
yak.Attach(name, data)
}
+29
View File
@@ -0,0 +1,29 @@
package rest
import (
"net/url"
"path"
"strings"
)
// NormalizeUrl removes duplicated slashes from a url path.
func NormalizeUrl(originalUrl string) (string, error) {
u, err := url.Parse(originalUrl)
if err != nil {
return "", err
}
hasSlash := strings.HasSuffix(u.Path, "/")
// clean up path by removing duplicated /
u.Path = path.Clean(u.Path)
u.RawPath = path.Clean(u.RawPath)
// restore original trailing slash
if hasSlash && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
u.RawPath += "/"
}
return u.String(), nil
}
+40
View File
@@ -0,0 +1,40 @@
package rest_test
import (
"testing"
"github.com/pocketbase/pocketbase/tools/rest"
)
func TestNormalizeUrl(t *testing.T) {
scenarios := []struct {
url string
expectError bool
expectUrl string
}{
{":/", true, ""},
{"./", false, "./"},
{"../../test////", false, "../../test/"},
{"/a/b/c", false, "/a/b/c"},
{"a/////b//c/", false, "a/b/c/"},
{"/a/////b//c", false, "/a/b/c"},
{"///a/b/c", false, "/a/b/c"},
{"//a/b/c", false, "//a/b/c"}, // preserve "auto-schema"
{"http://a/b/c", false, "http://a/b/c"},
{"a//bc?test=1//dd", false, "a/bc?test=1//dd"}, // only the path is normalized
{"a//bc?test=1#12///3", false, "a/bc?test=1#12///3"}, // only the path is normalized
}
for i, s := range scenarios {
result, err := rest.NormalizeUrl(s.url)
hasErr := err != nil
if hasErr != s.expectError {
t.Errorf("(%d) Expected hasErr %v, got %v", i, s.expectError, hasErr)
}
if result != s.expectUrl {
t.Errorf("(%d) Expected url %q, got %q", i, s.expectUrl, result)
}
}
}
+9 -1
View File
@@ -4,15 +4,23 @@ import (
"crypto/rand"
)
// RandomString generates a random string of specified length.
// RandomString generates a random string with the specified length.
//
// The generated string is cryptographically random and matches
// [A-Za-z0-9]+ (aka. it's transparent to URL-encoding).
func RandomString(length int) string {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return RandomStringWithAlphabet(length, alphabet)
}
// RandomStringWithAlphabet generates a cryptographically random string
// with the specified length and characters set.
func RandomStringWithAlphabet(length int, alphabet string) string {
bytes := make([]byte, length)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphabet[b%byte(len(alphabet))]
}
+42 -5
View File
@@ -10,25 +10,62 @@ import (
func TestRandomString(t *testing.T) {
generated := []string{}
reg := regexp.MustCompile(`[a-zA-Z0-9]+`)
length := 10
for i := 0; i < 30; i++ {
length := 5 + i
for i := 0; i < 100; i++ {
result := security.RandomString(length)
if len(result) != length {
t.Errorf("(%d) Expected the length of the string to be %d, got %d", i, length, len(result))
t.Fatalf("(%d) Expected the length of the string to be %d, got %d", i, length, len(result))
}
if match := reg.MatchString(result); !match {
t.Errorf("(%d) The generated strings should have only [a-zA-Z0-9]+ characters, got %q", i, result)
t.Fatalf("(%d) The generated string should have only [a-zA-Z0-9]+ characters, got %q", i, result)
}
for _, str := range generated {
if str == result {
t.Errorf("(%d) Repeating random string - found %q in \n%v", i, result, generated)
t.Fatalf("(%d) Repeating random string - found %q in \n%v", i, result, generated)
}
}
generated = append(generated, result)
}
}
func TestRandomStringWithAlphabet(t *testing.T) {
scenarios := []struct {
alphabet string
expectPattern string
}{
{"0123456789_", `[0-9_]+`},
{"abcd", `[abcd]+`},
{"!@#$%^&*()", `[\!\@\#\$\%\^\&\*\(\)]+`},
}
for i, s := range scenarios {
generated := make([]string, 100)
length := 10
for j := 0; j < 100; j++ {
result := security.RandomStringWithAlphabet(length, s.alphabet)
if len(result) != length {
t.Fatalf("(%d:%d) Expected the length of the string to be %d, got %d", i, j, length, len(result))
}
reg := regexp.MustCompile(s.expectPattern)
if match := reg.MatchString(result); !match {
t.Fatalf("(%d:%d) The generated string should have only %s characters, got %q", i, j, s.expectPattern, result)
}
for _, str := range generated {
if str == result {
t.Fatalf("(%d:%d) Repeating random string - found %q in %q", i, j, result, generated)
}
}
generated = append(generated, result)
}
}
}
+3
View File
@@ -27,6 +27,9 @@ func (b *Broker) Clients() map[string]Client {
//
// Returns non-nil error when client with clientId is not registered.
func (b *Broker) ClientById(clientId string) (Client, error) {
b.mux.RLock()
defer b.mux.RUnlock()
client, ok := b.clients[clientId]
if !ok {
return nil, fmt.Errorf("No client associated with connection ID %q", clientId)