Refactor `blacklist` to `blocklist`
This commit is contained in:
parent
2143def136
commit
8c0804ba9f
187
config-demo.toml
187
config-demo.toml
|
@ -2,75 +2,6 @@
|
||||||
# Interface and port where the app will run its webserver.
|
# Interface and port where the app will run its webserver.
|
||||||
address = "0.0.0.0:9000"
|
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
|
|
||||||
|
|
||||||
|
|
||||||
# Database.
|
# Database.
|
||||||
[db]
|
[db]
|
||||||
host = "demo-db"
|
host = "demo-db"
|
||||||
|
@ -79,121 +10,3 @@ user = "listmonk"
|
||||||
password = "listmonk"
|
password = "listmonk"
|
||||||
database = "listmonk"
|
database = "listmonk"
|
||||||
ssl_mode = "disable"
|
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"
|
|
||||||
|
|
|
@ -122,10 +122,10 @@ export const addSubscribersToLists = (data) => http.put('/api/subscribers/lists'
|
||||||
export const addSubscribersToListsByQuery = (data) => http.put('/api/subscribers/query/lists',
|
export const addSubscribersToListsByQuery = (data) => http.put('/api/subscribers/query/lists',
|
||||||
data, { loading: models.subscribers });
|
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 });
|
{ 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 });
|
{ loading: models.subscribers });
|
||||||
|
|
||||||
export const deleteSubscribers = (params) => http.delete('/api/subscribers',
|
export const deleteSubscribers = (params) => http.delete('/api/subscribers',
|
||||||
|
|
|
@ -294,7 +294,7 @@ section {
|
||||||
border: 1px solid lighten($color, 45%);
|
border: 1px solid lighten($color, 45%);
|
||||||
box-shadow: 1px 1px 0 lighten($color, 45%);
|
box-shadow: 1px 1px 0 lighten($color, 45%);
|
||||||
}
|
}
|
||||||
&.blacklisted, &.cancelled {
|
&.blocklisted, &.cancelled {
|
||||||
$color: #f5222d;
|
$color: #f5222d;
|
||||||
color: $color;
|
color: $color;
|
||||||
background: #fff1f0;
|
background: #fff1f0;
|
||||||
|
|
|
@ -67,8 +67,8 @@
|
||||||
<div class="column is-6">
|
<div class="column is-6">
|
||||||
<ul class="no is-size-7 has-text-grey">
|
<ul class="no is-size-7 has-text-grey">
|
||||||
<li>
|
<li>
|
||||||
<label>{{ $utils.niceNumber(counts.subscribers.blacklisted) }}</label>
|
<label>{{ $utils.niceNumber(counts.subscribers.blocklisted) }}</label>
|
||||||
blacklisted
|
blocklisted
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label>{{ $utils.niceNumber(counts.subscribers.orphans) }}</label>
|
<label>{{ $utils.niceNumber(counts.subscribers.orphans) }}</label>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<b-radio v-model="form.mode" name="mode"
|
<b-radio v-model="form.mode" name="mode"
|
||||||
native-value="subscribe">Subscribe</b-radio>
|
native-value="subscribe">Subscribe</b-radio>
|
||||||
<b-radio v-model="form.mode" name="mode"
|
<b-radio v-model="form.mode" name="mode"
|
||||||
native-value="blacklist">Blacklist</b-radio>
|
native-value="blocklist">Blocklist</b-radio>
|
||||||
</div>
|
</div>
|
||||||
</b-field>
|
</b-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -98,11 +98,11 @@
|
||||||
|
|
||||||
<b-tab-item label="Privacy">
|
<b-tab-item label="Privacy">
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<b-field label="Allow blacklisting"
|
<b-field label="Allow blocklisting"
|
||||||
message="Allow subscribers to unsubscribe from all mailing lists and mark
|
message="Allow subscribers to unsubscribe from all mailing lists and mark
|
||||||
themselves as blacklisted?">
|
themselves as blocklisted?">
|
||||||
<b-switch v-model="form['privacy.allow_blacklist']"
|
<b-switch v-model="form['privacy.allow_blocklist']"
|
||||||
name="privacy.allow_blacklist" />
|
name="privacy.allow_blocklist" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Allow exporting"
|
<b-field label="Allow exporting"
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-field label="Status" label-position="on-border"
|
<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>
|
<b-select v-model="form.status" placeholder="Status" required>
|
||||||
<option value="enabled">Enabled</option>
|
<option value="enabled">Enabled</option>
|
||||||
<option value="blacklisted">Blacklisted</option>
|
<option value="blocklisted">Blocklisted</option>
|
||||||
</b-select>
|
</b-select>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<b-input v-model="queryParams.queryExp"
|
<b-input v-model="queryParams.queryExp"
|
||||||
@keydown.native.enter="onAdvancedQueryEnter"
|
@keydown.native.enter="onAdvancedQueryEnter"
|
||||||
type="textarea" ref="queryExp"
|
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-input>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field>
|
<b-field>
|
||||||
|
@ -80,8 +80,8 @@
|
||||||
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href='' @click.prevent="blacklistSubscribers">
|
<a href='' @click.prevent="blocklistSubscribers">
|
||||||
<b-icon icon="account-off-outline" size="is-small" /> Blacklist
|
<b-icon icon="account-off-outline" size="is-small" /> Blocklist
|
||||||
</a>
|
</a>
|
||||||
</p><!-- selection actions //-->
|
</p><!-- selection actions //-->
|
||||||
</div>
|
</div>
|
||||||
|
@ -324,19 +324,19 @@ export default Vue.extend({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
blacklistSubscribers() {
|
blocklistSubscribers() {
|
||||||
let fn = null;
|
let fn = null;
|
||||||
if (!this.bulk.all && this.bulk.checked.length > 0) {
|
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 = () => {
|
fn = () => {
|
||||||
const ids = this.bulk.checked.map((s) => s.id);
|
const ids = this.bulk.checked.map((s) => s.id);
|
||||||
this.$api.blacklistSubscribers({ ids })
|
this.$api.blocklistSubscribers({ ids })
|
||||||
.then(() => this.querySubscribers());
|
.then(() => this.querySubscribers());
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 'All' is selected, blacklist by query.
|
// 'All' is selected, blocklist by query.
|
||||||
fn = () => {
|
fn = () => {
|
||||||
this.$api.blacklistSubscribersByQuery({
|
this.$api.blocklistSubscribersByQuery({
|
||||||
query: this.queryParams.queryExp,
|
query: this.queryParams.queryExp,
|
||||||
list_ids: [],
|
list_ids: [],
|
||||||
}).then(() => this.querySubscribers());
|
}).then(() => this.querySubscribers());
|
||||||
|
@ -344,7 +344,7 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$utils.confirm(
|
this.$utils.confirm(
|
||||||
`Blacklist ${this.numSelectedSubscribers} subscriber(s)?`,
|
`Blocklist ${this.numSelectedSubscribers} subscriber(s)?`,
|
||||||
fn,
|
fn,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -51,8 +51,8 @@ func registerHTTPHandlers(e *echo.Echo) {
|
||||||
e.POST("/api/subscribers", handleCreateSubscriber)
|
e.POST("/api/subscribers", handleCreateSubscriber)
|
||||||
e.PUT("/api/subscribers/:id", handleUpdateSubscriber)
|
e.PUT("/api/subscribers/:id", handleUpdateSubscriber)
|
||||||
e.POST("/api/subscribers/:id/optin", handleSubscriberSendOptin)
|
e.POST("/api/subscribers/:id/optin", handleSubscriberSendOptin)
|
||||||
e.PUT("/api/subscribers/blacklist", handleBlacklistSubscribers)
|
e.PUT("/api/subscribers/blocklist", handleBlocklistSubscribers)
|
||||||
e.PUT("/api/subscribers/:id/blacklist", handleBlacklistSubscribers)
|
e.PUT("/api/subscribers/:id/blocklist", handleBlocklistSubscribers)
|
||||||
e.PUT("/api/subscribers/lists/:id", handleManageSubscriberLists)
|
e.PUT("/api/subscribers/lists/:id", handleManageSubscriberLists)
|
||||||
e.PUT("/api/subscribers/lists", handleManageSubscriberLists)
|
e.PUT("/api/subscribers/lists", handleManageSubscriberLists)
|
||||||
e.DELETE("/api/subscribers/:id", handleDeleteSubscribers)
|
e.DELETE("/api/subscribers/:id", handleDeleteSubscribers)
|
||||||
|
@ -61,7 +61,7 @@ func registerHTTPHandlers(e *echo.Echo) {
|
||||||
// Subscriber operations based on arbitrary SQL queries.
|
// Subscriber operations based on arbitrary SQL queries.
|
||||||
// These aren't very REST-like.
|
// These aren't very REST-like.
|
||||||
e.POST("/api/subscribers/query/delete", handleDeleteSubscribersByQuery)
|
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.PUT("/api/subscribers/query/lists", handleManageSubscriberListsByQuery)
|
||||||
e.GET("/api/subscribers", handleQuerySubscribers)
|
e.GET("/api/subscribers", handleQuerySubscribers)
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ func handleImportSubscribers(c echo.Context) error {
|
||||||
fmt.Sprintf("Invalid `params` field: %v", err))
|
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`")
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid `mode`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
4
init.go
4
init.go
|
@ -43,7 +43,7 @@ type constants struct {
|
||||||
FromEmail string `koanf:"from_email"`
|
FromEmail string `koanf:"from_email"`
|
||||||
NotifyEmails []string `koanf:"notify_emails"`
|
NotifyEmails []string `koanf:"notify_emails"`
|
||||||
Privacy struct {
|
Privacy struct {
|
||||||
AllowBlacklist bool `koanf:"allow_blacklist"`
|
AllowBlocklist bool `koanf:"allow_blocklist"`
|
||||||
AllowExport bool `koanf:"allow_export"`
|
AllowExport bool `koanf:"allow_export"`
|
||||||
AllowWipe bool `koanf:"allow_wipe"`
|
AllowWipe bool `koanf:"allow_wipe"`
|
||||||
Exportable map[string]bool `koanf:"-"`
|
Exportable map[string]bool `koanf:"-"`
|
||||||
|
@ -278,7 +278,7 @@ func initImporter(q *Queries, db *sqlx.DB, app *App) *subimporter.Importer {
|
||||||
return subimporter.New(
|
return subimporter.New(
|
||||||
subimporter.Options{
|
subimporter.Options{
|
||||||
UpsertStmt: q.UpsertSubscriber.Stmt,
|
UpsertStmt: q.UpsertSubscriber.Stmt,
|
||||||
BlacklistStmt: q.UpsertBlacklistSubscriber.Stmt,
|
BlocklistStmt: q.UpsertBlocklistSubscriber.Stmt,
|
||||||
UpdateListDateStmt: q.UpdateListsDate.Stmt,
|
UpdateListDateStmt: q.UpdateListsDate.Stmt,
|
||||||
NotifCB: func(subject string, data interface{}) error {
|
NotifCB: func(subject string, data interface{}) error {
|
||||||
app.sendNotification(app.constants.NotifyEmails, subject, notifTplImport, data)
|
app.sendNotification(app.constants.NotifyEmails, subject, notifTplImport, data)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
@ -113,7 +112,6 @@ func install(db *sqlx.DB, fs stuffbin.FileSystem, prompt bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sample campaign.
|
// Sample campaign.
|
||||||
sendAt := time.Now().Add(time.Hour * 24)
|
|
||||||
if _, err := q.CreateCampaign.Exec(uuid.Must(uuid.NewV4()),
|
if _, err := q.CreateCampaign.Exec(uuid.Must(uuid.NewV4()),
|
||||||
models.CampaignTypeRegular,
|
models.CampaignTypeRegular,
|
||||||
"Test campaign",
|
"Test campaign",
|
||||||
|
@ -122,7 +120,7 @@ func install(db *sqlx.DB, fs stuffbin.FileSystem, prompt bool) {
|
||||||
`<h3>Hi {{ .Subscriber.FirstName }}!</h3>
|
`<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 }}.`,
|
This is a test e-mail campaign. Your second name is {{ .Subscriber.LastName }} and you are from {{ .Subscriber.Attribs.city }}.`,
|
||||||
"richtext",
|
"richtext",
|
||||||
sendAt,
|
nil,
|
||||||
pq.StringArray{"test-campaign"},
|
pq.StringArray{"test-campaign"},
|
||||||
"email",
|
"email",
|
||||||
1,
|
1,
|
||||||
|
|
|
@ -52,7 +52,7 @@ func NewEmailer(servers ...Server) (*Emailer, error) {
|
||||||
auth = smtp.PlainAuth("", s.Username, s.Password, s.Host)
|
auth = smtp.PlainAuth("", s.Username, s.Password, s.Host)
|
||||||
case "login":
|
case "login":
|
||||||
auth = &smtppool.LoginAuth{Username: s.Username, Password: s.Password}
|
auth = &smtppool.LoginAuth{Username: s.Username, Password: s.Password}
|
||||||
case "":
|
case "", "none":
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
|
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ const (
|
||||||
StatusFailed = "failed"
|
StatusFailed = "failed"
|
||||||
|
|
||||||
ModeSubscribe = "subscribe"
|
ModeSubscribe = "subscribe"
|
||||||
ModeBlacklist = "blacklist"
|
ModeBlocklist = "blocklist"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Importer represents the bulk CSV subscriber import system.
|
// Importer represents the bulk CSV subscriber import system.
|
||||||
|
@ -60,7 +60,7 @@ type Importer struct {
|
||||||
// Options represents inport options.
|
// Options represents inport options.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
UpsertStmt *sql.Stmt
|
UpsertStmt *sql.Stmt
|
||||||
BlacklistStmt *sql.Stmt
|
BlocklistStmt *sql.Stmt
|
||||||
UpdateListDateStmt *sql.Stmt
|
UpdateListDateStmt *sql.Stmt
|
||||||
NotifCB models.AdminNotifCallback
|
NotifCB models.AdminNotifCallback
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ func (s *Session) Start() {
|
||||||
if s.mode == ModeSubscribe {
|
if s.mode == ModeSubscribe {
|
||||||
stmt = tx.Stmt(s.im.opt.UpsertStmt)
|
stmt = tx.Stmt(s.im.opt.UpsertStmt)
|
||||||
} else {
|
} 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 {
|
if s.mode == ModeSubscribe {
|
||||||
_, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, listIDs, s.overwrite)
|
_, 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)
|
_, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,7 +21,7 @@ const (
|
||||||
// Subscriber.
|
// Subscriber.
|
||||||
SubscriberStatusEnabled = "enabled"
|
SubscriberStatusEnabled = "enabled"
|
||||||
SubscriberStatusDisabled = "disabled"
|
SubscriberStatusDisabled = "disabled"
|
||||||
SubscriberStatusBlackListed = "blacklisted"
|
SubscriberStatusBlockListed = "blocklisted"
|
||||||
|
|
||||||
// Subscription.
|
// Subscription.
|
||||||
SubscriptionStatusUnconfirmed = "unconfirmed"
|
SubscriptionStatusUnconfirmed = "unconfirmed"
|
||||||
|
|
14
public.go
14
public.go
|
@ -48,7 +48,7 @@ type publicTpl struct {
|
||||||
type unsubTpl struct {
|
type unsubTpl struct {
|
||||||
publicTpl
|
publicTpl
|
||||||
SubUUID string
|
SubUUID string
|
||||||
AllowBlacklist bool
|
AllowBlocklist bool
|
||||||
AllowExport bool
|
AllowExport bool
|
||||||
AllowWipe bool
|
AllowWipe bool
|
||||||
}
|
}
|
||||||
|
@ -147,23 +147,23 @@ func handleSubscriptionPage(c echo.Context) error {
|
||||||
campUUID = c.Param("campUUID")
|
campUUID = c.Param("campUUID")
|
||||||
subUUID = c.Param("subUUID")
|
subUUID = c.Param("subUUID")
|
||||||
unsub, _ = strconv.ParseBool(c.FormValue("unsubscribe"))
|
unsub, _ = strconv.ParseBool(c.FormValue("unsubscribe"))
|
||||||
blacklist, _ = strconv.ParseBool(c.FormValue("blacklist"))
|
blocklist, _ = strconv.ParseBool(c.FormValue("blocklist"))
|
||||||
out = unsubTpl{}
|
out = unsubTpl{}
|
||||||
)
|
)
|
||||||
out.SubUUID = subUUID
|
out.SubUUID = subUUID
|
||||||
out.Title = "Unsubscribe from mailing list"
|
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.AllowExport = app.constants.Privacy.AllowExport
|
||||||
out.AllowWipe = app.constants.Privacy.AllowWipe
|
out.AllowWipe = app.constants.Privacy.AllowWipe
|
||||||
|
|
||||||
// Unsubscribe.
|
// Unsubscribe.
|
||||||
if unsub {
|
if unsub {
|
||||||
// Is blacklisting allowed?
|
// Is blocklisting allowed?
|
||||||
if !app.constants.Privacy.AllowBlacklist {
|
if !app.constants.Privacy.AllowBlocklist {
|
||||||
blacklist = false
|
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)
|
app.log.Printf("error unsubscribing: %v", err)
|
||||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||||
makeMsgTpl("Error", "",
|
makeMsgTpl("Error", "",
|
||||||
|
|
|
@ -16,14 +16,14 @@ type Queries struct {
|
||||||
|
|
||||||
InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"`
|
InsertSubscriber *sqlx.Stmt `query:"insert-subscriber"`
|
||||||
UpsertSubscriber *sqlx.Stmt `query:"upsert-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"`
|
GetSubscriber *sqlx.Stmt `query:"get-subscriber"`
|
||||||
GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"`
|
GetSubscribersByEmails *sqlx.Stmt `query:"get-subscribers-by-emails"`
|
||||||
GetSubscriberLists *sqlx.Stmt `query:"get-subscriber-lists"`
|
GetSubscriberLists *sqlx.Stmt `query:"get-subscriber-lists"`
|
||||||
GetSubscriberListsLazy *sqlx.Stmt `query:"get-subscriber-lists-lazy"`
|
GetSubscriberListsLazy *sqlx.Stmt `query:"get-subscriber-lists-lazy"`
|
||||||
SubscriberExists *sqlx.Stmt `query:"subscriber-exists"`
|
SubscriberExists *sqlx.Stmt `query:"subscriber-exists"`
|
||||||
UpdateSubscriber *sqlx.Stmt `query:"update-subscriber"`
|
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"`
|
AddSubscribersToLists *sqlx.Stmt `query:"add-subscribers-to-lists"`
|
||||||
DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"`
|
DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"`
|
||||||
ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"`
|
ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"`
|
||||||
|
@ -37,7 +37,7 @@ type Queries struct {
|
||||||
QuerySubscribersTpl string `query:"query-subscribers-template"`
|
QuerySubscribersTpl string `query:"query-subscribers-template"`
|
||||||
DeleteSubscribersByQuery string `query:"delete-subscribers-by-query"`
|
DeleteSubscribersByQuery string `query:"delete-subscribers-by-query"`
|
||||||
AddSubscribersToListsByQuery string `query:"add-subscribers-to-lists-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"`
|
DeleteSubscriptionsByQuery string `query:"delete-subscriptions-by-query"`
|
||||||
UnsubscribeSubscribersFromListsByQuery string `query:"unsubscribe-subscribers-from-lists-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
|
// 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.
|
// combines and executes them.
|
||||||
func (q *Queries) execSubscriberQueryTpl(exp, tpl string, listIDs []int64, db *sqlx.DB, args ...interface{}) error {
|
func (q *Queries) execSubscriberQueryTpl(exp, tpl string, listIDs []int64, db *sqlx.DB, args ...interface{}) error {
|
||||||
// Perform a dry run.
|
// Perform a dry run.
|
||||||
|
|
32
queries.sql
32
queries.sql
|
@ -64,7 +64,7 @@ subs AS (
|
||||||
VALUES(
|
VALUES(
|
||||||
(SELECT id FROM sub),
|
(SELECT id FROM sub),
|
||||||
UNNEST(ARRAY(SELECT id FROM listIDs)),
|
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
|
ON CONFLICT (subscriber_id, list_id) DO UPDATE
|
||||||
SET updated_at=NOW()
|
SET updated_at=NOW()
|
||||||
|
@ -92,15 +92,15 @@ subs AS (
|
||||||
)
|
)
|
||||||
SELECT uuid, id from sub;
|
SELECT uuid, id from sub;
|
||||||
|
|
||||||
-- name: upsert-blacklist-subscriber
|
-- name: upsert-blocklist-subscriber
|
||||||
-- Upserts a subscriber where the update will only set the status to blacklisted
|
-- 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
|
-- unlike upsert-subscribers where name and attributes are updated. In addition, all
|
||||||
-- existing subscriptions are marked as 'unsubscribed'.
|
-- existing subscriptions are marked as 'unsubscribed'.
|
||||||
-- This is used in the bulk importer.
|
-- This is used in the bulk importer.
|
||||||
WITH sub AS (
|
WITH sub AS (
|
||||||
INSERT INTO subscribers (uuid, email, name, attribs, status)
|
INSERT INTO subscribers (uuid, email, name, attribs, status)
|
||||||
VALUES($1, $2, $3, $4, 'blacklisted')
|
VALUES($1, $2, $3, $4, 'blocklisted')
|
||||||
ON CONFLICT (email) DO UPDATE SET status='blacklisted', updated_at=NOW()
|
ON CONFLICT (email) DO UPDATE SET status='blocklisted', updated_at=NOW()
|
||||||
RETURNING id
|
RETURNING id
|
||||||
)
|
)
|
||||||
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
|
@ -125,18 +125,18 @@ INSERT INTO subscriber_lists (subscriber_id, list_id, status)
|
||||||
VALUES(
|
VALUES(
|
||||||
(SELECT id FROM s),
|
(SELECT id FROM s),
|
||||||
UNNEST($6),
|
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
|
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
|
-- name: delete-subscribers
|
||||||
-- Delete one or more subscribers by ID or UUID.
|
-- 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;
|
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 (
|
WITH b AS (
|
||||||
UPDATE subscribers SET status='blacklisted', updated_at=NOW()
|
UPDATE subscribers SET status='blocklisted', updated_at=NOW()
|
||||||
WHERE id = ANY($1::INT[])
|
WHERE id = ANY($1::INT[])
|
||||||
)
|
)
|
||||||
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
|
@ -167,7 +167,7 @@ UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
|
|
||||||
-- name: unsubscribe
|
-- name: unsubscribe
|
||||||
-- Unsubscribes a subscriber given a campaign UUID (from all the lists in the campaign) and the subscriber UUID.
|
-- 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.
|
-- and all existing subscriptions, irrespective of lists, unsubscribed.
|
||||||
WITH lists AS (
|
WITH lists AS (
|
||||||
SELECT list_id FROM campaign_lists
|
SELECT list_id FROM campaign_lists
|
||||||
|
@ -175,7 +175,7 @@ WITH lists AS (
|
||||||
WHERE campaigns.uuid = $1
|
WHERE campaigns.uuid = $1
|
||||||
),
|
),
|
||||||
sub AS (
|
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
|
WHERE uuid = $2 RETURNING id
|
||||||
)
|
)
|
||||||
UPDATE subscriber_lists SET status = 'unsubscribed' WHERE
|
UPDATE subscriber_lists SET status = 'unsubscribed' WHERE
|
||||||
|
@ -239,7 +239,7 @@ SELECT COUNT(*) OVER () AS total, subscribers.* FROM subscribers
|
||||||
|
|
||||||
-- name: query-subscribers-template
|
-- name: query-subscribers-template
|
||||||
-- raw: true
|
-- 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,
|
-- 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.
|
-- 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)
|
WITH subs AS (%s)
|
||||||
DELETE FROM subscribers WHERE id=ANY(SELECT id FROM subs);
|
DELETE FROM subscribers WHERE id=ANY(SELECT id FROM subs);
|
||||||
|
|
||||||
-- name: blacklist-subscribers-by-query
|
-- name: blocklist-subscribers-by-query
|
||||||
-- raw: true
|
-- raw: true
|
||||||
WITH subs AS (%s),
|
WITH subs AS (%s),
|
||||||
b AS (
|
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)
|
WHERE id = ANY(SELECT id FROM subs)
|
||||||
)
|
)
|
||||||
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
UPDATE subscriber_lists SET status='unsubscribed', updated_at=NOW()
|
||||||
|
@ -513,7 +513,7 @@ subs AS (
|
||||||
campLists.list_id = subscriber_lists.list_id
|
campLists.list_id = subscriber_lists.list_id
|
||||||
)
|
)
|
||||||
INNER JOIN subscribers ON (
|
INNER JOIN subscribers ON (
|
||||||
subscribers.status != 'blacklisted' AND
|
subscribers.status != 'blocklisted' AND
|
||||||
subscribers.id = subscriber_lists.subscriber_id AND
|
subscribers.id = subscriber_lists.subscriber_id AND
|
||||||
|
|
||||||
(CASE
|
(CASE
|
||||||
|
@ -702,7 +702,7 @@ SELECT JSON_BUILD_OBJECT('link_clicks', COALESCE((SELECT * FROM clicks), '[]'),
|
||||||
-- name: get-dashboard-counts
|
-- name: get-dashboard-counts
|
||||||
SELECT JSON_BUILD_OBJECT('subscribers', JSON_BUILD_OBJECT(
|
SELECT JSON_BUILD_OBJECT('subscribers', JSON_BUILD_OBJECT(
|
||||||
'total', (SELECT COUNT(*) FROM subscribers),
|
'total', (SELECT COUNT(*) FROM subscribers),
|
||||||
'blacklisted', (SELECT COUNT(*) FROM subscribers WHERE status='blacklisted'),
|
'blocklisted', (SELECT COUNT(*) FROM subscribers WHERE status='blocklisted'),
|
||||||
'orphans', (
|
'orphans', (
|
||||||
SELECT COUNT(id) FROM subscribers
|
SELECT COUNT(id) FROM subscribers
|
||||||
LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id)
|
LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id)
|
||||||
|
|
|
@ -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_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 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 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_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');
|
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.batch_size', '1000'),
|
||||||
('app.max_send_errors', '1000'),
|
('app.max_send_errors', '1000'),
|
||||||
('app.notify_emails', '["admin1@mysite.com", "admin2@mysite.com"]'),
|
('app.notify_emails', '["admin1@mysite.com", "admin2@mysite.com"]'),
|
||||||
('privacy.allow_blacklist', 'true'),
|
('privacy.allow_blocklist', 'true'),
|
||||||
('privacy.allow_export', 'true'),
|
('privacy.allow_export', 'true'),
|
||||||
('privacy.allow_wipe', 'true'),
|
('privacy.allow_wipe', 'true'),
|
||||||
('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'),
|
('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'),
|
||||||
|
|
|
@ -24,7 +24,7 @@ type settings struct {
|
||||||
|
|
||||||
Messengers []interface{} `json:"messengers"`
|
Messengers []interface{} `json:"messengers"`
|
||||||
|
|
||||||
PrivacyAllowBlacklist bool `json:"privacy.allow_blacklist"`
|
PrivacyAllowBlocklist bool `json:"privacy.allow_blocklist"`
|
||||||
PrivacyAllowExport bool `json:"privacy.allow_export"`
|
PrivacyAllowExport bool `json:"privacy.allow_export"`
|
||||||
PrivacyAllowWipe bool `json:"privacy.allow_wipe"`
|
PrivacyAllowWipe bool `json:"privacy.allow_wipe"`
|
||||||
PrivacyExportable []string `json:"privacy.exportable"`
|
PrivacyExportable []string `json:"privacy.exportable"`
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
<div>
|
<div>
|
||||||
<input type="hidden" name="unsubscribe" value="true" />
|
<input type="hidden" name="unsubscribe" value="true" />
|
||||||
|
|
||||||
{{ if .Data.AllowBlacklist }}
|
{{ if .Data.AllowBlocklist }}
|
||||||
<p>
|
<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>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
|
@ -248,9 +248,9 @@ func handleSubscriberSendOptin(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, okResp{true})
|
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.
|
// 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 (
|
var (
|
||||||
app = c.Get("app").(*App)
|
app = c.Get("app").(*App)
|
||||||
pID = c.Param("id")
|
pID = c.Param("id")
|
||||||
|
@ -278,10 +278,10 @@ func handleBlacklistSubscribers(c echo.Context) error {
|
||||||
IDs = req.SubscriberIDs
|
IDs = req.SubscriberIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := app.queries.BlacklistSubscribers.Exec(IDs); err != nil {
|
if _, err := app.queries.BlocklistSubscribers.Exec(IDs); err != nil {
|
||||||
app.log.Printf("error blacklisting subscribers: %v", err)
|
app.log.Printf("error blocklisting subscribers: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||||
fmt.Sprintf("Error blacklisting: %v", err))
|
fmt.Sprintf("Error blocklisting: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, okResp{true})
|
return c.JSON(http.StatusOK, okResp{true})
|
||||||
|
@ -407,9 +407,9 @@ func handleDeleteSubscribersByQuery(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, okResp{true})
|
return c.JSON(http.StatusOK, okResp{true})
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleBlacklistSubscribersByQuery bulk blacklists subscribers
|
// handleBlocklistSubscribersByQuery bulk blocklists subscribers
|
||||||
// based on an arbitrary SQL expression.
|
// based on an arbitrary SQL expression.
|
||||||
func handleBlacklistSubscribersByQuery(c echo.Context) error {
|
func handleBlocklistSubscribersByQuery(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
app = c.Get("app").(*App)
|
app = c.Get("app").(*App)
|
||||||
req subQueryReq
|
req subQueryReq
|
||||||
|
@ -420,10 +420,10 @@ func handleBlacklistSubscribersByQuery(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.queries.execSubscriberQueryTpl(sanitizeSQLExp(req.Query),
|
err := app.queries.execSubscriberQueryTpl(sanitizeSQLExp(req.Query),
|
||||||
app.queries.BlacklistSubscribersByQuery,
|
app.queries.BlocklistSubscribersByQuery,
|
||||||
req.ListIDs, app.db)
|
req.ListIDs, app.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.log.Printf("error blacklisting subscribers: %v", err)
|
app.log.Printf("error blocklisting subscribers: %v", err)
|
||||||
return echo.NewHTTPError(http.StatusBadRequest,
|
return echo.NewHTTPError(http.StatusBadRequest,
|
||||||
fmt.Sprintf("Error: %v", err))
|
fmt.Sprintf("Error: %v", err))
|
||||||
}
|
}
|
||||||
|
@ -431,7 +431,7 @@ func handleBlacklistSubscribersByQuery(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, okResp{true})
|
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.
|
// from one or more lists based on an arbitrary SQL expression.
|
||||||
func handleManageSubscriberListsByQuery(c echo.Context) error {
|
func handleManageSubscriberListsByQuery(c echo.Context) error {
|
||||||
var (
|
var (
|
||||||
|
|
Loading…
Reference in New Issue