Skip to content

Commit

Permalink
Add preconfirm_subscriptions=true/falsenew subs API.
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
knadh committed Apr 17, 2021
1 parent 708d0e0 commit ad0a0e0
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 12 deletions.
22 changes: 17 additions & 5 deletions cmd/subscribers.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,15 +643,23 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
}
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,
req.UUID,
req.Email,
strings.TrimSpace(req.Name),
req.Status,
req.Attribs,
req.Lists,
req.ListUUIDs); err != nil {
req.ListUUIDs,
subStatus); err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Constraint == "subscribers_email_key" {
isNew = false
} else {
Expand All @@ -670,9 +678,13 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
return sub, false, false, err
}

// Send a confirmation e-mail (if there are any double opt-in lists).
num, _ := sendOptinConfirmation(sub, []int64(req.Lists), app)
return sub, isNew, num > 0, nil
hasOptin := false
if !req.PreconfirmSubs {
// 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.
Expand Down
32 changes: 32 additions & 0 deletions frontend/cypress/integration/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,36 @@ describe('Forms', () => {
cy.get('ul[data-cy=lists] .checkbox').click();
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);
}
});
});
});
6 changes: 3 additions & 3 deletions frontend/cypress/integration/subscribers.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('Subscribers', () => {


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) => {
cy.get('tbody td.checkbox-cell .checkbox').eq(r).click();
});
Expand All @@ -86,7 +86,7 @@ describe('Subscribers', () => {
cy.wrap($el).find('.tag').should('have.length', c.rows[r].length);
c.rows[r].forEach((status, n) => {
// 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);
});
});
});
Expand Down Expand Up @@ -133,14 +133,14 @@ describe('Subscribers', () => {
});

// Confirm the edits on the table.
cy.wait(250);
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-label=E-mail]').contains(rows[id].email);
cy.wrap($el).find('td[data-label=Name]').contains(rows[id].name);
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.'
cy.wait(250);
cy.wrap($el).find(`.tags .${rows[id].status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
.its('length').should('eq', 2);
cy.wrap($el).find('td[data-label=Lists]').then((l) => {
Expand Down
5 changes: 3 additions & 2 deletions internal/subimporter/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ type Status struct {
// SubReq is a wrapper over the Subscriber model.
type SubReq struct {
models.Subscriber
Lists pq.Int64Array `json:"lists"`
ListUUIDs pq.StringArray `json:"list_uuids"`
Lists pq.Int64Array `json:"lists"`
ListUUIDs pq.StringArray `json:"list_uuids"`
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
}

type importStatusTpl struct {
Expand Down
2 changes: 1 addition & 1 deletion queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ subs AS (
VALUES(
(SELECT id FROM sub),
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
SET updated_at=NOW()
Expand Down
2 changes: 1 addition & 1 deletion static/public/templates/subscription-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ <h2>{{ L.T "public.subTitle" }}</h2>
<div>
<p>
<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>
<label>{{ L.T "public.subName" }}</label>
Expand Down

0 comments on commit ad0a0e0

Please sign in to comment.