Browse Source

Refactor `blacklist` to `blocklist`

Kailash Nadh 5 years ago
parent
commit
8c0804ba9f

+ 8 - 195
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 <from@mail.com>"
-
-# 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"

+ 2 - 2
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',

+ 1 - 1
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;

+ 2 - 2
frontend/src/views/Dashboard.vue

@@ -67,8 +67,8 @@
                   <div class="column is-6">
                     <ul class="no is-size-7 has-text-grey">
                       <li>
-                        <label>{{ $utils.niceNumber(counts.subscribers.blacklisted) }}</label>
-                        blacklisted
+                        <label>{{ $utils.niceNumber(counts.subscribers.blocklisted) }}</label>
+                        blocklisted
                       </li>
                       <li>
                         <label>{{ $utils.niceNumber(counts.subscribers.orphans) }}</label>

+ 1 - 1
frontend/src/views/Import.vue

@@ -14,7 +14,7 @@
                   <b-radio v-model="form.mode" name="mode"
                     native-value="subscribe">Subscribe</b-radio>
                   <b-radio v-model="form.mode" name="mode"
-                    native-value="blacklist">Blacklist</b-radio>
+                    native-value="blocklist">Blocklist</b-radio>
                 </div>
               </b-field>
             </div>

+ 4 - 4
frontend/src/views/Settings.vue

@@ -98,11 +98,11 @@
 
           <b-tab-item label="Privacy">
             <div class="items">
-              <b-field label="Allow blacklisting"
+              <b-field label="Allow blocklisting"
                 message="Allow subscribers to unsubscribe from all mailing lists and mark
-                      themselves as blacklisted?">
-                <b-switch v-model="form['privacy.allow_blacklist']"
-                    name="privacy.allow_blacklist" />
+                      themselves as blocklisted?">
+                <b-switch v-model="form['privacy.allow_blocklist']"
+                    name="privacy.allow_blocklist" />
               </b-field>
 
               <b-field label="Allow exporting"

+ 2 - 2
frontend/src/views/SubscriberForm.vue

@@ -22,10 +22,10 @@
         </b-field>
 
         <b-field label="Status" label-position="on-border"
-          message="Blacklisted subscribers will never receive any e-mails.">
+          message="Blocklisted subscribers will never receive any e-mails.">
           <b-select v-model="form.status" placeholder="Status" required>
             <option value="enabled">Enabled</option>
-            <option value="blacklisted">Blacklisted</option>
+            <option value="blocklisted">Blocklisted</option>
           </b-select>
         </b-field>
 

+ 9 - 9
frontend/src/views/Subscribers.vue

@@ -37,7 +37,7 @@
                 <b-input v-model="queryParams.queryExp"
                   @keydown.native.enter="onAdvancedQueryEnter"
                   type="textarea" ref="queryExp"
-                  placeholder="subscribers.name LIKE '%user%' or subscribers.status='blacklisted'">
+                  placeholder="subscribers.name LIKE '%user%' or subscribers.status='blocklisted'">
                 </b-input>
               </b-field>
               <b-field>
@@ -80,8 +80,8 @@
               <b-icon icon="trash-can-outline" size="is-small" /> Delete
             </a>
 
-            <a href='' @click.prevent="blacklistSubscribers">
-              <b-icon icon="account-off-outline" size="is-small" /> Blacklist
+            <a href='' @click.prevent="blocklistSubscribers">
+              <b-icon icon="account-off-outline" size="is-small" /> Blocklist
             </a>
           </p><!-- selection actions //-->
         </div>
@@ -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,
       );
     },

+ 3 - 3
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)
 

+ 1 - 1
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`")
 	}
 

+ 2 - 2
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)

+ 1 - 3
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) {
 		`<h3>Hi {{ .Subscriber.FirstName }}!</h3>
 			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,

+ 1 - 1
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)
 		}

+ 4 - 4
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 {

+ 1 - 1
models/models.go

@@ -21,7 +21,7 @@ const (
 	// Subscriber.
 	SubscriberStatusEnabled     = "enabled"
 	SubscriberStatusDisabled    = "disabled"
-	SubscriberStatusBlackListed = "blacklisted"
+	SubscriberStatusBlockListed = "blocklisted"
 
 	// Subscription.
 	SubscriptionStatusUnconfirmed  = "unconfirmed"

+ 7 - 7
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", "",

+ 4 - 4
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.

+ 16 - 16
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)

+ 2 - 2
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"]'),

+ 1 - 1
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"`

+ 2 - 2
static/public/templates/subscription.html

@@ -7,9 +7,9 @@
         <div>
             <input type="hidden" name="unsubscribe" value="true" />
 
-            {{ if .Data.AllowBlacklist }}
+            {{ if .Data.AllowBlocklist }}
                 <p>
-                    <input id="privacy-blacklist" type="checkbox" name="blacklist" value="true" /> <label for="privacy-blacklist">Also unsubscribe from all future e-mails.</label>
+                    <input id="privacy-blocklist" type="checkbox" name="blocklist" value="true" /> <label for="privacy-blocklist">Also unsubscribe from all future e-mails.</label>
                 </p>
             {{ end }}
 

+ 10 - 10
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 (