# HG changeset patch # User Sascha L. Teichmann # Date 1622508374 -7200 # Node ID dcd5692a288996bf66beaff3fb9e74084c1d5412 # Parent 45805c454436402fe8b85493d58007c0a7c32ecc Sending generated reports to receivers. diff -r 45805c454436 -r dcd5692a2889 pkg/imports/report.go --- a/pkg/imports/report.go Tue Jun 01 01:22:10 2021 +0200 +++ b/pkg/imports/report.go Tue Jun 01 02:46:14 2021 +0200 @@ -14,6 +14,7 @@ package imports import ( + "bytes" "context" "database/sql" "errors" @@ -22,9 +23,13 @@ "os" "path/filepath" "regexp" + "strings" + "text/template" + "time" "gemma.intevation.de/gemma/pkg/common" "gemma.intevation.de/gemma/pkg/config" + "gemma.intevation.de/gemma/pkg/misc" "gemma.intevation.de/gemma/pkg/models" "gemma.intevation.de/gemma/pkg/xlsx" @@ -40,6 +45,24 @@ type reportJobCreator struct{} +const selectReportUsersSQL = ` +SELECT username, email_address +FROM users.list_users +WHERE report_reciever +ORDER BY country, username` + +var reportMailTmpl = template.Must(template.New("report-mail"). + Parse(`Dear {{ .User }} + +this is an automatically generated report from the Gemma system. +You received this mail because you are marked a report receiver in +the system. Please contact the system adminstrator of the system +if your recieved this mail against you consent. + +Find attached {{ .Attachment }} which contains the {{ .Report }} from {{ .When }}. + +Kind Regards`)) + func init() { RegisterJobCreator(ReportJobKind, reportJobCreator{}) } func (reportJobCreator) Description() string { return "report" } @@ -123,12 +146,6 @@ return template, action, nil } -const selectReportUsersSQL = ` -SELECT username, email_address -FROM users.list_users -WHERE report_reciever -ORDER BY country, username` - func (r *Report) Do( ctx context.Context, importID int64, @@ -136,6 +153,10 @@ feedback Feedback, ) (interface{}, error) { + start := time.Now() + + feedback.Info("Generating report %s.", r.Name) + template, action, err := r.loadTemplate() if err != nil { return nil, err @@ -147,12 +168,7 @@ } defer tx.Rollback() - type user struct { - name string - email string - } - - var users []user + var users []misc.EmailReceiver if err := func() error { rows, err := tx.QueryContext(ctx, selectReportUsersSQL) @@ -162,8 +178,8 @@ defer rows.Close() for rows.Next() { - var u user - if err := rows.Scan(&u.name, &u.email); err != nil { + var u misc.EmailReceiver + if err := rows.Scan(&u.Name, &u.Address); err != nil { return err } users = append(users, u) @@ -183,7 +199,61 @@ return nil, fmt.Errorf("Generating report failed: %v", err) } - // TODO: Send report via email to users. + var buf bytes.Buffer + if _, err := template.WriteTo(&buf); err != nil { + log.Printf("error: %v\n", err) + return nil, fmt.Errorf("generating report failed: %v", err) + } + + feedback.Info("Sending reports to %d receivers.", len(users)) + + now := start.UTC().Format("2006-01-02") + + attached := r.Name + "-" + now + ".xlsx" + + attachments := []misc.EmailAttachment{{ + Name: attached, + Content: buf.Bytes(), + }} + + errorHandler := func(r misc.EmailReceiver, err error) error { + // We do not terminate the sending of the emails if + // sending failed. We only log it. + feedback.Warn("Sending report to %s failed: %v", r.Name, err) + return nil + } + + body := func(u misc.EmailReceiver) (string, error) { + fill := struct { + User string + Attachment string + Report string + When string + }{ + User: u.Name, + Attachment: attached, + Report: r.Name, + When: now, + } + var sb strings.Builder + if err := reportMailTmpl.Execute(&sb, &fill); err != nil { + return "", err + } + return sb.String(), nil + } + + if err := misc.SendMailToAll( + users, + "Report "+r.Name+" from "+now, + body, + attachments, + errorHandler, + ); err != nil { + return nil, err + } + + feedback.Info("Generating and sending report took %v.", + time.Since(start)) return nil, nil } diff -r 45805c454436 -r dcd5692a2889 pkg/misc/mail.go --- a/pkg/misc/mail.go Tue Jun 01 01:22:10 2021 +0200 +++ b/pkg/misc/mail.go Tue Jun 01 02:46:14 2021 +0200 @@ -14,11 +14,23 @@ package misc import ( + "io" + gomail "gopkg.in/gomail.v2" "gemma.intevation.de/gemma/pkg/config" ) +type EmailReceiver struct { + Name string + Address string +} + +type EmailAttachment struct { + Name string + Content []byte +} + // SendMail sends an email to a given address with a given subject // and body. // The credentials to contact the SMPT server are taken from the @@ -41,3 +53,54 @@ return d.DialAndSend(m) } + +func SendMailToAll( + receivers []EmailReceiver, + subject string, + body func(EmailReceiver) (string, error), + attachments []EmailAttachment, + errorHandler func(EmailReceiver, error) error, +) error { + + d := gomail.Dialer{ + Host: config.MailHost(), + Port: int(config.MailPort()), + Username: config.MailUser(), + Password: config.MailPassword(), + LocalName: config.MailHelo(), + SSL: config.MailPort() == 465, + } + + s, err := d.Dial() + if err != nil { + return err + } + defer s.Close() + + m := gomail.NewMessage() + for _, r := range receivers { + m.SetHeader("From", config.MailFrom()) + m.SetAddressHeader("To", r.Address, r.Name) + m.SetHeader("Subject", subject) + b, err := body(r) + if err != nil { + return err + } + m.SetBody("text/plain", b) + for _, at := range attachments { + content := at.Content + m.Attach(at.Name, gomail.SetCopyFunc(func(w io.Writer) error { + _, err := w.Write(content) + return err + })) + } + + if err := gomail.Send(s, m); err != nil { + if err = errorHandler(r, err); err != nil { + return err + } + } + m.Reset() + } + return nil +}