github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/domain/model/email/email.go (about) 1 package email 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/mail" 8 "strings" 9 "text/template" 10 ) 11 12 type Template struct { 13 template *template.Template 14 value string 15 emails []*Email 16 current int 17 inBody bool 18 } 19 20 func NewTemplate(name string, value string) *Template { 21 t := &Template{ 22 template: template.New(name), 23 value: strings.Replace(value, "\r", "", -1), 24 emails: make([]*Email, 0, 10000), 25 current: 0, 26 } 27 return t 28 } 29 30 func (t *Template) Parse() error { 31 _, err := t.template.Parse(t.value) 32 return err 33 } 34 35 func (t *Template) Execute(data interface{}) error { 36 var b bytes.Buffer 37 if err := t.template.Execute(&b, data); err != nil { 38 return err 39 } 40 41 email := NewEmail() 42 t.emails = append(t.emails, email) 43 defer func() { 44 t.current++ 45 }() 46 47 for lineNo := 1; ; lineNo++ { 48 line, err := b.ReadString([]byte("\n")[0]) // TODO: bufio.Scanner? 49 if err != nil { 50 if err == io.EOF { 51 defer func() { 52 t.inBody = false 53 }() 54 //fmt.Printf("[%d] line = %q\n", lineNo, line) 55 if err := t.parseLine(line, lineNo, email); err != nil { 56 return err 57 } 58 break 59 } else { 60 return err 61 } 62 } 63 64 //fmt.Printf("[%d] line = %q\n", lineNo, line) 65 if err := t.parseLine(line, lineNo, email); err != nil { 66 return err 67 } 68 } 69 70 return nil 71 } 72 73 func (t *Template) parseLine(line string, lineNo int, email *Email) error { 74 if t.inBody { 75 fmt.Fprint(email.Body.(*bytes.Buffer), line) 76 return nil 77 } 78 79 colonIndex := strings.Index(line, ":") 80 if colonIndex == -1 { 81 return fmt.Errorf("line:%v: Invalid email template", lineNo) 82 } 83 84 name := strings.ToLower(strings.TrimSpace(line[:colonIndex])) 85 value := strings.TrimSpace(line[colonIndex+1:]) 86 //fmt.Printf("name = %q, value = %q\n", name, value) 87 switch name { 88 case "from": 89 from, err := mail.ParseAddress(value) 90 if err != nil { 91 return fmt.Errorf("line:%v: Parse 'from' failed: %v", lineNo, err) 92 } 93 email.From = from 94 case "to": 95 tos, err := mail.ParseAddressList(value) 96 if err != nil { 97 return fmt.Errorf("line:%v: Parse 'to' failed: %v", lineNo, err) 98 } 99 email.Tos = tos 100 case "subject": 101 email.Subject = value 102 case "body": 103 if value != "text/plain" && value != "text/html" { 104 return fmt.Errorf("line:%v: Invalid body mime type: %v", lineNo, value) 105 } 106 email.BodyMIMEType = value 107 t.inBody = true // TODO: goroutine safe 108 default: 109 // TODO: accept as extra header 110 return fmt.Errorf("line:%v: Unknown header %q", lineNo, name) 111 } 112 return nil 113 } 114 115 type Email struct { 116 From *mail.Address 117 Tos []*mail.Address 118 Subject string 119 BodyMIMEType string 120 Body io.Reader 121 bodyCache string 122 customArgs map[string]string 123 } 124 125 func (e *Email) CustomArgs() map[string]string { 126 return e.customArgs 127 } 128 129 func NewEmail() *Email { 130 return &Email{ 131 Body: &bytes.Buffer{}, 132 customArgs: make(map[string]string), 133 } 134 } 135 136 // NewFromTemplate create Email from Template with given data. 137 // Return an error if 138 // - Parsing template fails 139 func NewFromTemplate(t *Template, data interface{}) (*Email, error) { 140 if err := t.Parse(); err != nil { 141 return nil, fmt.Errorf("Parse error: %v", err) 142 } 143 if err := t.Execute(data); err != nil { 144 return nil, err 145 } 146 return t.emails[0], nil 147 } 148 149 func NewEmailsFromTemplate(t *Template, data []interface{}) ([]*Email, error) { 150 if err := t.Parse(); err != nil { 151 return nil, fmt.Errorf("Parse error: %v", err) 152 } 153 for _, d := range data { 154 if err := t.Execute(d); err != nil { 155 return nil, err 156 } 157 } 158 return t.emails, nil 159 } 160 161 func (e *Email) BodyString() string { 162 if e.bodyCache != "" { 163 return e.bodyCache 164 } 165 b, err := io.ReadAll(e.Body) 166 if err != nil { 167 return "" 168 } 169 e.bodyCache = string(b) 170 return e.bodyCache 171 } 172 173 func (e *Email) SetCustomArg(key, value string) { 174 e.customArgs[key] = value 175 }