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.
178 lines
5.3 KiB
Go
178 lines
5.3 KiB
Go
package core
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gofrs/uuid"
|
|
"github.com/knadh/listmonk/models"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
// GetLists gets all lists optionally filtered by type.
|
|
func (c *Core) GetLists(typ string) ([]models.List, error) {
|
|
out := []models.List{}
|
|
|
|
if err := c.q.GetLists.Select(&out, typ, "id"); err != nil {
|
|
c.log.Printf("error fetching lists: %v", err)
|
|
return nil, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.lists}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
// Replace null tags.
|
|
for i, l := range out {
|
|
if l.Tags == nil {
|
|
out[i].Tags = []string{}
|
|
}
|
|
|
|
// Total counts.
|
|
for _, c := range l.SubscriberCounts {
|
|
out[i].SubscriberCount += c
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// QueryLists gets multiple lists based on multiple query params. Along with the paginated and sliced
|
|
// results, the total number of lists in the DB is returned.
|
|
func (c *Core) QueryLists(searchStr, typ, optin string, tags []string, orderBy, order string, offset, limit int) ([]models.List, int, error) {
|
|
_ = c.refreshCache(matListSubStats, false)
|
|
|
|
if tags == nil {
|
|
tags = []string{}
|
|
}
|
|
|
|
var (
|
|
out = []models.List{}
|
|
queryStr, stmt = makeSearchQuery(searchStr, orderBy, order, c.q.QueryLists, listQuerySortFields)
|
|
)
|
|
if err := c.db.Select(&out, stmt, 0, "", queryStr, typ, optin, pq.StringArray(tags), offset, limit); err != nil {
|
|
c.log.Printf("error fetching lists: %v", err)
|
|
return nil, 0, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.lists}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
total := 0
|
|
if len(out) > 0 {
|
|
total = out[0].Total
|
|
|
|
// Replace null tags.
|
|
for i, l := range out {
|
|
if l.Tags == nil {
|
|
out[i].Tags = []string{}
|
|
}
|
|
|
|
// Total counts.
|
|
for _, c := range l.SubscriberCounts {
|
|
out[i].SubscriberCount += c
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, total, nil
|
|
}
|
|
|
|
// GetList gets a list by its ID or UUID.
|
|
func (c *Core) GetList(id int, uuid string) (models.List, error) {
|
|
var uu interface{}
|
|
if uuid != "" {
|
|
uu = uuid
|
|
}
|
|
|
|
var res []models.List
|
|
queryStr, stmt := makeSearchQuery("", "", "", c.q.QueryLists, nil)
|
|
if err := c.db.Select(&res, stmt, id, uu, queryStr, "", "", pq.StringArray{}, 0, 1); err != nil {
|
|
c.log.Printf("error fetching lists: %v", err)
|
|
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.lists}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
if len(res) == 0 {
|
|
return models.List{}, echo.NewHTTPError(http.StatusBadRequest,
|
|
c.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.list}"))
|
|
}
|
|
|
|
out := res[0]
|
|
if out.Tags == nil {
|
|
out.Tags = []string{}
|
|
}
|
|
// Total counts.
|
|
for _, c := range out.SubscriberCounts {
|
|
out.SubscriberCount += c
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// GetListsByOptin returns lists by optin type.
|
|
func (c *Core) GetListsByOptin(ids []int, optinType string) ([]models.List, error) {
|
|
out := []models.List{}
|
|
if err := c.q.GetListsByOptin.Select(&out, optinType, pq.Array(ids), nil); err != nil {
|
|
c.log.Printf("error fetching lists for opt-in: %s", pqErrMsg(err))
|
|
return nil, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.list}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
// CreateList creates a new list.
|
|
func (c *Core) CreateList(l models.List) (models.List, error) {
|
|
uu, err := uuid.NewV4()
|
|
if err != nil {
|
|
c.log.Printf("error generating UUID: %v", err)
|
|
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorUUID", "error", err.Error()))
|
|
}
|
|
|
|
if l.Type == "" {
|
|
l.Type = models.ListTypePrivate
|
|
}
|
|
if l.Optin == "" {
|
|
l.Optin = models.ListOptinSingle
|
|
}
|
|
|
|
// Insert and read ID.
|
|
var newID int
|
|
l.UUID = uu.String()
|
|
if err := c.q.CreateList.Get(&newID, l.UUID, l.Name, l.Type, l.Optin, pq.StringArray(normalizeTags(l.Tags)), l.Description); err != nil {
|
|
c.log.Printf("error creating list: %v", err)
|
|
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.list}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
return c.GetList(newID, "")
|
|
}
|
|
|
|
// UpdateList updates a given list.
|
|
func (c *Core) UpdateList(id int, l models.List) (models.List, error) {
|
|
res, err := c.q.UpdateList.Exec(id, l.Name, l.Type, l.Optin, pq.StringArray(normalizeTags(l.Tags)), l.Description)
|
|
if err != nil {
|
|
c.log.Printf("error updating list: %v", err)
|
|
return models.List{}, echo.NewHTTPError(http.StatusInternalServerError,
|
|
c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.list}", "error", pqErrMsg(err)))
|
|
}
|
|
|
|
if n, _ := res.RowsAffected(); n == 0 {
|
|
return models.List{}, echo.NewHTTPError(http.StatusBadRequest,
|
|
c.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.list}"))
|
|
}
|
|
|
|
return c.GetList(id, "")
|
|
}
|
|
|
|
// DeleteList deletes a list.
|
|
func (c *Core) DeleteList(id int) error {
|
|
return c.DeleteLists([]int{id})
|
|
}
|
|
|
|
// DeleteLists deletes multiple lists.
|
|
func (c *Core) DeleteLists(ids []int) error {
|
|
if _, err := c.q.DeleteLists.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
|
|
}
|