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  }