Repository: liberapay/liberapay.com

Issue:
Subscriber counts are broken
If a user who is already subscribed sends the subscription request again, then the number of subscribers increases instead of staying the same. This is most likely a regression from a08290321413a3b94fade8beb6dc014610cfca75.

Report (not yet public): <https://hackerone.com/reports/362601>.
Subscriber counts are broken
If a user who is already subscribed sends the subscription request again, then the number of subscribers increases instead of staying the same. This is most likely a regression from a08290321413a3b94fade8beb6dc014610cfca75.

Report (not yet public): <https://hackerone.com/reports/362601>.


Discussion:

Resolving patch:
diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py
--- a/liberapay/models/participant.py
+++ b/liberapay/models/participant.py
@@ -1500,33 +1500,23 @@ def create_community(self, name, **kw):
 
     def upsert_community_membership(self, on, c_id):
         p_id = self.id
-        self.db.run("""
-            DO $$
-            DECLARE
-                cname text;
-            BEGIN
-                BEGIN
-                    INSERT INTO community_memberships
-                                (community, participant, is_on)
-                         VALUES (%(c_id)s, %(p_id)s, %(on)s);
-                    IF (FOUND) THEN RETURN; END IF;
-                EXCEPTION WHEN unique_violation THEN
-                    GET STACKED DIAGNOSTICS cname = CONSTRAINT_NAME;
-                    IF (cname <> 'community_memberships_participant_community_key') THEN
-                        RAISE;
-                    END IF;
-                END;
+        if on:
+            self.db.run("""
+                INSERT INTO community_memberships
+                            (community, participant, is_on)
+                     VALUES (%(c_id)s, %(p_id)s, %(on)s)
+                ON CONFLICT (participant, community) DO UPDATE
+                        SET is_on = excluded.is_on
+                          , mtime = current_timestamp
+            """, locals())
+        else:
+            self.db.run("""
                 UPDATE community_memberships
                    SET is_on = %(on)s
-                     , mtime = CURRENT_TIMESTAMP
+                     , mtime = current_timestamp
                  WHERE community = %(c_id)s
                    AND participant = %(p_id)s;
-                IF (NOT FOUND) THEN
-                    RAISE 'upsert in community_memberships failed';
-                END IF;
-            END;
-            $$ LANGUAGE plpgsql;
-        """, locals())
+            """, locals())
 
     def get_communities(self):
         return self.db.all("""


Test patch:
diff --git a/tests/py/test_communities.py b/tests/py/test_communities.py
--- a/tests/py/test_communities.py
+++ b/tests/py/test_communities.py
@@ -65,12 +65,20 @@ def test_post_bad_name_returns_404(self):
         assert response.code == 404
 
     def test_subscribe_and_unsubscribe(self):
-        response = self.client.POST('/for/test/subscribe', auth_as=self.bob,
-                                    xhr=True)
+        # Subscribe
+        response = self.client.POST('/for/test/subscribe', auth_as=self.bob, xhr=True)
         assert response.code == 200
+        p = self.community.participant.refetch()
+        assert p.nsubscribers == 1
 
-        self.client.POST('/for/test/unsubscribe', auth_as=self.bob, xhr=True)
+        # Subscribe again, shouldn't do anything
+        response = self.client.POST('/for/test/subscribe', auth_as=self.bob, xhr=True)
+        assert response.code == 200
+        p = self.community.participant.refetch()
+        assert p.nsubscribers == 1
 
+        # Unsubscribe
+        self.client.POST('/for/test/unsubscribe', auth_as=self.bob, xhr=True)
         communities = self.bob.get_communities()
         assert len(communities) == 0