Add new privacy option 'Record opt-in IP' to record IP address of optin confirmation.

- Add new 'Subscriptions' table on the subscriber list form that shows subs,
  IP, and other data.
- Add new `meta` JSONB field to `subscriber_lsts` table.

Closes #1329.
This commit is contained in:
Kailash Nadh 2023-07-22 13:28:45 +05:30
parent b26950c427
commit ad80c716f9
34 changed files with 122 additions and 41 deletions

View file

@ -69,6 +69,7 @@ type constants struct {
AllowBlocklist bool `koanf:"allow_blocklist"` 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"`
RecordOptinIP bool `koanf:"record_optin_ip"`
Exportable map[string]bool `koanf:"-"` Exportable map[string]bool `koanf:"-"`
DomainBlocklist []string `koanf:"-"` DomainBlocklist []string `koanf:"-"`
} `koanf:"privacy"` } `koanf:"privacy"`

View file

@ -374,7 +374,16 @@ func handleOptinPage(c echo.Context) error {
// Confirm. // Confirm.
if confirm { if confirm {
if err := app.core.ConfirmOptionSubscription(subUUID, out.ListUUIDs); err != nil { meta := models.JSON{}
if app.constants.Privacy.RecordOptinIP {
if h := c.Request().Header.Get("X-Forwarded-For"); h != "" {
meta["optin_ip"] = h
} else if h := c.Request().RemoteAddr; h != "" {
meta["optin_ip"] = strings.Split(h, ":")[0]
}
}
if err := app.core.ConfirmOptionSubscription(subUUID, out.ListUUIDs, meta); 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(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorProcessingRequest"))) makeMsgTpl(app.i18n.T("public.errorTitle"), "", app.i18n.Ts("public.errorProcessingRequest")))

View file

@ -75,39 +75,41 @@
</div> </div>
</b-field> </b-field>
<div> <div class="mb-5" v-if="data.lists">
<h5>{{ $tc('globals.terms.subscriptions', 2) }} ({{ data.lists.length }})</h5> <h5>{{ $tc('globals.terms.subscriptions', 2) }} ({{ data.lists.length }})</h5>
<div class="mb-5"> <b-table :data="data.lists" hoverable default-sort="createdAt" class="subscriptions"
<b-table :data="data.lists" hoverable default-sort="createdAt" class="subscriptions" >
> <b-table-column v-slot="props" field="name"
<b-table-column v-slot="props" field="name" :label="$tc('globals.terms.list', 1)">
:label="$tc('globals.terms.list', 1)"> <div>
<div> <router-link :to="`/lists/${props.row.id}`">
<router-link :to="`/lists/${props.row.id}`"> {{ props.row.name }}
{{ props.row.name }} </router-link>
</router-link> <br />
<br /> <b-tag :class="props.row.optin" :data-cy="`optin-${props.row.optin}`">
<b-tag :class="props.row.optin" :data-cy="`optin-${props.row.optin}`"> <b-icon :icon="props.row.optin === 'double' ?
<b-icon :icon="props.row.optin === 'double' ? 'account-check-outline' : 'account-off-outline'" size="is-small" />
'account-check-outline' : 'account-off-outline'" size="is-small" /> {{ ' ' }}
{{ ' ' }} {{ $t(`lists.optins.${props.row.optin}`) }}
{{ $t(`lists.optins.${props.row.optin}`) }} </b-tag>{{ ' ' }}
</b-tag>{{ ' ' }} </div>
</div> </b-table-column>
</b-table-column> <b-table-column v-slot="props" field="status" :label="$t('globals.fields.status')">
<b-table-column v-slot="props" field="status" :label="$t('globals.fields.status')"> {{ props.row.optin === 'double' ? props.row.subscriptionStatus : '-' }}
{{ props.row.optin === 'double' ? props.row.subscriptionStatus : '-' }} <template v-if="props.row.optin === 'double'
</b-table-column> && props.row.subscriptionMeta.optinIp">
<b-table-column v-slot="props" field="createdAt" <br /><span class="is-size-7">{{ props.row.subscriptionMeta.optinIp }}</span>
:label="$t('globals.fields.createdAt')"> </template>
{{ $utils.niceDate(props.row.subscriptionCreatedAt, true) }} </b-table-column>
</b-table-column> <b-table-column v-slot="props" field="createdAt"
<b-table-column v-slot="props" field="updatedAt" :label="$t('globals.fields.createdAt')">
:label="$t('globals.fields.updatedAt')"> {{ $utils.niceDate(props.row.subscriptionCreatedAt, true) }}
{{ $utils.niceDate(props.row.subscriptionCreatedAt, true) }} </b-table-column>
</b-table-column> <b-table-column v-slot="props" field="updatedAt"
</b-table> :label="$t('globals.fields.updatedAt')">
</div> {{ $utils.niceDate(props.row.subscriptionCreatedAt, true) }}
</b-table-column>
</b-table>
</div> </div>
<div class="bounces" v-show="bounces.length > 0"> <div class="bounces" v-show="bounces.length > 0">

View file

@ -36,6 +36,12 @@
name="privacy.allow_wipe" /> name="privacy.allow_wipe" />
</b-field> </b-field>
<b-field :label="$t('settings.privacy.recordOptinIP')"
:message="$t('settings.privacy.recordOptinIPHelp')">
<b-switch v-model="data['privacy.record_optin_ip']"
name="privacy.record_optin_ip" />
</b-field>
<b-field :label="$t('settings.privacy.domainBlocklist')" <b-field :label="$t('settings.privacy.domainBlocklist')"
:message="$t('settings.privacy.domainBlocklistHelp')"> :message="$t('settings.privacy.domainBlocklistHelp')">
<b-input type="textarea" <b-input type="textarea"

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Inclou la capçalera `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Inclou la capçalera `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Inclou capçaleres de cancel·lació de subscripció que permetin als clients de correu electrònic permetre als usuaris donar-se de baixa amb un sol clic.", "settings.privacy.listUnsubHeaderHelp": "Inclou capçaleres de cancel·lació de subscripció que permetin als clients de correu electrònic permetre als usuaris donar-se de baixa amb un sol clic.",
"settings.privacy.name": "Privadesa", "settings.privacy.name": "Privadesa",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Reinicia", "settings.restart": "Reinicia",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Zahrnout záhlaví `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Zahrnout záhlaví `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Zahrnout záhlaví zrušení odběrů, která umožňují e-mailovým klientům, aby povolili uživatelům zrušit odběr jediným klepnutím.", "settings.privacy.listUnsubHeaderHelp": "Zahrnout záhlaví zrušení odběrů, která umožňují e-mailovým klientům, aby povolili uživatelům zrušit odběr jediným klepnutím.",
"settings.privacy.name": "Soukromí", "settings.privacy.name": "Soukromí",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restart", "settings.restart": "Restart",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Cynnwys y pennawd 'Dad-danysgrifio o'r rhestr'", "settings.privacy.listUnsubHeader": "Cynnwys y pennawd 'Dad-danysgrifio o'r rhestr'",
"settings.privacy.listUnsubHeaderHelp": "Cynnwys penynnau dad-danysgrifio sy'n caniatáu i ddefnyddwyr dad-danysgrifio drwy glicio un botwm.", "settings.privacy.listUnsubHeaderHelp": "Cynnwys penynnau dad-danysgrifio sy'n caniatáu i ddefnyddwyr dad-danysgrifio drwy glicio un botwm.",
"settings.privacy.name": "Preifatrwydd", "settings.privacy.name": "Preifatrwydd",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Ailgychwyn", "settings.restart": "Ailgychwyn",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Inkludiere `List-Unsubscribe` (von Liste abmelden) Header", "settings.privacy.listUnsubHeader": "Inkludiere `List-Unsubscribe` (von Liste abmelden) Header",
"settings.privacy.listUnsubHeaderHelp": "Inkludiere Header zum einfachen Abmelden in den E-Mails. Erlaubt es, den E-Mail Clients der Nutzer eine \",Ein Klick\"-Abmeldung anzubieten.", "settings.privacy.listUnsubHeaderHelp": "Inkludiere Header zum einfachen Abmelden in den E-Mails. Erlaubt es, den E-Mail Clients der Nutzer eine \",Ein Klick\"-Abmeldung anzubieten.",
"settings.privacy.name": "Privatsphäre", "settings.privacy.name": "Privatsphäre",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Neustarten", "settings.restart": "Neustarten",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -488,6 +488,8 @@
"settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header", "settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header",
"settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.", "settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.",
"settings.privacy.name": "Privacy", "settings.privacy.name": "Privacy",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restart", "settings.restart": "Restart",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Incluir el encabezado para `darse de baja` de la lista", "settings.privacy.listUnsubHeader": "Incluir el encabezado para `darse de baja` de la lista",
"settings.privacy.listUnsubHeaderHelp": "Incluye los encabezados de darse de baja para habilitar a los clientes de correo para permitir a los usuarios darse de baja con un solo clic.", "settings.privacy.listUnsubHeaderHelp": "Incluye los encabezados de darse de baja para habilitar a los clientes de correo para permitir a los usuarios darse de baja con un solo clic.",
"settings.privacy.name": "Privacidad", "settings.privacy.name": "Privacidad",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Reiniciar", "settings.restart": "Reiniciar",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visite www.hcaptcha.com para conseguir la SiteKey y el secret.", "settings.security.captchaKeyHelp": "Visite www.hcaptcha.com para conseguir la SiteKey y el secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header", "settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header",
"settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.", "settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.",
"settings.privacy.name": "Yksityisyys", "settings.privacy.name": "Yksityisyys",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restart", "settings.restart": "Restart",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Inclure l'en-tête de désabonnement simplifié (via certaines messageries)", "settings.privacy.listUnsubHeader": "Inclure l'en-tête de désabonnement simplifié (via certaines messageries)",
"settings.privacy.listUnsubHeaderHelp": "Inclure des en-têtes de désabonnement qui permettent aux utilisateurs de se désabonner en un seul clic depuis leur client de messagerie.", "settings.privacy.listUnsubHeaderHelp": "Inclure des en-têtes de désabonnement qui permettent aux utilisateurs de se désabonner en un seul clic depuis leur client de messagerie.",
"settings.privacy.name": "Vie privée", "settings.privacy.name": "Vie privée",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Redémarrer", "settings.restart": "Redémarrer",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Allez sur www.hcaptcha.com pour obtenir une clef et son secret.", "settings.security.captchaKeyHelp": "Allez sur www.hcaptcha.com pour obtenir une clef et son secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "`List-Unsubscribe` fejléc", "settings.privacy.listUnsubHeader": "`List-Unsubscribe` fejléc",
"settings.privacy.listUnsubHeaderHelp": "Ha be van kapcsolva, egyes e-mail kliensek lehetővé teszik az egykattintásos leiratkozást.", "settings.privacy.listUnsubHeaderHelp": "Ha be van kapcsolva, egyes e-mail kliensek lehetővé teszik az egykattintásos leiratkozást.",
"settings.privacy.name": "Adatvédelem", "settings.privacy.name": "Adatvédelem",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Újraindítás", "settings.restart": "Újraindítás",
"settings.security.captchaKey": "hCaptcha.com kulcs", "settings.security.captchaKey": "hCaptcha.com kulcs",
"settings.security.captchaKeyHelp": "Kulcs és jelszó igénylése a hcaptcha.com oldalon.", "settings.security.captchaKeyHelp": "Kulcs és jelszó igénylése a hcaptcha.com oldalon.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Includere l'intestazione `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Includere l'intestazione `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Includere intestazioni di annullamento dell'iscrizione che consentono agli utenti di annullare l'iscrizione con un clic dal proprio client di posta elettronica.", "settings.privacy.listUnsubHeaderHelp": "Includere intestazioni di annullamento dell'iscrizione che consentono agli utenti di annullare l'iscrizione con un clic dal proprio client di posta elettronica.",
"settings.privacy.name": "Privacy", "settings.privacy.name": "Privacy",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Riavviare", "settings.restart": "Riavviare",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visita www.hcaptcha.com per ottenere la SiteKey anche il secret.", "settings.security.captchaKeyHelp": "Visita www.hcaptcha.com per ottenere la SiteKey anche il secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "`リスト-登録解除` ヘッダー", "settings.privacy.listUnsubHeader": "`リスト-登録解除` ヘッダー",
"settings.privacy.listUnsubHeaderHelp": "メールクライアントがワンクリックで登録解除をできるように登録解除用のヘッダーを含める。", "settings.privacy.listUnsubHeaderHelp": "メールクライアントがワンクリックで登録解除をできるように登録解除用のヘッダーを含める。",
"settings.privacy.name": "プライバシー", "settings.privacy.name": "プライバシー",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "再起動", "settings.restart": "再起動",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "`List-Unsubscribe` തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക", "settings.privacy.listUnsubHeader": "`List-Unsubscribe` തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക",
"settings.privacy.listUnsubHeaderHelp": "ഒറ്റ ക്ലിക്കിലൂടെ വരിക്കാനല്ലാതാക്കാൻ ഇ-മെയിൽ ക്ലൈന്റിൽ വരിക്കാരനല്ലാതാക്കാനുള്ള തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക.", "settings.privacy.listUnsubHeaderHelp": "ഒറ്റ ക്ലിക്കിലൂടെ വരിക്കാനല്ലാതാക്കാൻ ഇ-മെയിൽ ക്ലൈന്റിൽ വരിക്കാരനല്ലാതാക്കാനുള്ള തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക.",
"settings.privacy.name": "സ്വകാര്യത", "settings.privacy.name": "സ്വകാര്യത",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "പുനരാരംഭിയ്ക്കുക", "settings.restart": "പുനരാരംഭിയ്ക്കുക",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Voeg `List-Unsubscribe` header toe", "settings.privacy.listUnsubHeader": "Voeg `List-Unsubscribe` header toe",
"settings.privacy.listUnsubHeaderHelp": "Voeg header toe zodat e-mailprogramma's gebruikers zich kunnen laten uitschrijven in een klik.", "settings.privacy.listUnsubHeaderHelp": "Voeg header toe zodat e-mailprogramma's gebruikers zich kunnen laten uitschrijven in een klik.",
"settings.privacy.name": "Privacy", "settings.privacy.name": "Privacy",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Herstarten", "settings.restart": "Herstarten",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Dodawaj nagłówek `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Dodawaj nagłówek `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Dodaj nagłówki do wypisania się z subskrypcji. Niektóre programy pocztowe umożliwiają wypisanie się jednym kliknięciem.", "settings.privacy.listUnsubHeaderHelp": "Dodaj nagłówki do wypisania się z subskrypcji. Niektóre programy pocztowe umożliwiają wypisanie się jednym kliknięciem.",
"settings.privacy.name": "Prywatność", "settings.privacy.name": "Prywatność",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restart", "settings.restart": "Restart",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Wejdź na www.hcaptcha.com w celu pobrania klucza i sekretu.", "settings.security.captchaKeyHelp": "Wejdź na www.hcaptcha.com w celu pobrania klucza i sekretu.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Incluir cabeçalho `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Incluir cabeçalho `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Incluir cabeçalhos de desinscrição que permitem aos clientes de e-mail cancelem a inscrição em um único clique.", "settings.privacy.listUnsubHeaderHelp": "Incluir cabeçalhos de desinscrição que permitem aos clientes de e-mail cancelem a inscrição em um único clique.",
"settings.privacy.name": "Privacidade", "settings.privacy.name": "Privacidade",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Reiniciar", "settings.restart": "Reiniciar",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Incluir header `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Incluir header `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Incluir headers de cancelamento de subscrição que permite aos clientes de email permitir ao utilizadores cancelar a subscrição num único clique.", "settings.privacy.listUnsubHeaderHelp": "Incluir headers de cancelamento de subscrição que permite aos clientes de email permitir ao utilizadores cancelar a subscrição num único clique.",
"settings.privacy.name": "Privacidade", "settings.privacy.name": "Privacidade",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Reiniciar", "settings.restart": "Reiniciar",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visite www.hcaptcha.com para obter a chave e o segredo.", "settings.security.captchaKeyHelp": "Visite www.hcaptcha.com para obter a chave e o segredo.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Includeți antetul \"Listă-Dezabonare\"", "settings.privacy.listUnsubHeader": "Includeți antetul \"Listă-Dezabonare\"",
"settings.privacy.listUnsubHeaderHelp": "Include anteturi de dezabonare care permit clienților de e-mail să permită utilizatorilor să se dezaboneze printr-un singur clic.", "settings.privacy.listUnsubHeaderHelp": "Include anteturi de dezabonare care permit clienților de e-mail să permită utilizatorilor să se dezaboneze printr-un singur clic.",
"settings.privacy.name": "Confidențialitate", "settings.privacy.name": "Confidențialitate",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Repornește", "settings.restart": "Repornește",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Vizitați www.hcaptcha.com pentru a obține cheia și secretul.", "settings.security.captchaKeyHelp": "Vizitați www.hcaptcha.com pentru a obține cheia și secretul.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Включать заголовок `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Включать заголовок `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Включать заголовок отписки", "settings.privacy.listUnsubHeaderHelp": "Включать заголовок отписки",
"settings.privacy.name": "Конфиденциальност", "settings.privacy.name": "Конфиденциальност",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Перезапустить", "settings.restart": "Перезапустить",
"settings.security.captchaKey": "hCaptcha.com ключ сайта", "settings.security.captchaKey": "hCaptcha.com ключ сайта",
"settings.security.captchaKeyHelp": "Посетите www.hcaptcha.com для получения ключа сайта и секретного ключа.", "settings.security.captchaKeyHelp": "Посетите www.hcaptcha.com для получения ключа сайта и секретного ключа.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header", "settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header",
"settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.", "settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.",
"settings.privacy.name": "Privacy", "settings.privacy.name": "Privacy",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restart", "settings.restart": "Restart",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "Nastaviť hlavičku `List-Unsubscribe`", "settings.privacy.listUnsubHeader": "Nastaviť hlavičku `List-Unsubscribe`",
"settings.privacy.listUnsubHeaderHelp": "Nastaví hlavičku zrušenia odberov, ktorá umožňuje e-mailovým klientom, aby povolili používateľom zrušiť odber jedným kliknutím.", "settings.privacy.listUnsubHeaderHelp": "Nastaví hlavičku zrušenia odberov, ktorá umožňuje e-mailovým klientom, aby povolili používateľom zrušiť odber jedným kliknutím.",
"settings.privacy.name": "Súkromie", "settings.privacy.name": "Súkromie",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Restarť", "settings.restart": "Restarť",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": " `List-Unsubscribe` Başlık bilgisini ekle", "settings.privacy.listUnsubHeader": " `List-Unsubscribe` Başlık bilgisini ekle",
"settings.privacy.listUnsubHeaderHelp": "E-posta istemcilerinin kullanıcıların tek bir tıklamayla abonelikten çıkmalarına olanak tanıyan abonelik iptal başlıklarını ekleyin.", "settings.privacy.listUnsubHeaderHelp": "E-posta istemcilerinin kullanıcıların tek bir tıklamayla abonelikten çıkmalarına olanak tanıyan abonelik iptal başlıklarını ekleyin.",
"settings.privacy.name": "Gizlilik", "settings.privacy.name": "Gizlilik",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Yeniden başlat", "settings.restart": "Yeniden başlat",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "Bao gồm tiêu đề `Danh sách-Hủy đăng ký`", "settings.privacy.listUnsubHeader": "Bao gồm tiêu đề `Danh sách-Hủy đăng ký`",
"settings.privacy.listUnsubHeaderHelp": "Bao gồm các tiêu đề hủy đăng ký cho phép ứng dụng e-mail cho phép người dùng hủy đăng ký chỉ bằng một cú nhấp chuột.", "settings.privacy.listUnsubHeaderHelp": "Bao gồm các tiêu đề hủy đăng ký cho phép ứng dụng e-mail cho phép người dùng hủy đăng ký chỉ bằng một cú nhấp chuột.",
"settings.privacy.name": "Sự riêng tư", "settings.privacy.name": "Sự riêng tư",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "Khởi động lại", "settings.restart": "Khởi động lại",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -492,6 +492,8 @@
"settings.privacy.listUnsubHeader": "包括 `List-Unsubscribe` 标头", "settings.privacy.listUnsubHeader": "包括 `List-Unsubscribe` 标头",
"settings.privacy.listUnsubHeaderHelp": "包括允许电子邮件客户端允许用户通过单击取消订阅的取消订阅标题", "settings.privacy.listUnsubHeaderHelp": "包括允许电子邮件客户端允许用户通过单击取消订阅的取消订阅标题",
"settings.privacy.name": "隐私", "settings.privacy.name": "隐私",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "重新开始", "settings.restart": "重新开始",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -493,6 +493,8 @@
"settings.privacy.listUnsubHeader": "包括`List-Unsubscribe` 標頭", "settings.privacy.listUnsubHeader": "包括`List-Unsubscribe` 標頭",
"settings.privacy.listUnsubHeaderHelp": "包括允許電子郵件客戶端允許用戶通過單擊取消訂閱的取消訂閱標題", "settings.privacy.listUnsubHeaderHelp": "包括允許電子郵件客戶端允許用戶通過單擊取消訂閱的取消訂閱標題",
"settings.privacy.name": "隱私", "settings.privacy.name": "隱私",
"settings.privacy.recordOptinIP": "Record opt-in IP address",
"settings.privacy.recordOptinIPHelp": "Record IP address of double opt-ins in subscriber attributes.",
"settings.restart": "重新開始", "settings.restart": "重新開始",
"settings.security.captchaKey": "hCaptcha.com SiteKey", "settings.security.captchaKey": "hCaptcha.com SiteKey",
"settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.", "settings.security.captchaKeyHelp": "Visit www.hcaptcha.com to obtain the key and secret.",

View file

@ -451,8 +451,12 @@ func (c *Core) UnsubscribeByCampaign(subUUID, campUUID string, blocklist bool) e
} }
// ConfirmOptionSubscription confirms a subscriber's optin subscription. // ConfirmOptionSubscription confirms a subscriber's optin subscription.
func (c *Core) ConfirmOptionSubscription(subUUID string, listUUIDs []string) error { func (c *Core) ConfirmOptionSubscription(subUUID string, listUUIDs []string, meta models.JSON) error {
if _, err := c.q.ConfirmSubscriptionOptin.Exec(subUUID, pq.Array(listUUIDs)); err != nil { if meta == nil {
meta = models.JSON{}
}
if _, err := c.q.ConfirmSubscriptionOptin.Exec(subUUID, pq.Array(listUUIDs), meta); err != nil {
c.log.Printf("error confirming subscription: %v", err) c.log.Printf("error confirming subscription: %v", err)
return echo.NewHTTPError(http.StatusInternalServerError, return echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err))) c.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))

View file

@ -13,7 +13,8 @@ func V2_5_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
INSERT INTO settings (key, value) VALUES INSERT INTO settings (key, value) VALUES
('upload.extensions', '["jpg","jpeg","png","gif","svg","*"]'), ('upload.extensions', '["jpg","jpeg","png","gif","svg","*"]'),
('app.enable_public_archive_rss_content', 'false'), ('app.enable_public_archive_rss_content', 'false'),
('bounce.actions', '{"soft": {"count": 2, "action": "none"}, "hard": {"count": 2, "action": "blocklist"}, "complaint" : {"count": 2, "action": "blocklist"}}') ('bounce.actions', '{"soft": {"count": 2, "action": "none"}, "hard": {"count": 2, "action": "blocklist"}, "complaint" : {"count": 2, "action": "blocklist"}}'),
('privacy.record_optin_ip', 'false')
ON CONFLICT DO NOTHING; ON CONFLICT DO NOTHING;
`); err != nil { `); err != nil {
return err return err
@ -25,12 +26,14 @@ func V2_5_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
-- Add the content_type column. -- Add the content_type column.
ALTER TABLE media ADD COLUMN IF NOT EXISTS content_type TEXT NOT NULL DEFAULT 'application/octet-stream'; ALTER TABLE media ADD COLUMN IF NOT EXISTS content_type TEXT NOT NULL DEFAULT 'application/octet-stream';
-- Add meta column to subscriptions.
ALTER TABLE subscriber_lists ADD COLUMN IF NOT EXISTS meta JSONB NOT NULL DEFAULT '{}';
-- Fill the content type column for existing files (which would only be images at this point). -- Fill the content type column for existing files (which would only be images at this point).
UPDATE media SET content_type = CASE UPDATE media SET content_type = CASE
WHEN LOWER(SUBSTRING(filename FROM '.([^.]+)$')) = 'svg' THEN 'image/svg+xml' WHEN LOWER(SUBSTRING(filename FROM '.([^.]+)$')) = 'svg' THEN 'image/svg+xml'
ELSE 'image/' || LOWER(SUBSTRING(filename FROM '.([^.]+)$')) ELSE 'image/' || LOWER(SUBSTRING(filename FROM '.([^.]+)$'))
END; END;
`); err != nil { `); err != nil {
return err return err
} }

View file

@ -174,8 +174,9 @@ type subLists struct {
// Subscription represents a list attached to a subscriber. // Subscription represents a list attached to a subscriber.
type Subscription struct { type Subscription struct {
List List
SubscriptionStatus null.String `db:"subscription_status" json:"subscription_status"` SubscriptionStatus null.String `db:"subscription_status" json:"subscription_status"`
SubscriptionCreatedAt null.String `db:"subscription_created_at" json:"subscription_created_at"` SubscriptionCreatedAt null.String `db:"subscription_created_at" json:"subscription_created_at"`
Meta json.RawMessage `db:"meta" json:"meta"`
} }
// SubscriberExportProfile represents a subscriber's collated data in JSON for export. // SubscriberExportProfile represents a subscriber's collated data in JSON for export.

View file

@ -31,6 +31,7 @@ type Settings struct {
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"`
PrivacyRecordOptinIP bool `json:"privacy.record_optin_ip"`
DomainBlocklist []string `json:"privacy.domain_blocklist"` DomainBlocklist []string `json:"privacy.domain_blocklist"`
SecurityEnableCaptcha bool `json:"security.enable_captcha"` SecurityEnableCaptcha bool `json:"security.enable_captcha"`

View file

@ -43,6 +43,7 @@ WITH subs AS (
subscriber_lists.status AS subscription_status, subscriber_lists.status AS subscription_status,
subscriber_lists.created_at AS subscription_created_at, subscriber_lists.created_at AS subscription_created_at,
subscriber_lists.updated_at AS subscription_updated_at, subscriber_lists.updated_at AS subscription_updated_at,
subscriber_lists.meta AS subscription_meta,
lists.* lists.*
) l) ) l)
) )
@ -64,7 +65,10 @@ SELECT id as subscriber_id,
WITH sub AS ( WITH sub AS (
SELECT id FROM subscribers WHERE CASE WHEN $1 > 0 THEN id = $1 ELSE uuid = $2 END SELECT id FROM subscribers WHERE CASE WHEN $1 > 0 THEN id = $1 ELSE uuid = $2 END
) )
SELECT lists.*, subscriber_lists.status as subscription_status, subscriber_lists.created_at as subscription_created_at SELECT lists.*,
subscriber_lists.status as subscription_status,
subscriber_lists.created_at as subscription_created_at,
subscriber_lists.meta as subscription_meta
FROM lists LEFT JOIN subscriber_lists FROM lists LEFT JOIN subscriber_lists
ON (subscriber_lists.list_id = lists.id AND subscriber_lists.subscriber_id = (SELECT id FROM sub)) ON (subscriber_lists.list_id = lists.id AND subscriber_lists.subscriber_id = (SELECT id FROM sub))
WHERE CASE WHEN $3 = TRUE THEN TRUE ELSE subscriber_lists.status IS NOT NULL END WHERE CASE WHEN $3 = TRUE THEN TRUE ELSE subscriber_lists.status IS NOT NULL END
@ -214,7 +218,7 @@ WITH subID AS (
listIDs AS ( listIDs AS (
SELECT id FROM lists WHERE uuid = ANY($2::UUID[]) SELECT id FROM lists WHERE uuid = ANY($2::UUID[])
) )
UPDATE subscriber_lists SET status='confirmed', updated_at=NOW() UPDATE subscriber_lists SET status='confirmed', meta=meta || $3, updated_at=NOW()
WHERE subscriber_id = (SELECT id FROM subID) AND list_id = ANY(SELECT id FROM listIDs); WHERE subscriber_id = (SELECT id FROM subID) AND list_id = ANY(SELECT id FROM listIDs);
-- name: unsubscribe-subscribers-from-lists -- name: unsubscribe-subscribers-from-lists

View file

@ -43,6 +43,7 @@ DROP TABLE IF EXISTS subscriber_lists CASCADE;
CREATE TABLE subscriber_lists ( CREATE TABLE subscriber_lists (
subscriber_id INTEGER REFERENCES subscribers(id) ON DELETE CASCADE ON UPDATE CASCADE, subscriber_id INTEGER REFERENCES subscribers(id) ON DELETE CASCADE ON UPDATE CASCADE,
list_id INTEGER NULL REFERENCES lists(id) ON DELETE CASCADE ON UPDATE CASCADE, list_id INTEGER NULL REFERENCES lists(id) ON DELETE CASCADE ON UPDATE CASCADE,
meta JSONB NOT NULL DEFAULT '{}',
status subscription_status NOT NULL DEFAULT 'unconfirmed', status subscription_status NOT NULL DEFAULT 'unconfirmed',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
@ -225,6 +226,7 @@ INSERT INTO settings (key, value) VALUES
('privacy.allow_preferences', 'true'), ('privacy.allow_preferences', 'true'),
('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'), ('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'),
('privacy.domain_blocklist', '[]'), ('privacy.domain_blocklist', '[]'),
('privacy.record_optin_ip', 'false'),
('security.enable_captcha', 'false'), ('security.enable_captcha', 'false'),
('security.captcha_key', '""'), ('security.captcha_key', '""'),
('security.captcha_secret', '""'), ('security.captcha_secret', '""'),