瀏覽代碼

Add `preconfirm_subscriptions=true/false`new 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`.
Kailash Nadh 4 年之前
父節點
當前提交
ad0a0e0841

+ 17 - 5
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.

+ 32 - 0
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);
+      }
+    });
+  });
 });

+ 3 - 3
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) => {

+ 3 - 2
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 {

+ 1 - 1
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()

+ 1 - 1
static/public/templates/subscription-form.html

@@ -7,7 +7,7 @@
         <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>