summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2024-08-12 23:58:46 +0200
committerThomas Voss <mail@thomasvoss.com> 2024-08-12 23:58:54 +0200
commit71937c1079a77e0b38dae00662ab2041c3e28f77 (patch)
tree1e861548d89d9a57c873fe968b395ba5518f4770
parent17dd2383d5f17280288087da76803f6d3e9a5aae (diff)
Add email support
-rw-r--r--Makefile2
-rw-r--r--lib/email/email.go75
-rw-r--r--main.go57
-rw-r--r--template/error.templ24
4 files changed, 151 insertions, 7 deletions
diff --git a/Makefile b/Makefile
index 85ba3d9..8893611 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,6 @@ all-i18n:
go build
watch:
- ls euro-cash.eu | entr -r ./euro-cash.eu -port $${PORT:-8080}
+ ls euro-cash.eu | entr -r ./euro-cash.eu -no-email -port $${PORT:-8080}
.PHONY: watch
diff --git a/lib/email/email.go b/lib/email/email.go
new file mode 100644
index 0000000..f6d6673
--- /dev/null
+++ b/lib/email/email.go
@@ -0,0 +1,75 @@
+package email
+
+import (
+ "crypto/tls"
+ "fmt"
+ "math/rand"
+ "net/smtp"
+ "strconv"
+ "time"
+)
+
+var Config struct {
+ Host string
+ Port int
+ ToAddr, FromAddr string
+ Password string
+}
+
+const emailTemplate = `From: %s
+To: %s
+Subject: %s
+Date: %s
+Content-Type: text/plain; charset=UTF-8
+MIME-Version: 1.0
+Message-ID: <%s>
+
+%s`
+
+func ServerError(fault error) error {
+ r := rand.New(rand.NewSource(time.Now().UnixNano()))
+ msgid := strconv.FormatInt(r.Int63(), 10) + "@" + Config.Host
+ msg := fmt.Sprintf(emailTemplate, Config.FromAddr, Config.ToAddr,
+ "Error Report", time.Now().Format(time.RFC1123Z), msgid, fault)
+
+ tlsConfig := &tls.Config{
+ InsecureSkipVerify: false,
+ ServerName: Config.Host,
+ }
+
+ hostWithPort := Config.Host + ":" + strconv.Itoa(Config.Port)
+ conn, err := tls.Dial("tcp", hostWithPort, tlsConfig)
+ if err != nil {
+ return err
+ }
+
+ client, err := smtp.NewClient(conn, Config.Host)
+ if err != nil {
+ return err
+ }
+ defer client.Close()
+
+ auth := smtp.PlainAuth("", Config.FromAddr, Config.Password, Config.Host)
+ if err := client.Auth(auth); err != nil {
+ return err
+ }
+
+ if err := client.Mail(Config.FromAddr); err != nil {
+ return err
+ }
+
+ if err := client.Rcpt(Config.ToAddr); err != nil {
+ return err
+ }
+
+ wc, err := client.Data()
+ if err != nil {
+ return err
+ }
+ defer wc.Close()
+
+ if _, err = wc.Write([]byte(msg)); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/main.go b/main.go
index 86966b5..138f3cd 100644
--- a/main.go
+++ b/main.go
@@ -11,15 +11,19 @@ import (
"net/http"
"os"
"path/filepath"
+ "slices"
"strconv"
"strings"
"git.thomasvoss.com/euro-cash.eu/lib"
+ "git.thomasvoss.com/euro-cash.eu/lib/email"
"git.thomasvoss.com/euro-cash.eu/lib/mintage"
"git.thomasvoss.com/euro-cash.eu/template"
"github.com/a-h/templ"
)
+var emailDisabled bool
+
var components = map[string]templ.Component{
"/": template.Root(),
"/about": template.About(),
@@ -34,6 +38,18 @@ func main() {
lib.InitPrinters()
port := flag.Int("port", 8080, "port number")
+ flag.BoolVar(&emailDisabled, "no-email", false,
+ "disables email support")
+ flag.StringVar(&email.Config.Host, "smtp-host", "smtp.migadu.com",
+ "SMTP server hostname")
+ flag.IntVar(&email.Config.Port, "smtp-port", 465,
+ "SMTP server port number")
+ flag.StringVar(&email.Config.ToAddr, "email-to", "bugs@euro-cash.eu",
+ "address to send error messages to")
+ flag.StringVar(&email.Config.FromAddr, "email-from", "noreply@euro-cash.eu",
+ "address to send error messages from")
+ flag.StringVar(&email.Config.Password, "email-password", "",
+ "password to authenticate the email client")
flag.Parse()
fs := http.FileServer(http.Dir("static"))
@@ -106,20 +122,35 @@ func mintageHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
countries := lib.SortedCountries(
r.Context().Value("printer").(lib.Printer))
- code := strings.ToLower(cmp.Or(r.FormValue("code"), countries[0].Code))
- ctype := strings.ToLower(r.FormValue("type"))
- path := filepath.Join("data", "mintages", code)
- f, _ := os.Open(path) // TODO: Handle error
- defer f.Close()
- data, _ := mintage.Parse(f, path) // TODO: Handle error
+ code := strings.ToLower(r.FormValue("code"))
+ if !slices.ContainsFunc(countries, func(c lib.Country) bool {
+ return c.Code == code
+ }) {
+ code = countries[0].Code
+ }
+ ctype := strings.ToLower(r.FormValue("type"))
switch ctype {
case "circ", "nifc", "proof":
default:
ctype = "circ"
}
+ path := filepath.Join("data", "mintages", code)
+ f, err := os.Open(path)
+ if err != nil {
+ throwError(http.StatusInternalServerError, err, w, r)
+ return
+ }
+ defer f.Close()
+
+ data, err := mintage.Parse(f, path)
+ if err != nil {
+ throwError(http.StatusInternalServerError, err, w, r)
+ return
+ }
+
ctx := context.WithValue(r.Context(), "code", code)
ctx = context.WithValue(ctx, "type", ctype)
ctx = context.WithValue(ctx, "mintages", data)
@@ -152,3 +183,17 @@ func setUserLanguage(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, c.Value, http.StatusFound)
}
}
+
+func throwError(status int, err error, w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(status)
+ if emailDisabled {
+ log.Print(err)
+ } else {
+ go func() {
+ if err := email.ServerError(err); err != nil {
+ log.Print(err)
+ }
+ }()
+ }
+ template.Base(template.Error(status)).Render(r.Context(), w)
+}
diff --git a/template/error.templ b/template/error.templ
new file mode 100644
index 0000000..d2fed83
--- /dev/null
+++ b/template/error.templ
@@ -0,0 +1,24 @@
+package template
+
+import (
+ "net/http"
+ "strconv"
+
+ "git.thomasvoss.com/euro-cash.eu/lib"
+)
+
+templ Error(status int) {
+ {{ p := ctx.Value("printer").(lib.Printer) }}
+ <header>
+ @navbar()
+ <h1>{ strconv.Itoa(status) } { http.StatusText(status) }</h1>
+ </header>
+ <main>
+ <p>
+ { p.T("If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.") }
+ </p>
+ <p>
+ @templ.Raw(p.T("If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", contactEmail))
+ </p>
+ </main>
+}