From 8c0804ba9f1000fd72e2bbf6b216fd02fda828f7 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 1 Aug 2020 16:45:29 +0530 Subject: [PATCH] Refactor `blacklist` to `blocklist` --- config-demo.toml | 203 +--------------------- frontend/src/api/index.js | 4 +- frontend/src/assets/style.scss | 2 +- frontend/src/views/Dashboard.vue | 4 +- frontend/src/views/Import.vue | 2 +- frontend/src/views/Settings.vue | 8 +- frontend/src/views/SubscriberForm.vue | 4 +- frontend/src/views/Subscribers.vue | 18 +- handlers.go | 6 +- import.go | 2 +- init.go | 4 +- install.go | 4 +- internal/messenger/emailer.go | 2 +- internal/subimporter/importer.go | 8 +- models/models.go | 2 +- public.go | 14 +- queries.go | 8 +- queries.sql | 32 ++-- schema.sql | 4 +- settings.go | 2 +- static/public/templates/subscription.html | 4 +- subscribers.go | 20 +-- 22 files changed, 84 insertions(+), 273 deletions(-) diff --git a/config-demo.toml b/config-demo.toml index d5ad4ab..03a0eb0 100644 --- a/config-demo.toml +++ b/config-demo.toml @@ -1,199 +1,12 @@ [app] -# Interface and port where the app will run its webserver. -address = "0.0.0.0:9000" - -# Public root URL of the listmonk installation that'll be used -# in the messages for linking to images, unsubscribe page etc. -root = "https://listmonk.mysite.com" - -# (Optional) full URL to the static logo to be displayed on -# user facing view such as the unsubscription page. -# eg: https://mysite.com/images/logo.svg -logo_url = "https://listmonk.mysite.com/public/static/logo.png" - -# (Optional) full URL to the static favicon to be displayed on -# user facing view such as the unsubscription page. -# eg: https://mysite.com/images/favicon.png -favicon_url = "https://listmonk.mysite.com/public/static/favicon.png" - -# The default 'from' e-mail for outgoing e-mail campaigns. -from_email = "listmonk " - -# List of e-mail addresses to which admin notifications such as -# import updates, campaign completion, failure etc. should be sent. -# To disable notifications, set an empty list, eg: notify_emails = [] -notify_emails = ["admin1@mysite.com", "admin2@mysite.com"] - -# Maximum concurrent workers that will attempt to send messages -# simultaneously. This should ideally depend on the number of CPUs -# available, and should be based on the maximum number of messages -# a target SMTP server will accept. -concurrency = 5 - -# Maximum number of messages to be sent out per second per worker. -# If concurrency = 10 and message_rate = 10, then up to 10x10=100 messages -# may be pushed out every second. This, along with concurrency, should be -# tweaked to keep the net messages going out per second under the target -# SMTP's rate limits, if any. -message_rate = 5 - -# The number of errors (eg: SMTP timeouts while e-mailing) a running -# campaign should tolerate before it is paused for manual -# investigation or intervention. Set to 0 to never pause. -max_send_errors = 1000 - -# The number of subscribers to pull from the databse in a single iteration. -# Each iteration pulls subscribers from the database, sends messages to them, -# and then moves on to the next iteration to pull the next batch. -# This should ideally be higher than the maximum achievable throughput (concurrency * message_rate) -batch_size = 1000 - -[privacy] -# Allow subscribers to unsubscribe from all mailing lists and mark themselves -# as blacklisted? -allow_blacklist = false - -# Allow subscribers to export data recorded on them? -allow_export = false - -# Items to include in the data export. -# profile Subscriber's profile including custom attributes -# subscriptions Subscriber's subscription lists (private list names are masked) -# campaign_views Campaigns the subscriber has viewed and the view counts -# link_clicks Links that the subscriber has clicked and the click counts -exportable = ["profile", "subscriptions", "campaign_views", "link_clicks"] - -# Allow subscribers to delete themselves from the database? -# This deletes the subscriber and all their subscriptions. -# Their association to campaign views and link clicks are also -# removed while views and click counts remain (with no subscriber -# associated to them) so that stats and analytics aren't affected. -allow_wipe = false - + # Interface and port where the app will run its webserver. + address = "0.0.0.0:9000" # Database. [db] -host = "demo-db" -port = 5432 -user = "listmonk" -password = "listmonk" -database = "listmonk" -ssl_mode = "disable" - -# Maximum active and idle connections to pool. -max_open = 50 -max_idle = 10 - -# SMTP servers. -[smtp] - [smtp.my0] - enabled = true - host = "my.smtp.server" - port = 25 - - # "cram", "plain", or "login". Empty string for no auth. - auth_protocol = "cram" - username = "xxxxx" - password = "" - - # Format to send e-mails in: html|plain|both. - email_format = "both" - - # Optional. Some SMTP servers require a FQDN in the hostname. - # By default, HELLOs go with "localhost". Set this if a custom - # hostname should be used. - hello_hostname = "" - - # Maximum concurrent connections to the SMTP server. - max_conns = 10 - - # Time to wait for new activity on a connection before closing - # it and removing it from the pool. - idle_timeout = "15s" - - # Message send / wait timeout. - wait_timeout = "5s" - - # The number of times a message should be retried if sending fails. - max_msg_retries = 2 - - # Enable STARTTLS. - tls_enabled = true - tls_skip_verify = false - - # One or more optional custom headers to be attached to all e-mails - # sent from this SMTP server. Uncomment the line to enable. - # email_headers = { "X-Sender" = "listmonk", "X-Custom-Header" = "listmonk" } - - [smtp.postal] - enabled = false - host = "my.smtp.server2" - port = 25 - - # cram or plain. - auth_protocol = "plain" - username = "xxxxx" - password = "" - - # Format to send e-mails in: html|plain|both. - email_format = "both" - - # Optional. Some SMTP servers require a FQDN in the hostname. - # By default, HELLOs go with "localhost". Set this if a custom - # hostname should be used. - hello_hostname = "" - - # Maximum concurrent connections to the SMTP server. - max_conns = 10 - - # Time to wait for new activity on a connection before closing - # it and removing it from the pool. - idle_timeout = "15s" - - # Message send / wait timeout. - wait_timeout = "5s" - - # The number of times a message should be retried if sending fails. - max_msg_retries = 2 - - # Enable STARTTLS. - tls_enabled = true - tls_skip_verify = false - -[upload] -# File storage backend. "filesystem" or "s3". -provider = "filesystem" - - [upload.s3] - # (Optional). AWS Access Key and Secret Key for the user to access the bucket. - # Leaving it empty would default to use instance IAM role. - aws_access_key_id = "" - aws_secret_access_key = "" - - # AWS Region where S3 bucket is hosted. - aws_default_region = "ap-south-1" - - # Bucket name. - bucket = "" - - # Path where the files will be stored inside bucket. Default is "/". - bucket_path = "/" - - # Optional full URL to the bucket. eg: https://files.mycustom.com - bucket_url = "" - - # "private" or "public". - bucket_type = "public" - - # (Optional) Specify TTL (in seconds) for the generated presigned URL. - # Expiry value is used only if the bucket is private. - expiry = 86400 - - [upload.filesystem] - # Path to the uploads directory where media will be uploaded. - upload_path="./uploads" - - # Upload URI that's visible to the outside world. - # The media uploaded to upload_path will be made available publicly - # under this URI, for instance, list.yoursite.com/uploads. - upload_uri = "/uploads" + host = "demo-db" + port = 5432 + user = "listmonk" + password = "listmonk" + database = "listmonk" + ssl_mode = "disable" diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 2285f84..f28111d 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -122,10 +122,10 @@ export const addSubscribersToLists = (data) => http.put('/api/subscribers/lists' export const addSubscribersToListsByQuery = (data) => http.put('/api/subscribers/query/lists', data, { loading: models.subscribers }); -export const blacklistSubscribers = (data) => http.put('/api/subscribers/blacklist', data, +export const blocklistSubscribers = (data) => http.put('/api/subscribers/blocklist', data, { loading: models.subscribers }); -export const blacklistSubscribersByQuery = (data) => http.put('/api/subscribers/query/blacklist', data, +export const blocklistSubscribersByQuery = (data) => http.put('/api/subscribers/query/blocklist', data, { loading: models.subscribers }); export const deleteSubscribers = (params) => http.delete('/api/subscribers', diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss index e6abf42..7a77333 100644 --- a/frontend/src/assets/style.scss +++ b/frontend/src/assets/style.scss @@ -294,7 +294,7 @@ section { border: 1px solid lighten($color, 45%); box-shadow: 1px 1px 0 lighten($color, 45%); } - &.blacklisted, &.cancelled { + &.blocklisted, &.cancelled { $color: #f5222d; color: $color; background: #fff1f0; diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index cec3163..d382f76 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -67,8 +67,8 @@
  • - - blacklisted + + blocklisted
  • diff --git a/frontend/src/views/Import.vue b/frontend/src/views/Import.vue index 4899a69..de5b9d1 100644 --- a/frontend/src/views/Import.vue +++ b/frontend/src/views/Import.vue @@ -14,7 +14,7 @@ Subscribe Blacklist + native-value="blocklist">Blocklist
diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 221a99e..bbea567 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -98,11 +98,11 @@
- - + themselves as blocklisted?"> + + message="Blocklisted subscribers will never receive any e-mails."> - + diff --git a/frontend/src/views/Subscribers.vue b/frontend/src/views/Subscribers.vue index 30c088d..9936f94 100644 --- a/frontend/src/views/Subscribers.vue +++ b/frontend/src/views/Subscribers.vue @@ -37,7 +37,7 @@ + placeholder="subscribers.name LIKE '%user%' or subscribers.status='blocklisted'"> @@ -80,8 +80,8 @@ Delete - - Blacklist + + Blocklist

@@ -324,19 +324,19 @@ export default Vue.extend({ ); }, - blacklistSubscribers() { + blocklistSubscribers() { let fn = null; if (!this.bulk.all && this.bulk.checked.length > 0) { - // If 'all' is not selected, blacklist subscribers by IDs. + // If 'all' is not selected, blocklist subscribers by IDs. fn = () => { const ids = this.bulk.checked.map((s) => s.id); - this.$api.blacklistSubscribers({ ids }) + this.$api.blocklistSubscribers({ ids }) .then(() => this.querySubscribers()); }; } else { - // 'All' is selected, blacklist by query. + // 'All' is selected, blocklist by query. fn = () => { - this.$api.blacklistSubscribersByQuery({ + this.$api.blocklistSubscribersByQuery({ query: this.queryParams.queryExp, list_ids: [], }).then(() => this.querySubscribers()); @@ -344,7 +344,7 @@ export default Vue.extend({ } this.$utils.confirm( - `Blacklist ${this.numSelectedSubscribers} subscriber(s)?`, + `Blocklist ${this.numSelectedSubscribers} subscriber(s)?`, fn, ); }, diff --git a/handlers.go b/handlers.go index 83a456d..cf6a462 100644 --- a/handlers.go +++ b/handlers.go @@ -51,8 +51,8 @@ func registerHTTPHandlers(e *echo.Echo) { e.POST("/api/subscribers", handleCreateSubscriber) e.PUT("/api/subscribers/:id", handleUpdateSubscriber) e.POST("/api/subscribers/:id/optin", handleSubscriberSendOptin) - e.PUT("/api/subscribers/blacklist", handleBlacklistSubscribers) - e.PUT("/api/subscribers/:id/blacklist", handleBlacklistSubscribers) + e.PUT("/api/subscribers/blocklist", handleBlocklistSubscribers) + e.PUT("/api/subscribers/:id/blocklist", handleBlocklistSubscribers) e.PUT("/api/subscribers/lists/:id", handleManageSubscriberLists) e.PUT("/api/subscribers/lists", handleManageSubscriberLists) e.DELETE("/api/subscribers/:id", handleDeleteSubscribers) @@ -61,7 +61,7 @@ func registerHTTPHandlers(e *echo.Echo) { // Subscriber operations based on arbitrary SQL queries. // These aren't very REST-like. e.POST("/api/subscribers/query/delete", handleDeleteSubscribersByQuery) - e.PUT("/api/subscribers/query/blacklist", handleBlacklistSubscribersByQuery) + e.PUT("/api/subscribers/query/blocklist", handleBlocklistSubscribersByQuery) e.PUT("/api/subscribers/query/lists", handleManageSubscriberListsByQuery) e.GET("/api/subscribers", handleQuerySubscribers) diff --git a/import.go b/import.go index 8efb7b0..d295970 100644 --- a/import.go +++ b/import.go @@ -38,7 +38,7 @@ func handleImportSubscribers(c echo.Context) error { fmt.Sprintf("Invalid `params` field: %v", err)) } - if r.Mode != subimporter.ModeSubscribe && r.Mode != subimporter.ModeBlacklist { + if r.Mode != subimporter.ModeSubscribe && r.Mode != subimporter.ModeBlocklist { return echo.NewHTTPError(http.StatusBadRequest, "Invalid `mode`") } diff --git a/init.go b/init.go index 411a664..68233b4 100644 --- a/init.go +++ b/init.go @@ -43,7 +43,7 @@ type constants struct { FromEmail string `koanf:"from_email"` NotifyEmails []string `koanf:"notify_emails"` Privacy struct { - AllowBlacklist bool `koanf:"allow_blacklist"` + AllowBlocklist bool `koanf:"allow_blocklist"` AllowExport bool `koanf:"allow_export"` AllowWipe bool `koanf:"allow_wipe"` Exportable map[string]bool `koanf:"-"` @@ -278,7 +278,7 @@ func initImporter(q *Queries, db *sqlx.DB, app *App) *subimporter.Importer { return subimporter.New( subimporter.Options{ UpsertStmt: q.UpsertSubscriber.Stmt, - BlacklistStmt: q.UpsertBlacklistSubscriber.Stmt, + BlocklistStmt: q.UpsertBlocklistSubscriber.Stmt, UpdateListDateStmt: q.UpdateListsDate.Stmt, NotifCB: func(subject string, data interface{}) error { app.sendNotification(app.constants.NotifyEmails, subject, notifTplImport, data) diff --git a/install.go b/install.go index 53527b1..1ac4624 100644 --- a/install.go +++ b/install.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "strings" - "time" "github.com/gofrs/uuid" "github.com/jmoiron/sqlx" @@ -113,7 +112,6 @@ func install(db *sqlx.DB, fs stuffbin.FileSystem, prompt bool) { } // Sample campaign. - sendAt := time.Now().Add(time.Hour * 24) if _, err := q.CreateCampaign.Exec(uuid.Must(uuid.NewV4()), models.CampaignTypeRegular, "Test campaign", @@ -122,7 +120,7 @@ func install(db *sqlx.DB, fs stuffbin.FileSystem, prompt bool) { `

Hi {{ .Subscriber.FirstName }}!

This is a test e-mail campaign. Your second name is {{ .Subscriber.LastName }} and you are from {{ .Subscriber.Attribs.city }}.`, "richtext", - sendAt, + nil, pq.StringArray{"test-campaign"}, "email", 1, diff --git a/internal/messenger/emailer.go b/internal/messenger/emailer.go index 352bb8f..6c73600 100644 --- a/internal/messenger/emailer.go +++ b/internal/messenger/emailer.go @@ -52,7 +52,7 @@ func NewEmailer(servers ...Server) (*Emailer, error) { auth = smtp.PlainAuth("", s.Username, s.Password, s.Host) case "login": auth = &smtppool.LoginAuth{Username: s.Username, Password: s.Password} - case "": + case "", "none": default: return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol) } diff --git a/internal/subimporter/importer.go b/internal/subimporter/importer.go index 33f4184..0099efd 100644 --- a/internal/subimporter/importer.go +++ b/internal/subimporter/importer.go @@ -44,7 +44,7 @@ const ( StatusFailed = "failed" ModeSubscribe = "subscribe" - ModeBlacklist = "blacklist" + ModeBlocklist = "blocklist" ) // Importer represents the bulk CSV subscriber import system. @@ -60,7 +60,7 @@ type Importer struct { // Options represents inport options. type Options struct { UpsertStmt *sql.Stmt - BlacklistStmt *sql.Stmt + BlocklistStmt *sql.Stmt UpdateListDateStmt *sql.Stmt NotifCB models.AdminNotifCallback } @@ -255,7 +255,7 @@ func (s *Session) Start() { if s.mode == ModeSubscribe { stmt = tx.Stmt(s.im.opt.UpsertStmt) } else { - stmt = tx.Stmt(s.im.opt.BlacklistStmt) + stmt = tx.Stmt(s.im.opt.BlocklistStmt) } } @@ -268,7 +268,7 @@ func (s *Session) Start() { if s.mode == ModeSubscribe { _, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, listIDs, s.overwrite) - } else if s.mode == ModeBlacklist { + } else if s.mode == ModeBlocklist { _, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs) } if err != nil { diff --git a/models/models.go b/models/models.go index 22477c7..6826eb5 100644 --- a/models/models.go +++ b/models/models.go @@ -21,7 +21,7 @@ const ( // Subscriber. SubscriberStatusEnabled = "enabled" SubscriberStatusDisabled = "disabled" - SubscriberStatusBlackListed = "blacklisted" + SubscriberStatusBlockListed = "blocklisted" // Subscription. SubscriptionStatusUnconfirmed = "unconfirmed" diff --git a/public.go b/public.go index 96ecbb8..0fd8c6e 100644 --- a/public.go +++ b/public.go @@ -48,7 +48,7 @@ type publicTpl struct { type unsubTpl struct { publicTpl SubUUID string - AllowBlacklist bool + AllowBlocklist bool AllowExport bool AllowWipe bool } @@ -147,23 +147,23 @@ func handleSubscriptionPage(c echo.Context) error { campUUID = c.Param("campUUID") subUUID = c.Param("subUUID") unsub, _ = strconv.ParseBool(c.FormValue("unsubscribe")) - blacklist, _ = strconv.ParseBool(c.FormValue("blacklist")) + blocklist, _ = strconv.ParseBool(c.FormValue("blocklist")) out = unsubTpl{} ) out.SubUUID = subUUID out.Title = "Unsubscribe from mailing list" - out.AllowBlacklist = app.constants.Privacy.AllowBlacklist + out.AllowBlocklist = app.constants.Privacy.AllowBlocklist out.AllowExport = app.constants.Privacy.AllowExport out.AllowWipe = app.constants.Privacy.AllowWipe // Unsubscribe. if unsub { - // Is blacklisting allowed? - if !app.constants.Privacy.AllowBlacklist { - blacklist = false + // Is blocklisting allowed? + if !app.constants.Privacy.AllowBlocklist { + blocklist = false } - if _, err := app.queries.Unsubscribe.Exec(campUUID, subUUID, blacklist); err != nil { + if _, err := app.queries.Unsubscribe.Exec(campUUID, subUUID, blocklist); err != nil { app.log.Printf("error unsubscribing: %v", err) return c.Render(http.StatusInternalServerError, tplMessage, makeMsgTpl("Error", "", diff --git a/queries.go b/queries.go index dd880a2..5f49755 100644 --- a/queries.go +++ b/queries.go @@ -16,14 +16,14 @@ type Queries struct { InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"` UpsertSubscriber *sqlx.Stmt `query:"upsert-subscriber"` - UpsertBlacklistSubscriber *sqlx.Stmt `query:"upsert-blacklist-subscriber"` + UpsertBlocklistSubscriber *sqlx.Stmt `query:"upsert-blocklist-subscriber"` GetSubscriber *sqlx.Stmt `query:"get-subscriber"` GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"` GetSubscriberLists *sqlx.Stmt `query:"get-subscriber-lists"` GetSubscriberListsLazy *sqlx.Stmt `query:"get-subscriber-lists-lazy"` SubscriberExists *sqlx.Stmt `query:"subscriber-exists"` UpdateSubscriber *sqlx.Stmt `query:"update-subscriber"` - BlacklistSubscribers *sqlx.Stmt `query:"blacklist-subscribers"` + BlocklistSubscribers *sqlx.Stmt `query:"blocklist-subscribers"` AddSubscribersToLists *sqlx.Stmt `query:"add-subscribers-to-lists"` DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"` ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"` @@ -37,7 +37,7 @@ type Queries struct { QuerySubscribersTpl string `query:"query-subscribers-template"` DeleteSubscribersByQuery string `query:"delete-subscribers-by-query"` AddSubscribersToListsByQuery string `query:"add-subscribers-to-lists-by-query"` - BlacklistSubscribersByQuery string `query:"blacklist-subscribers-by-query"` + BlocklistSubscribersByQuery string `query:"blocklist-subscribers-by-query"` DeleteSubscriptionsByQuery string `query:"delete-subscriptions-by-query"` UnsubscribeSubscribersFromListsByQuery string `query:"unsubscribe-subscribers-from-lists-by-query"` @@ -132,7 +132,7 @@ func (q *Queries) compileSubscriberQueryTpl(exp string, db *sqlx.DB) (string, er } // compileSubscriberQueryTpl takes a arbitrary WHERE expressions and a subscriber -// query template that depends on the filter (eg: delete by query, blacklist by query etc.) +// query template that depends on the filter (eg: delete by query, blocklist by query etc.) // combines and executes them. func (q *Queries) execSubscriberQueryTpl(exp, tpl string, listIDs []int64, db *sqlx.DB, args ...interface{}) error { // Perform a dry run. diff --git a/queries.sql b/queries.sql index 3e533bf..04ab602 100644 --- a/queries.sql +++ b/queries.sql @@ -64,7 +64,7 @@ subs AS ( VALUES( (SELECT id FROM sub), UNNEST(ARRAY(SELECT id FROM listIDs)), - (CASE WHEN $4='blacklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END) + (CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END) ) ON CONFLICT (subscriber_id, list_id) DO UPDATE SET updated_at=NOW() @@ -92,15 +92,15 @@ subs AS ( ) SELECT uuid, id from sub; --- name: upsert-blacklist-subscriber --- Upserts a subscriber where the update will only set the status to blacklisted +-- name: upsert-blocklist-subscriber +-- Upserts a subscriber where the update will only set the status to blocklisted -- unlike upsert-subscribers where name and attributes are updated. In addition, all -- existing subscriptions are marked as 'unsubscribed'. -- This is used in the bulk importer. WITH sub AS ( INSERT INTO subscribers (uuid, email, name, attribs, status) - VALUES($1, $2, $3, $4, 'blacklisted') - ON CONFLICT (email) DO UPDATE SET status='blacklisted', updated_at=NOW() + VALUES($1, $2, $3, $4, 'blocklisted') + ON CONFLICT (email) DO UPDATE SET status='blocklisted', updated_at=NOW() RETURNING id ) UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW() @@ -125,18 +125,18 @@ INSERT INTO subscriber_lists (subscriber_id, list_id, status) VALUES( (SELECT id FROM s), UNNEST($6), - (CASE WHEN $4='blacklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END) + (CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END) ) ON CONFLICT (subscriber_id, list_id) DO UPDATE - SET status = (CASE WHEN $4='blacklisted' THEN 'unsubscribed'::subscription_status ELSE subscriber_lists.status END); + SET status = (CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE subscriber_lists.status END); -- name: delete-subscribers -- Delete one or more subscribers by ID or UUID. DELETE FROM subscribers WHERE CASE WHEN ARRAY_LENGTH($1::INT[], 1) > 0 THEN id = ANY($1) ELSE uuid = ANY($2::UUID[]) END; --- name: blacklist-subscribers +-- name: blocklist-subscribers WITH b AS ( - UPDATE subscribers SET status='blacklisted', updated_at=NOW() + UPDATE subscribers SET status='blocklisted', updated_at=NOW() WHERE id = ANY($1::INT[]) ) UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW() @@ -167,7 +167,7 @@ UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW() -- name: unsubscribe -- Unsubscribes a subscriber given a campaign UUID (from all the lists in the campaign) and the subscriber UUID. --- If $3 is TRUE, then all subscriptions of the subscriber is blacklisted +-- If $3 is TRUE, then all subscriptions of the subscriber is blocklisted -- and all existing subscriptions, irrespective of lists, unsubscribed. WITH lists AS ( SELECT list_id FROM campaign_lists @@ -175,7 +175,7 @@ WITH lists AS ( WHERE campaigns.uuid = $1 ), sub AS ( - UPDATE subscribers SET status = (CASE WHEN $3 IS TRUE THEN 'blacklisted' ELSE status END) + UPDATE subscribers SET status = (CASE WHEN $3 IS TRUE THEN 'blocklisted' ELSE status END) WHERE uuid = $2 RETURNING id ) UPDATE subscriber_lists SET status = 'unsubscribed' WHERE @@ -239,7 +239,7 @@ SELECT COUNT(*) OVER () AS total, subscribers.* FROM subscribers -- name: query-subscribers-template -- raw: true --- This raw query is reused in multiple queries (blacklist, add to list, delete) +-- This raw query is reused in multiple queries (blocklist, add to list, delete) -- etc., so it's kept has a raw template to be injected into other raw queries, -- and for the same reason, it is not terminated with a semicolon. -- @@ -261,11 +261,11 @@ LIMIT (CASE WHEN $1 THEN 1 END) WITH subs AS (%s) DELETE FROM subscribers WHERE id=ANY(SELECT id FROM subs); --- name: blacklist-subscribers-by-query +-- name: blocklist-subscribers-by-query -- raw: true WITH subs AS (%s), b AS ( - UPDATE subscribers SET status='blacklisted', updated_at=NOW() + UPDATE subscribers SET status='blocklisted', updated_at=NOW() WHERE id = ANY(SELECT id FROM subs) ) UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW() @@ -513,7 +513,7 @@ subs AS ( campLists.list_id = subscriber_lists.list_id ) INNER JOIN subscribers ON ( - subscribers.status != 'blacklisted' AND + subscribers.status != 'blocklisted' AND subscribers.id = subscriber_lists.subscriber_id AND (CASE @@ -702,7 +702,7 @@ SELECT JSON_BUILD_OBJECT('link_clicks', COALESCE((SELECT * FROM clicks), '[]'), -- name: get-dashboard-counts SELECT JSON_BUILD_OBJECT('subscribers', JSON_BUILD_OBJECT( 'total', (SELECT COUNT(*) FROM subscribers), - 'blacklisted', (SELECT COUNT(*) FROM subscribers WHERE status='blacklisted'), + 'blocklisted', (SELECT COUNT(*) FROM subscribers WHERE status='blocklisted'), 'orphans', ( SELECT COUNT(id) FROM subscribers LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id) diff --git a/schema.sql b/schema.sql index 646772b..01934bf 100644 --- a/schema.sql +++ b/schema.sql @@ -1,6 +1,6 @@ DROP TYPE IF EXISTS list_type CASCADE; CREATE TYPE list_type AS ENUM ('public', 'private', 'temporary'); DROP TYPE IF EXISTS list_optin CASCADE; CREATE TYPE list_optin AS ENUM ('single', 'double'); -DROP TYPE IF EXISTS subscriber_status CASCADE; CREATE TYPE subscriber_status AS ENUM ('enabled', 'disabled', 'blacklisted'); +DROP TYPE IF EXISTS subscriber_status CASCADE; CREATE TYPE subscriber_status AS ENUM ('enabled', 'disabled', 'blocklisted'); DROP TYPE IF EXISTS subscription_status CASCADE; CREATE TYPE subscription_status AS ENUM ('unconfirmed', 'confirmed', 'unsubscribed'); DROP TYPE IF EXISTS campaign_status CASCADE; CREATE TYPE campaign_status AS ENUM ('draft', 'running', 'scheduled', 'paused', 'cancelled', 'finished'); DROP TYPE IF EXISTS campaign_type CASCADE; CREATE TYPE campaign_type AS ENUM ('regular', 'optin'); @@ -173,7 +173,7 @@ INSERT INTO settings (key, value) VALUES ('app.batch_size', '1000'), ('app.max_send_errors', '1000'), ('app.notify_emails', '["admin1@mysite.com", "admin2@mysite.com"]'), - ('privacy.allow_blacklist', 'true'), + ('privacy.allow_blocklist', 'true'), ('privacy.allow_export', 'true'), ('privacy.allow_wipe', 'true'), ('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'), diff --git a/settings.go b/settings.go index 7369183..5920e89 100644 --- a/settings.go +++ b/settings.go @@ -24,7 +24,7 @@ type settings struct { Messengers []interface{} `json:"messengers"` - PrivacyAllowBlacklist bool `json:"privacy.allow_blacklist"` + PrivacyAllowBlocklist bool `json:"privacy.allow_blocklist"` PrivacyAllowExport bool `json:"privacy.allow_export"` PrivacyAllowWipe bool `json:"privacy.allow_wipe"` PrivacyExportable []string `json:"privacy.exportable"` diff --git a/static/public/templates/subscription.html b/static/public/templates/subscription.html index 62ef061..87198a5 100644 --- a/static/public/templates/subscription.html +++ b/static/public/templates/subscription.html @@ -7,9 +7,9 @@
- {{ if .Data.AllowBlacklist }} + {{ if .Data.AllowBlocklist }}

- +

{{ end }} diff --git a/subscribers.go b/subscribers.go index dda279d..157e384 100644 --- a/subscribers.go +++ b/subscribers.go @@ -248,9 +248,9 @@ func handleSubscriberSendOptin(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } -// handleBlacklistSubscribers handles the blacklisting of one or more subscribers. +// handleBlocklistSubscribers handles the blocklisting of one or more subscribers. // It takes either an ID in the URI, or a list of IDs in the request body. -func handleBlacklistSubscribers(c echo.Context) error { +func handleBlocklistSubscribers(c echo.Context) error { var ( app = c.Get("app").(*App) pID = c.Param("id") @@ -278,10 +278,10 @@ func handleBlacklistSubscribers(c echo.Context) error { IDs = req.SubscriberIDs } - if _, err := app.queries.BlacklistSubscribers.Exec(IDs); err != nil { - app.log.Printf("error blacklisting subscribers: %v", err) + if _, err := app.queries.BlocklistSubscribers.Exec(IDs); err != nil { + app.log.Printf("error blocklisting subscribers: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, - fmt.Sprintf("Error blacklisting: %v", err)) + fmt.Sprintf("Error blocklisting: %v", err)) } return c.JSON(http.StatusOK, okResp{true}) @@ -407,9 +407,9 @@ func handleDeleteSubscribersByQuery(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } -// handleBlacklistSubscribersByQuery bulk blacklists subscribers +// handleBlocklistSubscribersByQuery bulk blocklists subscribers // based on an arbitrary SQL expression. -func handleBlacklistSubscribersByQuery(c echo.Context) error { +func handleBlocklistSubscribersByQuery(c echo.Context) error { var ( app = c.Get("app").(*App) req subQueryReq @@ -420,10 +420,10 @@ func handleBlacklistSubscribersByQuery(c echo.Context) error { } err := app.queries.execSubscriberQueryTpl(sanitizeSQLExp(req.Query), - app.queries.BlacklistSubscribersByQuery, + app.queries.BlocklistSubscribersByQuery, req.ListIDs, app.db) if err != nil { - app.log.Printf("error blacklisting subscribers: %v", err) + app.log.Printf("error blocklisting subscribers: %v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Error: %v", err)) } @@ -431,7 +431,7 @@ func handleBlacklistSubscribersByQuery(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } -// handleBlacklistSubscribersByQuery bulk adds/removes/unsubscribers subscribers +// handleManageSubscriberListsByQuery bulk adds/removes/unsubscribers subscribers // from one or more lists based on an arbitrary SQL expression. func handleManageSubscriberListsByQuery(c echo.Context) error { var (