diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..344672f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tmp/ +app.ini \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ef4a8bf --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module send_email_site + +go 1.21.6 + +require gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a8937af --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/main.go b/main.go new file mode 100644 index 0000000..af7cec2 --- /dev/null +++ b/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "html/template" + "io" + "log" + "net/http" + "os" + "os/signal" + "strings" + "time" + + "gopkg.in/ini.v1" +) + +type EmailsResonse struct { + Emails []string `json:"emails"` +} + +func main() { + // Read the config file and get the HMC api settings + config, err := ini.Load("app.ini") + if err != nil { + panic(err) + } + HMC_CFG := config.Section("hmc") + HMC_API_SECRET := HMC_CFG.Key("SHARED_SECRET").String() + HMC_API_URL := HMC_CFG.Key("API_URL").String() + HMC_CITY := HMC_CFG.Key("CITY").String() + + // Prepare the go html templates and static file server + templates := template.Must(template.ParseGlob("templates/*.html")) + static_files := http.FileServer(http.Dir("static/")) + + mux := http.NewServeMux() + mux.Handle("/static/", http.StripPrefix("/static/", static_files)) + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Printf("Hello '%v %v' handler", r.Method, r.URL) + templates.ExecuteTemplate(w, "index", nil) + }) + + mux.HandleFunc("/mail-content", func(w http.ResponseWriter, r *http.Request) { + log.Printf("Hello '%v %v' handler", r.Method, r.URL) + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer r.Body.Close() + + textarea_content := string(body) + email_content, _ := strings.CutPrefix(textarea_content, "email_input=") + // Return a response to HTMX + response := fmt.Sprintf("

Received content: %s

", email_content) + w.Write([]byte(response)) + }) + + mux.HandleFunc("/mailing_list", func(w http.ResponseWriter, r *http.Request) { + body := []byte(fmt.Sprintf("{\"city\": \"%s\"}", HMC_CITY)) + + req, err := http.NewRequest("POST", HMC_API_URL, bytes.NewBuffer(body)) + if err != nil { + panic(err) + } + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", HMC_API_SECRET) + + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + panic(err) + } + defer res.Body.Close() + + response := &EmailsResonse{} + decode_error := json.NewDecoder(res.Body).Decode(response) + if decode_error != nil { + panic(decode_error) + } + + templates.ExecuteTemplate(w, "mailing_list", response) + }) + + srv := &http.Server{ + Addr: "0.0.0.0:80", + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + Handler: mux, + } + + go func() { + if err := srv.ListenAndServe(); err != nil { + log.Println(err) + } + }() + + ////////////////////////////// Cleanup ////////////////////////////// + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + log.Println("Waiting for interrupt") + // Block until we receive our signal. + <-c + + // Create a deadline to wait for. + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + // Defer calling the canclefunction returned from context.WithTimeout + defer cancel() + + // Doesn't block if no connections, but will otherwise wait + // until the timeout deadline. + srv.Shutdown(ctx) + + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should wait for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} diff --git a/static/css/site.css b/static/css/site.css new file mode 100644 index 0000000..f4e4dfd --- /dev/null +++ b/static/css/site.css @@ -0,0 +1,70 @@ +body { + width: 100%; + height: 100%; + font-family: arial; + background-color: #111; + color: #CCC; + tab-size: 4; + line-height: 1.2; + /* text-align: justify; */ +} + +body { + overflow-y: scroll; + /* Show vertical scrollbar */ +} + +.main-container { + margin-left: auto; + margin-right: auto; + width: 95%; + max-width: 1080px; + padding: 1em 0 1em 0; + /* padding-top:1em; + padding-bottom:1em; */ +} + +.content { + background-color: #222; + border-radius: 1em; + padding: 1em; +} + +h1, +h2 { + color: #40C3C3 +} + +h2 { + margin-bottom: 0.25em; +} + +a { + color: inherit; + font-style: italic; + /*font-weight: bold; */ +} + +a:hover { + color: #E8B; +} + +.card { + border-radius: 1em; + padding: 0.75em; + margin: 0.25em; + background-color: #333; + min-width: 250px; +} + +.card li { + padding-bottom: 0.2em; +} + +li { + padding-bottom: 0.5em; +} + +ul { + margin: 0.5em 0 0 0; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..13312a2 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,48 @@ +{{ block "index" . }} + + + + + + + First page + + + + + + +
+
+

This is an input

+
+
+ +
+ +
+
+ +
+
+

Current Mailing List Subscribers

+
+
+ +
+
+ +
+
+ + + +{{ end }} + +{{ block "mailing_list" . }} +{{ range .Emails }} +
  • {{.}}
  • +{{end}} +{{end}} \ No newline at end of file