Add the ability to view a preview of the email entered into the textarea
parent
cf5c04759a
commit
5934036a09
99
main.go
99
main.go
|
@ -9,6 +9,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
@ -17,20 +18,80 @@ import (
|
|||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// ------------- Api configs -------------
|
||||
type DefaultSectionConfig struct {
|
||||
Live bool
|
||||
TestEmail string
|
||||
SenderName string
|
||||
}
|
||||
|
||||
type HMCConfig struct {
|
||||
ApiSecret string
|
||||
ApiUrl string
|
||||
City string
|
||||
}
|
||||
|
||||
type PostmarkConfig struct {
|
||||
ServerToken string
|
||||
ApiUrl string
|
||||
TemplateId int
|
||||
SenderEmail string
|
||||
MessageStream string
|
||||
}
|
||||
|
||||
type ApiConfig struct {
|
||||
Default DefaultSectionConfig
|
||||
Hmc HMCConfig
|
||||
Postmark PostmarkConfig
|
||||
}
|
||||
|
||||
// ------------- HMC Api Responses -------------
|
||||
type EmailsResonse struct {
|
||||
Emails []string `json:"emails"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Load the HMCsend-email app.ini and store the values in a structure, some of the keys in here
|
||||
// we probably don't actualy care about for this usage.
|
||||
func loadConfig() ApiConfig {
|
||||
// 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()
|
||||
|
||||
defSection := config.Section("DEFAULT")
|
||||
def := DefaultSectionConfig{
|
||||
Live: defSection.Key("LIVE").MustBool(),
|
||||
TestEmail: defSection.Key("TEST_EMAIL").String(),
|
||||
SenderName: defSection.Key("SENDER_NAME").String(),
|
||||
}
|
||||
|
||||
hmcSection := config.Section("hmc")
|
||||
hmc := HMCConfig{
|
||||
ApiSecret: hmcSection.Key("SHARED_SECRET").String(),
|
||||
ApiUrl: hmcSection.Key("API_URL").String(),
|
||||
City: hmcSection.Key("CITY").String(),
|
||||
}
|
||||
|
||||
postmarkSection := config.Section("postmark")
|
||||
postmark := PostmarkConfig{
|
||||
ServerToken: postmarkSection.Key("SERVER_TOKEN").String(),
|
||||
ApiUrl: postmarkSection.Key("API_URL").String(),
|
||||
TemplateId: postmarkSection.Key("TEMPLATE_ID").MustInt(),
|
||||
SenderEmail: postmarkSection.Key("SENDER_EMAIL").String(),
|
||||
MessageStream: postmarkSection.Key("MESSAGE_STREAM").String(),
|
||||
}
|
||||
|
||||
return ApiConfig{
|
||||
Default: def,
|
||||
Hmc: hmc,
|
||||
Postmark: postmark,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
apiConfig := loadConfig()
|
||||
postmarkTemplate := getPostmarkTemplate(apiConfig.Postmark)
|
||||
|
||||
// Prepare the go html templates and static file server
|
||||
templates := template.Must(template.ParseGlob("templates/*.html"))
|
||||
|
@ -39,11 +100,13 @@ func main() {
|
|||
mux := http.NewServeMux()
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", static_files))
|
||||
|
||||
// The base endpoint for the web-app simply renders the index tempalte
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Hello '%v %v' handler", r.Method, r.URL)
|
||||
templates.ExecuteTemplate(w, "index", nil)
|
||||
})
|
||||
|
||||
// Endpoint called when editing the text area and posts to the postmark api to generate the email preview
|
||||
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)
|
||||
|
@ -52,24 +115,34 @@ func main() {
|
|||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
textarea_content := string(body)
|
||||
email_content, _ := strings.CutPrefix(textarea_content, "email_input=")
|
||||
decoded_textarea, err := url.QueryUnescape(textarea_content)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
email_content, _ := strings.CutPrefix(decoded_textarea, "email_input=")
|
||||
|
||||
// Return a response to HTMX
|
||||
response := fmt.Sprintf("<p>Received content: %s</p>", email_content)
|
||||
w.Write([]byte(response))
|
||||
model := PostmarkTemplateModel{
|
||||
Name: apiConfig.Default.SenderName,
|
||||
Email: apiConfig.Default.TestEmail,
|
||||
Body: email_content,
|
||||
Subject: "This is a test subject", // TODO(jack): Get from user input on page
|
||||
}
|
||||
w.Write([]byte(renderPostmarkTemplate(apiConfig.Postmark, postmarkTemplate, model)))
|
||||
})
|
||||
|
||||
// Endpoint called on load or button press by htmx to retrieve and populate the mailing list
|
||||
mux.HandleFunc("/mailing_list", func(w http.ResponseWriter, r *http.Request) {
|
||||
body := []byte(fmt.Sprintf("{\"city\": \"%s\"}", HMC_CITY))
|
||||
body := []byte(fmt.Sprintf("{\"city\": \"%s\"}", apiConfig.Hmc.City))
|
||||
|
||||
req, err := http.NewRequest("POST", HMC_API_URL, bytes.NewBuffer(body))
|
||||
req, err := http.NewRequest("POST", apiConfig.Hmc.ApiUrl, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", HMC_API_SECRET)
|
||||
req.Header.Add("Authorization", apiConfig.Hmc.ApiSecret)
|
||||
|
||||
client := &http.Client{}
|
||||
res, err := client.Do(req)
|
||||
|
@ -79,7 +152,7 @@ func main() {
|
|||
defer res.Body.Close()
|
||||
|
||||
response := &EmailsResonse{}
|
||||
decode_error := json.NewDecoder(res.Body).Decode(response)
|
||||
decode_error := json.NewDecoder(res.Body).Decode(&response)
|
||||
if decode_error != nil {
|
||||
panic(decode_error)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// The response from a get reqeuest to the postmark tempalte API.
|
||||
type PostmarkTemplate struct {
|
||||
TemplateId int
|
||||
Name string
|
||||
Subject string
|
||||
HtmlBody string
|
||||
TextBody string
|
||||
AssociatedServerId int
|
||||
Active bool
|
||||
Alias string
|
||||
TemplateType string
|
||||
LayoutTemplate string
|
||||
}
|
||||
|
||||
// The model that we're using for the template that abner made
|
||||
type PostmarkTemplateModel struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Body string `json:"body"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
// The request body for the postmark validate endpoint which renders a provided tempalte if its valid
|
||||
type ValidateTemplateBody struct {
|
||||
Subject string
|
||||
HTMLBody string
|
||||
TextBody string
|
||||
|
||||
TestRenderModel PostmarkTemplateModel
|
||||
// There are other body options but i think this is all we care about
|
||||
// InlineCssForHtmlTestRender bool
|
||||
// TemplateType string
|
||||
}
|
||||
|
||||
// The validation error types returned from the postmark template validation api
|
||||
type ValidationError struct {
|
||||
Message string
|
||||
Line int
|
||||
CharacterPosition int
|
||||
}
|
||||
|
||||
type ContentBodyValidationResponse struct {
|
||||
ContentIsValid bool
|
||||
ValidationErrors []ValidationError
|
||||
RenderedContent string
|
||||
}
|
||||
|
||||
// The response from the postmark tempalte validation endpoint
|
||||
type ValidationResponse struct {
|
||||
AllContentIsValid bool
|
||||
Subject ContentBodyValidationResponse
|
||||
HtmlBody ContentBodyValidationResponse
|
||||
TextBody ContentBodyValidationResponse
|
||||
SuggestedTemplateModel interface{}
|
||||
}
|
||||
|
||||
// Gets the tempalte in the postmark config section of the app.ini file from teh postmark server so we can render it
|
||||
func getPostmarkTemplate(cfg PostmarkConfig) PostmarkTemplate {
|
||||
// Get Template
|
||||
getTemplateURL := fmt.Sprintf("https://api.postmarkapp.com/templates/%v", cfg.TemplateId)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getTemplateURL, bytes.NewBuffer([]byte("")))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
getReq.Header.Add("Accept", "application/json")
|
||||
getReq.Header.Add("X-Postmark-Server-Token", cfg.ServerToken)
|
||||
|
||||
client := &http.Client{}
|
||||
getResp, err := client.Do(getReq)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer getReq.Body.Close()
|
||||
|
||||
template := PostmarkTemplate{}
|
||||
decode_error := json.NewDecoder(getResp.Body).Decode(&template)
|
||||
|
||||
if decode_error != nil {
|
||||
panic(decode_error)
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
||||
// Given a tempalte and model use the postmark tempalte validation api to render the template
|
||||
func renderPostmarkTemplate(cfg PostmarkConfig, template PostmarkTemplate, model PostmarkTemplateModel) string {
|
||||
bodyObj := ValidateTemplateBody{
|
||||
Subject: template.Subject,
|
||||
HTMLBody: template.HtmlBody,
|
||||
TextBody: template.TextBody,
|
||||
TestRenderModel: model,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(bodyObj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.postmarkapp.com/templates/validate", bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("X-Postmark-Server-Token", cfg.ServerToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
response := &ValidationResponse{}
|
||||
rawBodyContent, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
decode_error := json.NewDecoder(bytes.NewBuffer(rawBodyContent)).Decode(response)
|
||||
if decode_error != nil {
|
||||
panic(decode_error)
|
||||
}
|
||||
|
||||
return response.HtmlBody.RenderedContent
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
<div class="row" style="display: flex;">
|
||||
<div class="column" style="flex: 50%;">
|
||||
<textarea name="email_input" id="typebox" rows="32" cols="64" style="resize: none;"
|
||||
hx-trigger="keyup changed delay:500ms" hx-post="/mail-content" hx-target="#email-preview"
|
||||
hx-trigger="keyup changed delay:1000ms" hx-post="/mail-content" hx-target="#email-preview"
|
||||
placeholder="Type out your email here..."></textarea>
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in New Issue