From 01acd386f60b42338748cabf67d1da6aa455d714 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 23 Dec 2023 14:45:55 +0530 Subject: [PATCH] Add tag/type/optin filter options to lists and campaigns APIs. Closes #1631. --- cmd/campaigns.go | 3 ++- cmd/lists.go | 5 ++++- docs/docs/content/apis/campaigns.md | 16 +++++++++------- docs/docs/content/apis/lists.md | 16 +++++++++------- internal/core/campaigns.go | 8 ++++++-- internal/core/lists.go | 10 +++++++--- queries.sql | 27 +++++++++++++++------------ 7 files changed, 52 insertions(+), 33 deletions(-) diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 517b989a..17fcaadd 100644 --- a/cmd/campaigns.go +++ b/cmd/campaigns.go @@ -57,13 +57,14 @@ func handleGetCampaigns(c echo.Context) error { pg = app.paginator.NewFromURL(c.Request().URL.Query()) status = c.QueryParams()["status"] + tags = c.QueryParams()["tag"] query = strings.TrimSpace(c.FormValue("query")) orderBy = c.FormValue("order_by") order = c.FormValue("order") noBody, _ = strconv.ParseBool(c.QueryParam("no_body")) ) - res, total, err := app.core.QueryCampaigns(query, status, orderBy, order, pg.Offset, pg.Limit) + res, total, err := app.core.QueryCampaigns(query, status, tags, orderBy, order, pg.Offset, pg.Limit) if err != nil { return err } diff --git a/cmd/lists.go b/cmd/lists.go index f4548951..7a8d34e7 100644 --- a/cmd/lists.go +++ b/cmd/lists.go @@ -16,7 +16,10 @@ func handleGetLists(c echo.Context) error { pg = app.paginator.NewFromURL(c.Request().URL.Query()) query = strings.TrimSpace(c.FormValue("query")) + tags = c.QueryParams()["tag"] orderBy = c.FormValue("order_by") + typ = c.FormValue("type") + optin = c.FormValue("optin") order = c.FormValue("order") minimal, _ = strconv.ParseBool(c.FormValue("minimal")) listID, _ = strconv.Atoi(c.Param("id")) @@ -58,7 +61,7 @@ func handleGetLists(c echo.Context) error { } // Full list query. - res, total, err := app.core.QueryLists(query, orderBy, order, pg.Offset, pg.Limit) + res, total, err := app.core.QueryLists(query, typ, optin, tags, orderBy, order, pg.Offset, pg.Limit) if err != nil { return err } diff --git a/docs/docs/content/apis/campaigns.md b/docs/docs/content/apis/campaigns.md index b169daa1..66e95a97 100644 --- a/docs/docs/content/apis/campaigns.md +++ b/docs/docs/content/apis/campaigns.md @@ -26,13 +26,15 @@ Retrieve all campaigns. ##### Parameters -| Name | Type | Required | Description | -|:---------|:-------|:---------|:---------------------------------------------------------------------| -| order | string | | Sorting order: ASC for ascending, DESC for descending. | -| order_by | string | | Result sorting field. Options: name, status, created_at, updated_at. | -| query | string | | SQL query expression to filter subscribers. | -| page | number | | Page number for paginated results. | -| per_page | number | | Results per page. Set as 'all' for all results. | +| Name | Type | Required | Description | +|:---------|:---------|:---------|:---------------------------------------------------------------------| +| order | string | | Sorting order: ASC for ascending, DESC for descending. | +| order_by | string | | Result sorting field. Options: name, status, created_at, updated_at. | +| query | string | | SQL query expression to filter campaigns. | +| status | []string | | Status to filter campaigns. Repeat in the query for multiple values. | +| tags | []string | | Tags to filter campaigns. Repeat in the query for multiple values. | +| page | number | | Page number for paginated results. | +| per_page | number | | Results per page. Set as 'all' for all results. | ##### Example Response diff --git a/docs/docs/content/apis/lists.md b/docs/docs/content/apis/lists.md index b946962f..80c11869 100644 --- a/docs/docs/content/apis/lists.md +++ b/docs/docs/content/apis/lists.md @@ -16,13 +16,15 @@ Retrieve lists. ##### Parameters -| Name | Type | Required | Description | -|:---------|:----------|:---------|:-----------------------------------------------------------| -| query | string | | string for list name search. | -| order_by | string | | Sort field. Options: name, status, created_at, updated_at. | -| order | string | | Sorting order. Options: ASC, DESC. | -| page | number | | Page number for pagination. | -| per_page | number | | Results per page. Set to 'all' to return all results. | +| Name | Type | Required | Description | +|:---------|:---------|:---------|:-----------------------------------------------------------------| +| query | string | | string for list name search. | +| status | []string | | Status to filter lists. Repeat in the query for multiple values. | +| tags | []string | | Tags to filter lists. Repeat in the query for multiple values. | +| order_by | string | | Sort field. Options: name, status, created_at, updated_at. | +| order | string | | Sorting order. Options: ASC, DESC. | +| page | number | | Page number for pagination. | +| per_page | number | | Results per page. Set to 'all' to return all results. | ##### Example Request diff --git a/internal/core/campaigns.go b/internal/core/campaigns.go index 2f214c7f..2ced8119 100644 --- a/internal/core/campaigns.go +++ b/internal/core/campaigns.go @@ -23,16 +23,20 @@ const ( // QueryCampaigns retrieves paginated campaigns optionally filtering them by the given arbitrary // query expression. It also returns the total number of records in the DB. -func (c *Core) QueryCampaigns(searchStr string, statuses []string, orderBy, order string, offset, limit int) (models.Campaigns, int, error) { +func (c *Core) QueryCampaigns(searchStr string, statuses, tags []string, orderBy, order string, offset, limit int) (models.Campaigns, int, error) { queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryCampaigns, campQuerySortFields) if statuses == nil { statuses = []string{} } + if tags == nil { + tags = []string{} + } + // Unsafe to ignore scanning fields not present in models.Campaigns. var out models.Campaigns - if err := c.db.Select(&out, stmt, 0, pq.Array(statuses), queryStr, offset, limit); err != nil { + if err := c.db.Select(&out, stmt, 0, pq.StringArray(statuses), pq.StringArray(tags), queryStr, offset, limit); err != nil { c.log.Printf("error fetching campaigns: %v", err) return nil, 0, echo.NewHTTPError(http.StatusInternalServerError, c.i18n.Ts("globals.messages.errorFetching", "name", "{globals.terms.campaign}", "error", pqErrMsg(err))) diff --git a/internal/core/lists.go b/internal/core/lists.go index e66eba06..85cfad84 100644 --- a/internal/core/lists.go +++ b/internal/core/lists.go @@ -36,12 +36,16 @@ func (c *Core) GetLists(typ string) ([]models.List, error) { // 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, orderBy, order string, offset, limit int) ([]models.List, int, error) { +func (c *Core) QueryLists(searchStr, typ, optin string, tags []string, orderBy, order string, offset, limit int) ([]models.List, int, error) { out := []models.List{} queryStr, stmt := makeSearchQuery(searchStr, orderBy, order, c.q.QueryLists, listQuerySortFields) - if err := c.db.Select(&out, stmt, 0, "", queryStr, offset, limit); err != nil { + if tags == nil { + tags = []string{} + } + + 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))) @@ -76,7 +80,7 @@ func (c *Core) GetList(id int, uuid string) (models.List, error) { var res []models.List queryStr, stmt := makeSearchQuery("", "", "", c.q.QueryLists, nil) - if err := c.db.Select(&res, stmt, id, uu, queryStr, 0, 1); err != 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))) diff --git a/queries.sql b/queries.sql index 92972b38..d4a96583 100644 --- a/queries.sql +++ b/queries.sql @@ -408,15 +408,17 @@ SELECT * FROM lists WHERE (CASE WHEN $1 = '' THEN 1=1 ELSE type=$1::list_type EN -- name: query-lists WITH ls AS ( - SELECT COUNT(*) OVER () AS total, lists.* FROM lists - WHERE - CASE - WHEN $1 > 0 THEN id = $1 - WHEN $2 != '' THEN uuid = $2::UUID - WHEN $3 != '' THEN to_tsvector(name) @@ to_tsquery ($3) - ELSE true - END - OFFSET $4 LIMIT (CASE WHEN $5 < 1 THEN NULL ELSE $5 END) + SELECT COUNT(*) OVER () AS total, lists.* FROM lists WHERE + CASE + WHEN $1 > 0 THEN id = $1 + WHEN $2 != '' THEN uuid = $2::UUID + WHEN $3 != '' THEN to_tsvector(name) @@ to_tsquery ($3) + ELSE TRUE + END + AND ($4 = '' OR type = $4::list_type) + AND ($5 = '' OR optin = $5::list_optin) + AND (CARDINALITY($6::VARCHAR(100)[]) = 0 OR $6 <@ tags) + OFFSET $7 LIMIT (CASE WHEN $8 < 1 THEN NULL ELSE $8 END) ), counts AS ( SELECT list_id, JSON_OBJECT_AGG(status, num) AS subscriber_statuses, SUM(num) AS subscriber_count @@ -525,9 +527,10 @@ SELECT c.id, c.uuid, c.name, c.subject, c.from_email, ) AS lists FROM campaigns c WHERE ($1 = 0 OR id = $1) - AND status=ANY(CASE WHEN CARDINALITY($2::campaign_status[]) != 0 THEN $2::campaign_status[] ELSE ARRAY[status] END) - AND ($3 = '' OR TO_TSVECTOR(CONCAT(name, ' ', subject)) @@ TO_TSQUERY($3)) -ORDER BY %order% OFFSET $4 LIMIT (CASE WHEN $5 < 1 THEN NULL ELSE $5 END); + AND (CARDINALITY($2::campaign_status[]) = 0 OR status = ANY($2)) + AND (CARDINALITY($3::VARCHAR(100)[]) = 0 OR $3 <@ tags) + AND ($4 = '' OR TO_TSVECTOR(CONCAT(name, ' ', subject)) @@ TO_TSQUERY($4)) +ORDER BY %order% OFFSET $5 LIMIT (CASE WHEN $6 < 1 THEN NULL ELSE $6 END); -- name: get-campaign SELECT campaigns.*,