mirror of
https://github.com/knadh/listmonk.git
synced 2024-09-20 07:16:33 +08:00
parent
550cd3e1f8
commit
16f4dfd3e9
|
@ -22,12 +22,13 @@ const (
|
||||||
// subQueryReq is a "catch all" struct for reading various
|
// subQueryReq is a "catch all" struct for reading various
|
||||||
// subscriber related requests.
|
// subscriber related requests.
|
||||||
type subQueryReq struct {
|
type subQueryReq struct {
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
ListIDs []int `json:"list_ids"`
|
ListIDs []int `json:"list_ids"`
|
||||||
TargetListIDs []int `json:"target_list_ids"`
|
TargetListIDs []int `json:"target_list_ids"`
|
||||||
SubscriberIDs []int `json:"ids"`
|
SubscriberIDs []int `json:"ids"`
|
||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
SubscriptionStatus string `json:"subscription_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// subProfileData represents a subscriber's collated data in JSON
|
// subProfileData represents a subscriber's collated data in JSON
|
||||||
|
@ -415,7 +416,7 @@ func handleDeleteSubscribersByQuery(c echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.core.DeleteSubscribersByQuery(req.Query, req.ListIDs); err != nil {
|
if err := app.core.DeleteSubscribersByQuery(req.Query, req.ListIDs, req.SubscriptionStatus); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,7 +435,7 @@ func handleBlocklistSubscribersByQuery(c echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.core.BlocklistSubscribersByQuery(req.Query, req.ListIDs); err != nil {
|
if err := app.core.BlocklistSubscribersByQuery(req.Query, req.ListIDs, req.SubscriptionStatus); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,11 +462,11 @@ func handleManageSubscriberListsByQuery(c echo.Context) error {
|
||||||
var err error
|
var err error
|
||||||
switch req.Action {
|
switch req.Action {
|
||||||
case "add":
|
case "add":
|
||||||
err = app.core.AddSubscriptionsByQuery(req.Query, req.ListIDs, req.TargetListIDs, req.Status)
|
err = app.core.AddSubscriptionsByQuery(req.Query, req.ListIDs, req.TargetListIDs, req.Status, req.SubscriptionStatus)
|
||||||
case "remove":
|
case "remove":
|
||||||
err = app.core.DeleteSubscriptionsByQuery(req.Query, req.ListIDs, req.TargetListIDs)
|
err = app.core.DeleteSubscriptionsByQuery(req.Query, req.ListIDs, req.TargetListIDs, req.SubscriptionStatus)
|
||||||
case "unsubscribe":
|
case "unsubscribe":
|
||||||
err = app.core.UnsubscribeListsByQuery(req.Query, req.ListIDs, req.TargetListIDs)
|
err = app.core.UnsubscribeListsByQuery(req.Query, req.ListIDs, req.TargetListIDs, req.SubscriptionStatus)
|
||||||
default:
|
default:
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidAction"))
|
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidAction"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -375,6 +375,7 @@ export default Vue.extend({
|
||||||
this.$api.blocklistSubscribersByQuery({
|
this.$api.blocklistSubscribersByQuery({
|
||||||
query: this.queryParams.queryExp,
|
query: this.queryParams.queryExp,
|
||||||
list_ids: this.queryParams.listID ? [this.queryParams.listID] : null,
|
list_ids: this.queryParams.listID ? [this.queryParams.listID] : null,
|
||||||
|
subscription_status: this.queryParams.subStatus,
|
||||||
}).then(() => this.querySubscribers());
|
}).then(() => this.querySubscribers());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -426,6 +427,7 @@ export default Vue.extend({
|
||||||
this.$api.deleteSubscribersByQuery({
|
this.$api.deleteSubscribersByQuery({
|
||||||
query: this.queryParams.queryExp,
|
query: this.queryParams.queryExp,
|
||||||
list_ids: this.queryParams.listID ? [this.queryParams.listID] : null,
|
list_ids: this.queryParams.listID ? [this.queryParams.listID] : null,
|
||||||
|
subscription_status: this.queryParams.subStatus,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
|
|
||||||
|
@ -460,6 +462,7 @@ export default Vue.extend({
|
||||||
} else {
|
} else {
|
||||||
// 'All' is selected, perform by query.
|
// 'All' is selected, perform by query.
|
||||||
data.query = this.queryParams.queryExp;
|
data.query = this.queryParams.queryExp;
|
||||||
|
data.subscription_status = this.queryParams.subStatus;
|
||||||
fn = this.$api.addSubscribersToListsByQuery;
|
fn = this.$api.addSubscribersToListsByQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,7 +506,6 @@ export default Vue.extend({
|
||||||
// Get subscribers on load.
|
// Get subscribers on load.
|
||||||
this.querySubscribers();
|
this.querySubscribers();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.$route.query.subscription_status) {
|
if (this.$route.query.subscription_status) {
|
||||||
this.queryParams.subStatus = this.$route.query.subscription_status;
|
this.queryParams.subStatus = this.$route.query.subscription_status;
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,8 +394,8 @@ func (c *Core) BlocklistSubscribers(subIDs []int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlocklistSubscribersByQuery blocklists the given list of subscribers.
|
// BlocklistSubscribersByQuery blocklists the given list of subscribers.
|
||||||
func (c *Core) BlocklistSubscribersByQuery(query string, listIDs []int) error {
|
func (c *Core) BlocklistSubscribersByQuery(query string, listIDs []int, subStatus string) error {
|
||||||
if err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.BlocklistSubscribersByQuery, listIDs, c.db); err != nil {
|
if err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.BlocklistSubscribersByQuery, listIDs, c.db, subStatus); err != nil {
|
||||||
c.log.Printf("error blocklisting subscribers: %v", err)
|
c.log.Printf("error blocklisting subscribers: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
c.i18n.Ts("subscribers.errorBlocklisting", "error", pqErrMsg(err)))
|
c.i18n.Ts("subscribers.errorBlocklisting", "error", pqErrMsg(err)))
|
||||||
|
@ -423,8 +423,8 @@ func (c *Core) DeleteSubscribers(subIDs []int, subUUIDs []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSubscribersByQuery deletes subscribers by a given arbitrary query expression.
|
// DeleteSubscribersByQuery deletes subscribers by a given arbitrary query expression.
|
||||||
func (c *Core) DeleteSubscribersByQuery(query string, listIDs []int) error {
|
func (c *Core) DeleteSubscribersByQuery(query string, listIDs []int, subStatus string) error {
|
||||||
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.DeleteSubscribersByQuery, listIDs, c.db)
|
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.DeleteSubscribersByQuery, listIDs, c.db, subStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Printf("error deleting subscribers: %v", err)
|
c.log.Printf("error deleting subscribers: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
|
|
|
@ -35,12 +35,12 @@ func (c *Core) AddSubscriptions(subIDs, listIDs []int, status string) error {
|
||||||
|
|
||||||
// AddSubscriptionsByQuery adds list subscriptions to subscribers by a given arbitrary query expression.
|
// AddSubscriptionsByQuery adds list subscriptions to subscribers by a given arbitrary query expression.
|
||||||
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
||||||
func (c *Core) AddSubscriptionsByQuery(query string, sourceListIDs, targetListIDs []int, status string) error {
|
func (c *Core) AddSubscriptionsByQuery(query string, sourceListIDs, targetListIDs []int, status string, subStatus string) error {
|
||||||
if sourceListIDs == nil {
|
if sourceListIDs == nil {
|
||||||
sourceListIDs = []int{}
|
sourceListIDs = []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.AddSubscribersToListsByQuery, sourceListIDs, c.db, pq.Array(targetListIDs), status)
|
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.AddSubscribersToListsByQuery, sourceListIDs, c.db, subStatus, pq.Array(targetListIDs), status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Printf("error adding subscriptions by query: %v", err)
|
c.log.Printf("error adding subscriptions by query: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
|
@ -64,12 +64,12 @@ func (c *Core) DeleteSubscriptions(subIDs, listIDs []int) error {
|
||||||
|
|
||||||
// DeleteSubscriptionsByQuery deletes list subscriptions from subscribers by a given arbitrary query expression.
|
// DeleteSubscriptionsByQuery deletes list subscriptions from subscribers by a given arbitrary query expression.
|
||||||
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
||||||
func (c *Core) DeleteSubscriptionsByQuery(query string, sourceListIDs, targetListIDs []int) error {
|
func (c *Core) DeleteSubscriptionsByQuery(query string, sourceListIDs, targetListIDs []int, subStatus string) error {
|
||||||
if sourceListIDs == nil {
|
if sourceListIDs == nil {
|
||||||
sourceListIDs = []int{}
|
sourceListIDs = []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.DeleteSubscriptionsByQuery, sourceListIDs, c.db, pq.Array(targetListIDs))
|
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.DeleteSubscriptionsByQuery, sourceListIDs, c.db, subStatus, pq.Array(targetListIDs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Printf("error deleting subscriptions by query: %v", err)
|
c.log.Printf("error deleting subscriptions by query: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
|
@ -92,12 +92,12 @@ func (c *Core) UnsubscribeLists(subIDs, listIDs []int, listUUIDs []string) error
|
||||||
|
|
||||||
// UnsubscribeListsByQuery sets list subscriptions to 'unsubscribed' by a given arbitrary query expression.
|
// UnsubscribeListsByQuery sets list subscriptions to 'unsubscribed' by a given arbitrary query expression.
|
||||||
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
// sourceListIDs is the list of list IDs to filter the subscriber query with.
|
||||||
func (c *Core) UnsubscribeListsByQuery(query string, sourceListIDs, targetListIDs []int) error {
|
func (c *Core) UnsubscribeListsByQuery(query string, sourceListIDs, targetListIDs []int, subStatus string) error {
|
||||||
if sourceListIDs == nil {
|
if sourceListIDs == nil {
|
||||||
sourceListIDs = []int{}
|
sourceListIDs = []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.UnsubscribeSubscribersFromListsByQuery, sourceListIDs, c.db, pq.Array(targetListIDs))
|
err := c.q.ExecSubQueryTpl(sanitizeSQLExp(query), c.q.UnsubscribeSubscribersFromListsByQuery, sourceListIDs, c.db, subStatus, pq.Array(targetListIDs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.log.Printf("error unsubscribing from lists by query: %v", err)
|
c.log.Printf("error unsubscribing from lists by query: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
|
|
|
@ -114,7 +114,7 @@ type Queries struct {
|
||||||
// out of it using the raw `query-subscribers-template` query template.
|
// out of it using the raw `query-subscribers-template` query template.
|
||||||
// While doing this, a readonly transaction is created and the query is
|
// While doing this, a readonly transaction is created and the query is
|
||||||
// dry run on it to ensure that it is indeed readonly.
|
// dry run on it to ensure that it is indeed readonly.
|
||||||
func (q *Queries) CompileSubscriberQueryTpl(exp string, db *sqlx.DB) (string, error) {
|
func (q *Queries) CompileSubscriberQueryTpl(exp string, db *sqlx.DB, subStatus string) (string, error) {
|
||||||
tx, err := db.BeginTxx(context.Background(), &sql.TxOptions{ReadOnly: true})
|
tx, err := db.BeginTxx(context.Background(), &sql.TxOptions{ReadOnly: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -126,19 +126,18 @@ func (q *Queries) CompileSubscriberQueryTpl(exp string, db *sqlx.DB) (string, er
|
||||||
exp = " AND " + exp
|
exp = " AND " + exp
|
||||||
}
|
}
|
||||||
stmt := fmt.Sprintf(q.QuerySubscribersTpl, exp)
|
stmt := fmt.Sprintf(q.QuerySubscribersTpl, exp)
|
||||||
if _, err := tx.Exec(stmt, true, pq.Int64Array{}); err != nil {
|
if _, err := tx.Exec(stmt, true, pq.Int64Array{}, subStatus); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return stmt, nil
|
return stmt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileSubscriberQueryTpl takes an arbitrary WHERE expressions and a subscriber
|
// compileSubscriberQueryTpl takes an arbitrary WHERE expressions and a subscriber
|
||||||
// query template that depends on the filter (eg: delete by query, blocklist by query etc.)
|
// query template that depends on the filter (eg: delete by query, blocklist by query etc.)
|
||||||
// combines and executes them.
|
// combines and executes them.
|
||||||
func (q *Queries) ExecSubQueryTpl(exp, tpl string, listIDs []int, db *sqlx.DB, args ...interface{}) error {
|
func (q *Queries) ExecSubQueryTpl(exp, tpl string, listIDs []int, db *sqlx.DB, subStatus string, args ...interface{}) error {
|
||||||
// Perform a dry run.
|
// Perform a dry run.
|
||||||
filterExp, err := q.CompileSubscriberQueryTpl(exp, db)
|
filterExp, err := q.CompileSubscriberQueryTpl(exp, db, subStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -148,10 +147,9 @@ func (q *Queries) ExecSubQueryTpl(exp, tpl string, listIDs []int, db *sqlx.DB, a
|
||||||
}
|
}
|
||||||
|
|
||||||
// First argument is the boolean indicating if the query is a dry run.
|
// First argument is the boolean indicating if the query is a dry run.
|
||||||
a := append([]interface{}{false, pq.Array(listIDs)}, args...)
|
a := append([]interface{}{false, pq.Array(listIDs), subStatus}, args...)
|
||||||
if _, err := db.Exec(fmt.Sprintf(tpl, filterExp), a...); err != nil {
|
if _, err := db.Exec(fmt.Sprintf(tpl, filterExp), a...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,6 +370,7 @@ ON (
|
||||||
-- Optional list filtering.
|
-- Optional list filtering.
|
||||||
(CASE WHEN CARDINALITY($2::INT[]) > 0 THEN true ELSE false END)
|
(CASE WHEN CARDINALITY($2::INT[]) > 0 THEN true ELSE false END)
|
||||||
AND subscriber_lists.subscriber_id = subscribers.id
|
AND subscriber_lists.subscriber_id = subscribers.id
|
||||||
|
AND ($3 = '' OR subscriber_lists.status = $3::subscription_status)
|
||||||
)
|
)
|
||||||
WHERE subscriber_lists.list_id = ALL($2::INT[]) %s
|
WHERE subscriber_lists.list_id = ALL($2::INT[]) %s
|
||||||
LIMIT (CASE WHEN $1 THEN 1 END)
|
LIMIT (CASE WHEN $1 THEN 1 END)
|
||||||
|
@ -393,20 +394,20 @@ UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
-- raw: true
|
-- raw: true
|
||||||
WITH subs AS (%s)
|
WITH subs AS (%s)
|
||||||
INSERT INTO subscriber_lists (subscriber_id, list_id, status)
|
INSERT INTO subscriber_lists (subscriber_id, list_id, status)
|
||||||
(SELECT a, b, (CASE WHEN $4 != '' THEN $4::subscription_status ELSE 'unconfirmed' END) FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($3::INT[]) b)
|
(SELECT a, b, (CASE WHEN $5 != '' THEN $5::subscription_status ELSE 'unconfirmed' END) FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($4::INT[]) b)
|
||||||
ON CONFLICT (subscriber_id, list_id) DO NOTHING;
|
ON CONFLICT (subscriber_id, list_id) DO NOTHING;
|
||||||
|
|
||||||
-- name: delete-subscriptions-by-query
|
-- name: delete-subscriptions-by-query
|
||||||
-- raw: true
|
-- raw: true
|
||||||
WITH subs AS (%s)
|
WITH subs AS (%s)
|
||||||
DELETE FROM subscriber_lists
|
DELETE FROM subscriber_lists
|
||||||
WHERE (subscriber_id, list_id) = ANY(SELECT a, b FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($3::INT[]) b);
|
WHERE (subscriber_id, list_id) = ANY(SELECT a, b FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($4::INT[]) b);
|
||||||
|
|
||||||
-- name: unsubscribe-subscribers-from-lists-by-query
|
-- name: unsubscribe-subscribers-from-lists-by-query
|
||||||
-- raw: true
|
-- raw: true
|
||||||
WITH subs AS (%s)
|
WITH subs AS (%s)
|
||||||
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
WHERE (subscriber_id, list_id) = ANY(SELECT a, b FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($3::INT[]) b);
|
WHERE (subscriber_id, list_id) = ANY(SELECT a, b FROM UNNEST(ARRAY(SELECT id FROM subs)) a, UNNEST($4::INT[]) b);
|
||||||
|
|
||||||
|
|
||||||
-- lists
|
-- lists
|
||||||
|
|
Loading…
Reference in a new issue