mirror of
https://github.com/knadh/listmonk.git
synced 2024-09-20 07:16:33 +08:00
Make providing name
in subscriber creation optional and assign internally. Closes #1630.
This commit is contained in:
parent
a9a715696a
commit
0d74619cac
|
@ -10,6 +10,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/knadh/listmonk/internal/subimporter"
|
||||
"github.com/knadh/listmonk/models"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
@ -183,12 +184,7 @@ loop:
|
|||
func handleCreateSubscriber(c echo.Context) error {
|
||||
var (
|
||||
app = c.Get("app").(*App)
|
||||
req struct {
|
||||
models.Subscriber
|
||||
Lists []int `json:"lists"`
|
||||
ListUUIDs []string `json:"list_uuids"`
|
||||
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
|
||||
}
|
||||
req subimporter.SubReq
|
||||
)
|
||||
|
||||
// Get and validate fields.
|
||||
|
@ -197,20 +193,10 @@ func handleCreateSubscriber(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Validate fields.
|
||||
if len(req.Email) > 1000 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidEmail"))
|
||||
}
|
||||
|
||||
em, err := app.importer.SanitizeEmail(req.Email)
|
||||
req, err := app.importer.ValidateFields(req)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
req.Email = em
|
||||
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
if len(req.Name) == 0 || len(req.Name) > stdInputMaxLen {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidName"))
|
||||
}
|
||||
|
||||
// Insert the subscriber into the DB.
|
||||
sub, _, err := app.core.InsertSubscriber(req.Subscriber, req.Lists, req.ListUUIDs, req.PreconfirmSubs)
|
||||
|
|
|
@ -104,3 +104,12 @@ func strSliceContains(str string, sl []string) bool {
|
|||
func trimNullBytes(b []byte) string {
|
||||
return string(bytes.Trim(b, "\x00"))
|
||||
}
|
||||
|
||||
func titleCase(input string) string {
|
||||
parts := strings.Fields(input)
|
||||
for n, p := range parts {
|
||||
parts[n] = strings.Title(p)
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
|
|
@ -222,18 +222,6 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
onSubmit() {
|
||||
// If there is no name, auto-generate one from the e-mail.
|
||||
if (!this.form.name) {
|
||||
let name = '';
|
||||
[name] = this.form.email.toLowerCase().split('@');
|
||||
|
||||
if (name.includes('.')) {
|
||||
this.form.name = name.split('.').map((c) => this.$utils.titleCase(c)).join(' ');
|
||||
} else {
|
||||
this.form.name = this.$utils.titleCase(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isEditing) {
|
||||
this.updateSubscriber();
|
||||
return;
|
||||
|
|
|
@ -103,9 +103,9 @@ type Status struct {
|
|||
// SubReq is a wrapper over the Subscriber model.
|
||||
type SubReq struct {
|
||||
models.Subscriber
|
||||
Lists pq.Int64Array `json:"lists"`
|
||||
ListUUIDs pq.StringArray `json:"list_uuids"`
|
||||
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
|
||||
Lists []int `json:"lists"`
|
||||
ListUUIDs []string `json:"list_uuids"`
|
||||
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
|
||||
}
|
||||
|
||||
type importStatusTpl struct {
|
||||
|
@ -263,11 +263,11 @@ func (s *Session) Start() {
|
|||
total = 0
|
||||
cur = 0
|
||||
|
||||
listIDs = make(pq.Int64Array, len(s.opt.ListIDs))
|
||||
listIDs = make([]int, len(s.opt.ListIDs))
|
||||
)
|
||||
|
||||
for i, v := range s.opt.ListIDs {
|
||||
listIDs[i] = int64(v)
|
||||
listIDs[i] = v
|
||||
}
|
||||
|
||||
for sub := range s.subQueue {
|
||||
|
@ -294,7 +294,7 @@ func (s *Session) Start() {
|
|||
}
|
||||
|
||||
if s.opt.Mode == ModeSubscribe {
|
||||
_, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, listIDs, s.opt.SubStatus, s.opt.Overwrite)
|
||||
_, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, pq.Array(listIDs), s.opt.SubStatus, s.opt.Overwrite)
|
||||
} else if s.opt.Mode == ModeBlocklist {
|
||||
_, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs)
|
||||
}
|
||||
|
@ -324,7 +324,7 @@ func (s *Session) Start() {
|
|||
if cur == 0 {
|
||||
s.im.setStatus(StatusFinished)
|
||||
s.log.Printf("imported finished")
|
||||
if _, err := s.im.opt.UpdateListDateStmt.Exec(listIDs); err != nil {
|
||||
if _, err := s.im.opt.UpdateListDateStmt.Exec(pq.Array(listIDs)); err != nil {
|
||||
s.log.Printf("error updating lists date: %v", err)
|
||||
}
|
||||
s.im.sendNotif(StatusFinished)
|
||||
|
@ -343,7 +343,7 @@ func (s *Session) Start() {
|
|||
s.im.incrementImportCount(cur)
|
||||
s.im.setStatus(StatusFinished)
|
||||
s.log.Printf("imported finished")
|
||||
if _, err := s.im.opt.UpdateListDateStmt.Exec(listIDs); err != nil {
|
||||
if _, err := s.im.opt.UpdateListDateStmt.Exec(pq.Array(listIDs)); err != nil {
|
||||
s.log.Printf("error updating lists date: %v", err)
|
||||
}
|
||||
s.im.sendNotif(StatusFinished)
|
||||
|
@ -486,15 +486,11 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error {
|
|||
}
|
||||
|
||||
hdrKeys := s.mapCSVHeaders(csvHdr, csvHeaders)
|
||||
// email, and name are required headers.
|
||||
// email is a required header.
|
||||
if _, ok := hdrKeys["email"]; !ok {
|
||||
s.log.Printf("'email' column not found in '%s'", srcPath)
|
||||
return errors.New("'email' column not found")
|
||||
}
|
||||
if _, ok := hdrKeys["name"]; !ok {
|
||||
s.log.Printf("'name' column not found in '%s'", srcPath)
|
||||
return errors.New("'name' column not found")
|
||||
}
|
||||
|
||||
var (
|
||||
lnHdr = len(hdrKeys)
|
||||
|
@ -541,9 +537,12 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error {
|
|||
|
||||
sub := SubReq{}
|
||||
sub.Email = row["email"]
|
||||
sub.Name = row["name"]
|
||||
|
||||
sub, err = s.im.validateFields(sub)
|
||||
if v, ok := row["name"]; ok {
|
||||
sub.Name = v
|
||||
}
|
||||
|
||||
sub, err = s.im.ValidateFields(sub)
|
||||
if err != nil {
|
||||
s.log.Printf("skipping line %d: %s: %v", i, sub.Email, err)
|
||||
continue
|
||||
|
@ -630,23 +629,31 @@ func (im *Importer) SanitizeEmail(email string) (string, error) {
|
|||
return em.Address, nil
|
||||
}
|
||||
|
||||
// validateFields validates incoming subscriber field values and returns sanitized fields.
|
||||
func (im *Importer) validateFields(s SubReq) (SubReq, error) {
|
||||
// ValidateFields validates incoming subscriber field values and returns sanitized fields.
|
||||
func (im *Importer) ValidateFields(s SubReq) (SubReq, error) {
|
||||
if len(s.Email) > 1000 {
|
||||
return s, errors.New(im.i18n.T("subscribers.invalidEmail"))
|
||||
}
|
||||
|
||||
s.Name = strings.TrimSpace(s.Name)
|
||||
if len(s.Name) == 0 || len(s.Name) > stdInputMaxLen {
|
||||
return s, errors.New(im.i18n.T("subscribers.invalidName"))
|
||||
}
|
||||
|
||||
em, err := im.SanitizeEmail(s.Email)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.Email = strings.ToLower(em)
|
||||
|
||||
// If there's no name, use the name part of the e-mail.
|
||||
s.Name = strings.TrimSpace(s.Name)
|
||||
if len(s.Name) == 0 {
|
||||
name := strings.ToLower(strings.Split(s.Email, "@")[0])
|
||||
|
||||
parts := strings.Fields(strings.ReplaceAll(name, ".", " "))
|
||||
for n, p := range parts {
|
||||
parts[n] = strings.Title(p)
|
||||
}
|
||||
|
||||
s.Name = strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue