diff --git a/cmd/handlers.go b/cmd/handlers.go index b031953b..5634dd31 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -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, diff --git a/cmd/public.go b/cmd/public.go index 7216cb8b..bced2723 100644 --- a/cmd/public.go +++ b/cmd/public.go @@ -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"))) +} diff --git a/cmd/subscribers.go b/cmd/subscribers.go index 353387b7..6776ddaa 100644 --- a/cmd/subscribers.go +++ b/cmd/subscribers.go @@ -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 } diff --git a/docs/docs/content/apis/subscribers.md b/docs/docs/content/apis/subscribers.md index e0e07d1f..0cc43e8d 100644 --- a/docs/docs/content/apis/subscribers.md +++ b/docs/docs/content/apis/subscribers.md @@ -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 diff --git a/i18n/en.json b/i18n/en.json index 91e4bcb8..3b9b2cac 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -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.", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index e66f30ee..a7de0c9c 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -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.", diff --git a/internal/core/subscribers.go b/internal/core/subscribers.go index f2159170..71eb407a 100644 --- a/internal/core/subscribers.go +++ b/internal/core/subscribers.go @@ -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 }