2022-07-02 10:00:17 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-03-19 10:20:44 +00:00
|
|
|
"encoding/json"
|
2022-07-02 10:37:29 +00:00
|
|
|
"fmt"
|
2023-11-16 08:27:00 +00:00
|
|
|
"io"
|
2022-07-02 10:00:17 +00:00
|
|
|
"net/http"
|
|
|
|
"net/textproto"
|
2022-12-25 12:01:22 +00:00
|
|
|
"strings"
|
2022-07-02 10:00:17 +00:00
|
|
|
|
|
|
|
"github.com/knadh/listmonk/internal/manager"
|
|
|
|
"github.com/knadh/listmonk/models"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
)
|
|
|
|
|
|
|
|
// handleSendTxMessage handles the sending of a transactional message.
|
|
|
|
func handleSendTxMessage(c echo.Context) error {
|
|
|
|
var (
|
|
|
|
app = c.Get("app").(*App)
|
|
|
|
m models.TxMessage
|
|
|
|
)
|
|
|
|
|
2023-03-19 10:20:44 +00:00
|
|
|
// If it's a multipart form, there may be file attachments.
|
|
|
|
if strings.HasPrefix(c.Request().Header.Get("Content-Type"), "multipart/form-data") {
|
|
|
|
form, err := c.MultipartForm()
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", err.Error()))
|
|
|
|
}
|
|
|
|
|
|
|
|
data, ok := form.Value["data"]
|
|
|
|
if !ok || len(data) != 1 {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", "data"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the JSON data.
|
|
|
|
if err := json.Unmarshal([]byte(data[0]), &m); err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", fmt.Sprintf("data: %s", err.Error())))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach files.
|
|
|
|
for _, f := range form.File["file"] {
|
|
|
|
file, err := f.Open()
|
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", fmt.Sprintf("file: %s", err.Error())))
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2023-11-16 08:27:00 +00:00
|
|
|
b, err := io.ReadAll(file)
|
2023-03-19 10:20:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", fmt.Sprintf("file: %s", err.Error())))
|
|
|
|
}
|
|
|
|
|
2023-05-08 17:13:25 +00:00
|
|
|
m.Attachments = append(m.Attachments, models.Attachment{
|
2023-03-19 10:20:44 +00:00
|
|
|
Name: f.Filename,
|
2023-05-18 11:25:59 +00:00
|
|
|
Header: manager.MakeAttachmentHeader(f.Filename, "base64", f.Header.Get("Content-Type")),
|
2023-03-19 10:20:44 +00:00
|
|
|
Content: b,
|
|
|
|
})
|
|
|
|
}
|
2023-05-18 11:25:59 +00:00
|
|
|
|
2023-03-19 10:20:44 +00:00
|
|
|
} else if err := c.Bind(&m); err != nil {
|
2022-07-02 10:00:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate input.
|
|
|
|
if r, err := validateTxMessage(m, app); err != nil {
|
|
|
|
return err
|
|
|
|
} else {
|
|
|
|
m = r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the cached tx template.
|
|
|
|
tpl, err := app.manager.GetTpl(m.TemplateID)
|
|
|
|
if err != nil {
|
2022-07-02 10:37:29 +00:00
|
|
|
return echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.notFound", "name", fmt.Sprintf("template %d", m.TemplateID)))
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-25 12:01:22 +00:00
|
|
|
var (
|
|
|
|
num = len(m.SubscriberEmails)
|
|
|
|
isEmails = true
|
|
|
|
)
|
|
|
|
if len(m.SubscriberIDs) > 0 {
|
|
|
|
num = len(m.SubscriberIDs)
|
|
|
|
isEmails = false
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-25 12:01:22 +00:00
|
|
|
notFound := []string{}
|
|
|
|
for n := 0; n < num; n++ {
|
|
|
|
var (
|
|
|
|
subID int
|
|
|
|
subEmail string
|
|
|
|
)
|
|
|
|
|
|
|
|
if !isEmails {
|
|
|
|
subID = m.SubscriberIDs[n]
|
|
|
|
} else {
|
|
|
|
subEmail = m.SubscriberEmails[n]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the subscriber.
|
|
|
|
sub, err := app.core.GetSubscriber(subID, "", subEmail)
|
|
|
|
if err != nil {
|
|
|
|
// If the subscriber is not found, log that error and move on without halting on the list.
|
|
|
|
if er, ok := err.(*echo.HTTPError); ok && er.Code == http.StatusBadRequest {
|
|
|
|
notFound = append(notFound, fmt.Sprintf("%v", er.Message))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the message.
|
|
|
|
if err := m.Render(sub, tpl); err != nil {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.errorFetching", "name"))
|
|
|
|
}
|
2022-07-02 10:00:17 +00:00
|
|
|
|
2022-12-25 12:01:22 +00:00
|
|
|
// Prepare the final message.
|
2023-05-08 17:13:25 +00:00
|
|
|
msg := models.Message{}
|
2022-12-25 12:01:22 +00:00
|
|
|
msg.Subscriber = sub
|
|
|
|
msg.To = []string{sub.Email}
|
|
|
|
msg.From = m.FromEmail
|
|
|
|
msg.Subject = m.Subject
|
|
|
|
msg.ContentType = m.ContentType
|
|
|
|
msg.Messenger = m.Messenger
|
|
|
|
msg.Body = m.Body
|
2023-03-19 10:20:44 +00:00
|
|
|
for _, a := range m.Attachments {
|
2023-05-08 17:13:25 +00:00
|
|
|
msg.Attachments = append(msg.Attachments, models.Attachment{
|
2023-03-19 10:20:44 +00:00
|
|
|
Name: a.Name,
|
|
|
|
Header: a.Header,
|
|
|
|
Content: a.Content,
|
|
|
|
})
|
|
|
|
}
|
2022-12-25 12:01:22 +00:00
|
|
|
|
|
|
|
// Optional headers.
|
|
|
|
if len(m.Headers) != 0 {
|
|
|
|
msg.Headers = make(textproto.MIMEHeader, len(m.Headers))
|
|
|
|
for _, set := range m.Headers {
|
|
|
|
for hdr, val := range set {
|
|
|
|
msg.Headers.Add(hdr, val)
|
|
|
|
}
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-25 12:01:22 +00:00
|
|
|
|
|
|
|
if err := app.manager.PushMessage(msg); err != nil {
|
|
|
|
app.log.Printf("error sending message (%s): %v", msg.Subject, err)
|
|
|
|
return err
|
|
|
|
}
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-25 12:01:22 +00:00
|
|
|
if len(notFound) > 0 {
|
|
|
|
return echo.NewHTTPError(http.StatusBadRequest, strings.Join(notFound, "; "))
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(http.StatusOK, okResp{true})
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateTxMessage(m models.TxMessage, app *App) (models.TxMessage, error) {
|
2022-12-25 12:01:22 +00:00
|
|
|
if len(m.SubscriberEmails) > 0 && m.SubscriberEmail != "" {
|
2022-07-02 10:00:17 +00:00
|
|
|
return m, echo.NewHTTPError(http.StatusBadRequest,
|
2022-12-25 12:01:22 +00:00
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", "do not send `subscriber_email`"))
|
|
|
|
}
|
|
|
|
if len(m.SubscriberIDs) > 0 && m.SubscriberID != 0 {
|
|
|
|
return m, echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", "do not send `subscriber_id`"))
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if m.SubscriberEmail != "" {
|
2022-12-25 12:01:22 +00:00
|
|
|
m.SubscriberEmails = append(m.SubscriberEmails, m.SubscriberEmail)
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.SubscriberID != 0 {
|
|
|
|
m.SubscriberIDs = append(m.SubscriberIDs, m.SubscriberID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len(m.SubscriberEmails) == 0 && len(m.SubscriberIDs) == 0) || (len(m.SubscriberEmails) > 0 && len(m.SubscriberIDs) > 0) {
|
|
|
|
return m, echo.NewHTTPError(http.StatusBadRequest,
|
|
|
|
app.i18n.Ts("globals.messages.invalidFields", "name", "send subscriber_emails OR subscriber_ids"))
|
|
|
|
}
|
|
|
|
|
|
|
|
for n, email := range m.SubscriberEmails {
|
|
|
|
if m.SubscriberEmail != "" {
|
|
|
|
em, err := app.importer.SanitizeEmail(email)
|
|
|
|
if err != nil {
|
|
|
|
return m, echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
|
|
|
}
|
|
|
|
m.SubscriberEmails[n] = em
|
2022-07-02 10:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.FromEmail == "" {
|
|
|
|
m.FromEmail = app.constants.FromEmail
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.Messenger == "" {
|
|
|
|
m.Messenger = emailMsgr
|
|
|
|
} else if !app.manager.HasMessenger(m.Messenger) {
|
|
|
|
return m, echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("campaigns.fieldInvalidMessenger", "name", m.Messenger))
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|