diff --git a/cmd/subscribers.go b/cmd/subscribers.go index 84d07bf..20d214c 100644 --- a/cmd/subscribers.go +++ b/cmd/subscribers.go @@ -643,7 +643,14 @@ 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, @@ -651,7 +658,8 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool 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 { @@ -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. diff --git a/frontend/cypress/integration/forms.js b/frontend/cypress/integration/forms.js index d401d51..55240a0 100644 --- a/frontend/cypress/integration/forms.js +++ b/frontend/cypress/integration/forms.js @@ -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); + } + }); + }); }); diff --git a/frontend/cypress/integration/subscribers.js b/frontend/cypress/integration/subscribers.js index aa74094..2dc8a6a 100644 --- a/frontend/cypress/integration/subscribers.js +++ b/frontend/cypress/integration/subscribers.js @@ -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(); }); @@ -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); }); }); }); @@ -133,6 +133,7 @@ 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); @@ -140,7 +141,6 @@ describe('Subscribers', () => { 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) => { diff --git a/internal/subimporter/importer.go b/internal/subimporter/importer.go index a5d9ae8..c9b5815 100644 --- a/internal/subimporter/importer.go +++ b/internal/subimporter/importer.go @@ -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 { diff --git a/queries.sql b/queries.sql index 8973340..a86ecb1 100644 --- a/queries.sql +++ b/queries.sql @@ -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() diff --git a/static/public/templates/subscription-form.html b/static/public/templates/subscription-form.html index 2bbe852..a4790d9 100644 --- a/static/public/templates/subscription-form.html +++ b/static/public/templates/subscription-form.html @@ -7,7 +7,7 @@

- +