changeset 5335:dcd5692a2889 extented-report

Sending generated reports to receivers.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 01 Jun 2021 02:46:14 +0200
parents 45805c454436
children 2ec8a34ae683
files pkg/imports/report.go pkg/misc/mail.go
diffstat 2 files changed, 148 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- 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
 }
--- 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
+}