mirror of
https://github.com/knadh/listmonk.git
synced 2024-11-14 11:36:51 +08:00
5a3664aee2
- Add materialized views for list -> subscriber counts, dashboard chart, and dashboard aggregate stats that slow down significantly on large databases (with millions or tens of millions of subscribers). These slow queries involve full table scan COUNTS(). - Add a toggle to enable caching slow results in Settings -> Performance. - Add support for setting a cron string that crons and periodically refreshes aggregated stats in materialized views. Closes #1019.
103 lines
3 KiB
Go
103 lines
3 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/knadh/listmonk/models"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
var bounceQuerySortFields = []string{"email", "campaign_name", "source", "created_at"}
|
|
|
|
// QueryBounces retrieves paginated bounce entries based on the given params.
|
|
// It also returns the total number of bounce records in the DB.
|
|
func (c *Core) QueryBounces(campID, subID int, source, orderBy, order string, offset, limit int) ([]models.Bounce, int, error) {
|
|
if !strSliceContains(orderBy, bounceQuerySortFields) {
|
|
orderBy = "created_at"
|
|
}
|
|
if order != SortAsc && order != SortDesc {
|
|
order = SortDesc
|
|
}
|
|
|
|
out := []models.Bounce{}
|
|
stmt := strings.ReplaceAll(c.q.QueryBounces, "%order%", orderBy+" "+order)
|
|
if err := c.db.Select(&out, stmt, 0, campID, subID, source, offset, limit); err != nil {
|
|
c.log.Printf("error fetching bounces: %v", err)
|
|
return nil, 0, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.bounce}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
total := 0
|
|
if len(out) > 0 {
|
|
total = out[0].Total
|
|
}
|
|
|
|
return out, total, nil
|
|
}
|
|
|
|
// GetBounce retrieves bounce entries based on the given params.
|
|
func (c *Core) GetBounce(id int) (models.Bounce, error) {
|
|
var out []models.Bounce
|
|
stmt := fmt.Sprintf(c.q.QueryBounces, "id", SortAsc)
|
|
if err := c.db.Select(&out, stmt, id, 0, 0, "", 0, 1); err != nil {
|
|
c.log.Printf("error fetching bounces: %v", err)
|
|
return models.Bounce{}, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.bounce}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
if len(out) == 0 {
|
|
return models.Bounce{}, echo.NewHTTPError(http.StatusBadRequest,
|
|
c.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.bounce}"))
|
|
|
|
}
|
|
|
|
return out[0], nil
|
|
}
|
|
|
|
// RecordBounce records a new bounce.
|
|
func (c *Core) RecordBounce(b models.Bounce) error {
|
|
action, ok := c.consts.BounceActions[b.Type]
|
|
if !ok {
|
|
return echo.NewHTTPError(http.StatusBadRequest, c.i18n.Ts("globals.messages.invalidData")+": "+b.Type)
|
|
}
|
|
|
|
_, err := c.q.RecordBounce.Exec(b.SubscriberUUID,
|
|
b.Email,
|
|
b.CampaignUUID,
|
|
b.Type,
|
|
b.Source,
|
|
b.Meta,
|
|
b.CreatedAt,
|
|
action.Count,
|
|
action.Action)
|
|
|
|
if err != nil {
|
|
// Ignore the error if it complained of no subscriber.
|
|
if pqErr, ok := err.(*pq.Error); ok && pqErr.Column == "subscriber_id" {
|
|
c.log.Printf("bounced subscriber (%s / %s) not found", b.SubscriberUUID, b.Email)
|
|
return nil
|
|
}
|
|
|
|
c.log.Printf("error recording bounce: %v", err)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// DeleteBounce deletes a list.
|
|
func (c *Core) DeleteBounce(id int) error {
|
|
return c.DeleteBounces([]int{id})
|
|
}
|
|
|
|
// DeleteBounces deletes multiple lists.
|
|
func (c *Core) DeleteBounces(ids []int) error {
|
|
if _, err := c.q.DeleteBounces.Exec(pq.Array(ids)); err != nil {
|
|
c.log.Printf("error deleting lists: %v", err)
|
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.list}", "error", pqErrMsg(err)))
|
|
}
|
|
return nil
|
|
}
|