Add preconfirm_subscriptions=true/falsenew subs API.

Sending th optional flag as `trunue` in the POST /api/subscrirs
body will skip sending opt-iconfirmation e-mails to subscribers
and mark list subscriptions in the request a`confirmed`.
This commit is contained in:
Kailash Nadh 2021-04-17 13:34:37 +05:30
parent 708d0e0b00
commit ad0a0e0841
6 changed files with 57 additions and 12 deletions

View file

@ -643,7 +643,14 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
} }
req.UUID = uu.String() req.UUID = uu.String()
isNew := true var (
isNew = true
subStatus = models.SubscriptionStatusUnconfirmed
)
if req.PreconfirmSubs {
subStatus = models.SubscriptionStatusConfirmed
}
if err = app.queries.InsertSubscriber.Get(&req.ID, if err = app.queries.InsertSubscriber.Get(&req.ID,
req.UUID, req.UUID,
req.Email, req.Email,
@ -651,7 +658,8 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
req.Status, req.Status,
req.Attribs, req.Attribs,
req.Lists, req.Lists,
req.ListUUIDs); err != nil { req.ListUUIDs,
subStatus); err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Constraint == "subscribers_email_key" { if pqErr, ok := err.(*pq.Error); ok && pqErr.Constraint == "subscribers_email_key" {
isNew = false isNew = false
} else { } else {
@ -670,9 +678,13 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
return sub, false, false, err return sub, false, false, err
} }
// Send a confirmation e-mail (if there are any double opt-in lists). hasOptin := false
num, _ := sendOptinConfirmation(sub, []int64(req.Lists), app) if !req.PreconfirmSubs {
return sub, isNew, num > 0, nil // Send a confirmation e-mail (if there are any double opt-in lists).
num, _ := sendOptinConfirmation(sub, []int64(req.Lists), app)
hasOptin = num > 0
}
return sub, isNew, hasOptin, nil
} }
// getSubscriber gets a single subscriber by ID, uuid, or e-mail in that order. // getSubscriber gets a single subscriber by ID, uuid, or e-mail in that order.

View file

@ -33,4 +33,36 @@ describe('Forms', () => {
cy.get('ul[data-cy=lists] .checkbox').click(); cy.get('ul[data-cy=lists] .checkbox').click();
cy.get('[data-cy=form] pre').should('not.exist'); cy.get('[data-cy=form] pre').should('not.exist');
}); });
it('Subscribes from public form page', () => {
// Create a public test list.
cy.request('POST', '/api/lists', { name: 'test-list', type: 'public', optin: 'single' });
// Open the public page and subscribe to alternating lists multiple times.
// There should be no errors and two new subscribers should be subscribed to two lists.
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2; j++) {
cy.loginAndVisit('/subscription/form');
cy.get('input[name=email]').clear().type(`test${i}@test.com`);
cy.get('input[name=name]').clear().type(`test${i}`);
cy.get('input[type=checkbox]').eq(j).click();
cy.get('button').click();
cy.wait(250);
cy.get('.wrap').contains(/has been sent|successfully/);
}
}
// Verify form subscriptions.
cy.request('/api/subscribers').should((response) => {
const { data } = response.body;
// Two new + two dummy subscribers that are there by default.
expect(data.total).to.equal(4);
// The two new subscribers should each have two list subscriptions.
for (let i = 0; i < 2; i++) {
expect(data.results.find((s) => s.email === `test${i}@test.com`).lists.length).to.equal(2);
}
});
});
}); });

View file

@ -60,7 +60,7 @@ describe('Subscribers', () => {
cases.forEach((c, n) => { cases.forEach((c, n) => {
// Select one of the 2 subscriber in the table. // Select one of the 2 subscribers in the table.
Object.keys(c.rows).forEach((r) => { Object.keys(c.rows).forEach((r) => {
cy.get('tbody td.checkbox-cell .checkbox').eq(r).click(); cy.get('tbody td.checkbox-cell .checkbox').eq(r).click();
}); });
@ -86,7 +86,7 @@ describe('Subscribers', () => {
cy.wrap($el).find('.tag').should('have.length', c.rows[r].length); cy.wrap($el).find('.tag').should('have.length', c.rows[r].length);
c.rows[r].forEach((status, n) => { c.rows[r].forEach((status, n) => {
// eg: .tag(n).unconfirmed // eg: .tag(n).unconfirmed
cy.wrap($el).find(`.tag:nth-child(${n + 1}).${status}`); cy.wrap($el).find('.tag').eq(n).should('have.class', status);
}); });
}); });
}); });
@ -133,6 +133,7 @@ describe('Subscribers', () => {
}); });
// Confirm the edits on the table. // Confirm the edits on the table.
cy.wait(250);
cy.get('tbody tr').each(($el) => { cy.get('tbody tr').each(($el) => {
cy.wrap($el).find('td[data-id]').invoke('attr', 'data-id').then((id) => { cy.wrap($el).find('td[data-id]').invoke('attr', 'data-id').then((id) => {
cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email); cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email);
@ -140,7 +141,6 @@ describe('Subscribers', () => {
cy.wrap($el).find('td[data-label=Status]').contains(rows[id].status, { matchCase: false }); cy.wrap($el).find('td[data-label=Status]').contains(rows[id].status, { matchCase: false });
// Both lists on the enabled sub should be 'unconfirmed' and the blocklisted one, 'unsubscribed.' // Both lists on the enabled sub should be 'unconfirmed' and the blocklisted one, 'unsubscribed.'
cy.wait(250);
cy.wrap($el).find(`.tags .${rows[id].status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`) cy.wrap($el).find(`.tags .${rows[id].status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
.its('length').should('eq', 2); .its('length').should('eq', 2);
cy.wrap($el).find('td[data-label=Lists]').then((l) => { cy.wrap($el).find('td[data-label=Lists]').then((l) => {

View file

@ -89,8 +89,9 @@ type Status struct {
// SubReq is a wrapper over the Subscriber model. // SubReq is a wrapper over the Subscriber model.
type SubReq struct { type SubReq struct {
models.Subscriber models.Subscriber
Lists pq.Int64Array `json:"lists"` Lists pq.Int64Array `json:"lists"`
ListUUIDs pq.StringArray `json:"list_uuids"` ListUUIDs pq.StringArray `json:"list_uuids"`
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
} }
type importStatusTpl struct { type importStatusTpl struct {

View file

@ -71,7 +71,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='blocklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END) (CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE $8::subscription_status 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()

View file

@ -7,7 +7,7 @@
<div> <div>
<p> <p>
<label>{{ L.T "subscribers.email" }}</label> <label>{{ L.T "subscribers.email" }}</label>
<input name="email" required="true" type="email" placeholder="{{ L.T "subscribers.email" }}" > <input name="email" required="true" type="email" placeholder="{{ L.T "subscribers.email" }}" autofocus="true" >
</p> </p>
<p> <p>
<label>{{ L.T "public.subName" }}</label> <label>{{ L.T "public.subName" }}</label>