summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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>
+}