Compare commits

...

6 commits

Author SHA1 Message Date
Macgayver Armini 8d8534fa19
Merge dccbdf90f3 into 550cd3e1f8 2024-09-06 13:59:20 -07:00
Bishop Clark 550cd3e1f8
Update README.md (#2034)
'software', when used as a noun, is not a 'countable' type, and it does not get an article like 'a'.  It's like 'traffic'.
2024-09-06 15:49:53 +05:30
Macgayver Armini dccbdf90f3 Add no_lists param to subscriber query. 2024-08-19 09:37:09 -03:00
Macgayver Armini 681f3073c6 Add Portuguese new string. 2024-08-12 07:49:05 -03:00
Macgayver Armini d24a3e787f Allows you to unsubscribe from lists directly without using uuid campaigns. 2024-08-12 07:44:06 -03:00
Macgayver Armini 23a4a7a492 Added support for unsubscribing from lists. 2024-08-09 17:22:03 -03:00
8 changed files with 75 additions and 8 deletions

View file

@ -48,7 +48,7 @@ __________________
## Developers
listmonk is a free and open source software licensed under AGPLv3. If you are interested in contributing, refer to the [developer setup](https://listmonk.app/docs/developer-setup). The backend is written in Go and the frontend is Vue with Buefy for UI.
listmonk is free and open source software licensed under AGPLv3. If you are interested in contributing, refer to the [developer setup](https://listmonk.app/docs/developer-setup). The backend is written in Go and the frontend is Vue with Buefy for UI.
## License

View file

@ -196,6 +196,11 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
"subUUID"))
e.POST("/subscription/wipe/:subUUID", validateUUID(subscriberExists(handleWipeSubscriberData),
"subUUID"))
// one-click unsubscribe from list
e.POST("/unsubscribe/:subUUID/:listUUID", unsubscribeList)
e.GET("/unsubscribe/:subUUID/:listUUID", noIndex(validateUUID(subscriberExists(handleListSubscriptionPage), "subUUID", "listUUID")))
e.GET("/link/:linkUUID/:campUUID/:subUUID", noIndex(validateUUID(handleLinkRedirect,
"linkUUID", "campUUID", "subUUID")))
e.GET("/campaign/:campUUID/:subUUID", noIndex(validateUUID(handleViewCampaignMessage,

View file

@ -730,3 +730,60 @@ func processSubForm(c echo.Context) (bool, error) {
return hasOptin, nil
}
func unsubscribeList(c echo.Context) error {
var (
app = c.Get("app").(*App)
subUUID = c.Param("subUUID")
listUUID = c.Param("listUUID")
)
// Get the subscriber ID from the UUID.
sub, err := app.core.GetSubscriber(0, subUUID, "")
if err != nil {
return c.String(http.StatusInternalServerError, "Error processing request")
}
// Unsubscribe from the list.
if err := app.core.UnsubscribeLists([]int{sub.ID}, nil, []string{listUUID}); err != nil {
return c.String(http.StatusInternalServerError, "Error processing request")
}
return c.String(http.StatusOK, "You have been unsubscribed successfully")
}
func handleListSubscriptionPage(c echo.Context) error {
var (
app = c.Get("app").(*App)
subUUID = c.Param("subUUID")
listUUID = c.Param("listUUID")
)
// Get the subscriber ID from the UUID.
sub, err := app.core.GetSubscriber(0, subUUID, "")
if err != nil {
return c.Render(http.StatusInternalServerError, tplMessage,
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorProcessingRequest")))
}
// Check if the subscriber is already unsubscribed from the list
subscription, err := app.core.GetSubscriptions(0, listUUID, true)
if err != nil {
return c.Render(http.StatusInternalServerError, tplMessage,
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.T("public.errorProcessingRequest")))
}
if len(subscription) > 0 && subscription[0].SubscriptionStatus.String == models.SubscriptionStatusUnsubscribed {
return c.Render(http.StatusOK, tplMessage,
makeMsgTpl(app.i18n.T("public.unsubbedTitle"), "", app.i18n.T("public.unsubbedAlreadyInfo")))
}
// Unsubscribe from the list.
if err := app.core.UnsubscribeLists([]int{sub.ID}, nil, []string{listUUID}); err != nil {
return c.Render(http.StatusInternalServerError, tplMessage,
makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.T("public.errorProcessingRequest")))
}
return c.Render(http.StatusOK, tplMessage,
makeMsgTpl(app.i18n.T("public.unsubbedTitle"), "", app.i18n.T("public.unsubbedInfo")))
}

View file

@ -92,6 +92,7 @@ func handleQuerySubscribers(c echo.Context) error {
subStatus = c.FormValue("subscription_status")
orderBy = c.FormValue("order_by")
order = c.FormValue("order")
noLists = c.FormValue("no_lists") == "true"
out models.PageResults
)
@ -101,7 +102,7 @@ func handleQuerySubscribers(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
}
res, total, err := app.core.QuerySubscribers(query, listIDs, subStatus, order, orderBy, pg.Offset, pg.Limit)
res, total, err := app.core.QuerySubscribers(query, listIDs, subStatus, order, orderBy, pg.Offset, pg.Limit, noLists)
if err != nil {
return err
}

View file

@ -36,6 +36,7 @@ Retrieve all subscribers.
| order | string | | Sorting order: ASC for ascending, DESC for descending. |
| page | number | | Page number for paginated results. |
| per_page | number | | Results per page. Set as 'all' for all results. |
| no_lists | bool | | The lists to which the subscriber is registered are not displayed. |
##### Example Request

View file

@ -353,6 +353,7 @@
"public.unsubHelp": "Do you want to unsubscribe from this mailing list?",
"public.unsubTitle": "Unsubscribe",
"public.unsubbedInfo": "You have unsubscribed successfully.",
"public.unsubbedAlreadyInfo": "You have already unsubscribed successfully.",
"public.unsubbedTitle": "Unsubscribed",
"public.unsubscribeTitle": "Unsubscribe from mailing list",
"settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.",

View file

@ -352,6 +352,7 @@
"public.unsubHelp": "Deseja cancelar a inscrição desta lista de e-mail?",
"public.unsubTitle": "Cancelar inscrição",
"public.unsubbedInfo": "Você cancelou a inscrição com sucesso.",
"public.unsubbedAlreadyInfo": "Você já cancelou a inscrição com sucesso.",
"public.unsubbedTitle": "Inscrição cancelada",
"public.unsubscribeTitle": "Cancelar inscrição na lista de e-mails",
"settings.appearance.adminHelp": "CSS customizado para aplicar na admin UI.",

View file

@ -66,7 +66,7 @@ func (c *Core) GetSubscribersByEmail(emails []string) (models.Subscribers, error
}
// QuerySubscribers queries and returns paginated subscrribers based on the given params including the total count.
func (c *Core) QuerySubscribers(query string, listIDs []int, subStatus string, order, orderBy string, offset, limit int) (models.Subscribers, int, error) {
func (c *Core) QuerySubscribers(query string, listIDs []int, subStatus string, order, orderBy string, offset, limit int, noLists bool) (models.Subscribers, int, error) {
// There's an arbitrary query condition.
cond := ""
if query != "" {
@ -117,12 +117,13 @@ func (c *Core) QuerySubscribers(query string, listIDs []int, subStatus string, o
}
// Lazy load lists for each subscriber.
if err := out.LoadLists(c.q.GetSubscriberListsLazy); err != nil {
c.log.Printf("error fetching subscriber lists: %v", err)
return nil, 0, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
if !noLists {
if err := out.LoadLists(c.q.GetSubscriberListsLazy); err != nil {
c.log.Printf("error fetching subscriber lists: %v", err)
return nil, 0, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
}
}
return out, total, nil
}