diff options
| -rw-r--r-- | postgres/init.sql | 9 | ||||
| -rw-r--r-- | pydis_site/apps/api/models/bot/metricity.py | 31 | ||||
| -rw-r--r-- | pydis_site/apps/api/tests/test_users.py | 10 | ||||
| -rw-r--r-- | pydis_site/apps/api/viewsets/bot/user.py | 5 | 
4 files changed, 48 insertions, 7 deletions
diff --git a/postgres/init.sql b/postgres/init.sql index 922ce1ad..75a9154c 100644 --- a/postgres/init.sql +++ b/postgres/init.sql @@ -16,15 +16,18 @@ INSERT INTO users VALUES (  CREATE TABLE messages (      id varchar,      author_id varchar references users(id), -    primary key(id) +    primary key(id), +    is_deleted boolean  );  INSERT INTO messages VALUES(      0, -    0 +    0, +    false  );  INSERT INTO messages VALUES(      1, -    0 +    0, +    false  ); diff --git a/pydis_site/apps/api/models/bot/metricity.py b/pydis_site/apps/api/models/bot/metricity.py index eed1deb4..e7fc92fc 100644 --- a/pydis_site/apps/api/models/bot/metricity.py +++ b/pydis_site/apps/api/models/bot/metricity.py @@ -1,5 +1,7 @@  from django.db import connections +BLOCK_INTERVAL = 10 * 60  # 10 minute blocks +  class NotFound(Exception):      """Raised when an entity cannot be found.""" @@ -43,3 +45,32 @@ class Metricity:              raise NotFound()          return values[0] + +    def total_message_blocks(self, user_id: str) -> int: +        """ +        Query number of 10 minute blocks during which the user has been active. + +        This metric prevents users from spamming to achieve the message total threshold. +        """ +        self.cursor.execute( +            """ +            SELECT +                COUNT(*) +            FROM ( +                SELECT +                    (floor((extract('epoch' from created_at) / %d )) * %d) AS interval +                FROM messages +                WHERE +                    author_id='%s' +                    AND NOT is_deleted +                GROUP BY interval +            ) block_query; +            """, +            [BLOCK_INTERVAL, BLOCK_INTERVAL, user_id] +        ) +        values = self.cursor.fetchone() + +        if not values: +            raise NotFound() + +        return values[0] diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py index 72ffcb3c..c422f895 100644 --- a/pydis_site/apps/api/tests/test_users.py +++ b/pydis_site/apps/api/tests/test_users.py @@ -409,7 +409,8 @@ class UserMetricityTests(APISubdomainTestCase):          # Given          verified_at = "foo"          total_messages = 1 -        self.mock_metricity_user(verified_at, total_messages) +        total_blocks = 1 +        self.mock_metricity_user(verified_at, total_messages, total_blocks)          # When          url = reverse('bot:user-metricity-data', args=[0], host='api') @@ -421,6 +422,7 @@ class UserMetricityTests(APISubdomainTestCase):              "verified_at": verified_at,              "total_messages": total_messages,              "voice_banned": False, +            "activity_blocks": total_blocks          })      def test_no_metricity_user(self): @@ -440,7 +442,7 @@ class UserMetricityTests(APISubdomainTestCase):              {'exception': ObjectDoesNotExist, 'voice_banned': False},          ] -        self.mock_metricity_user("foo", 1) +        self.mock_metricity_user("foo", 1, 1)          for case in cases:              with self.subTest(exception=case['exception'], voice_banned=case['voice_banned']): @@ -453,13 +455,14 @@ class UserMetricityTests(APISubdomainTestCase):                      self.assertEqual(response.status_code, 200)                      self.assertEqual(response.json()["voice_banned"], case["voice_banned"]) -    def mock_metricity_user(self, verified_at, total_messages): +    def mock_metricity_user(self, verified_at, total_messages, total_blocks):          patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity")          self.metricity = patcher.start()          self.addCleanup(patcher.stop)          self.metricity = self.metricity.return_value.__enter__.return_value          self.metricity.user.return_value = dict(verified_at=verified_at)          self.metricity.total_messages.return_value = total_messages +        self.metricity.total_message_blocks.return_value = total_blocks      def mock_no_metricity_user(self):          patcher = patch("pydis_site.apps.api.viewsets.bot.user.Metricity") @@ -468,3 +471,4 @@ class UserMetricityTests(APISubdomainTestCase):          self.metricity = self.metricity.return_value.__enter__.return_value          self.metricity.user.side_effect = NotFound()          self.metricity.total_messages.side_effect = NotFound() +        self.metricity.total_message_blocks.side_effect = NotFound() diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py index 5205dc97..79f90163 100644 --- a/pydis_site/apps/api/viewsets/bot/user.py +++ b/pydis_site/apps/api/viewsets/bot/user.py @@ -110,7 +110,9 @@ class UserViewSet(ModelViewSet):      #### Response format      >>> {      ...    "verified_at": "2020-10-06T21:54:23.540766", -    ...    "total_messages": 2 +    ...    "total_messages": 2, +    ...    "voice_banned": False, +    ...    "activity_blocks": 1      ...}      #### Status codes @@ -255,6 +257,7 @@ class UserViewSet(ModelViewSet):                  data = metricity.user(user.id)                  data["total_messages"] = metricity.total_messages(user.id)                  data["voice_banned"] = voice_banned +                data["activity_blocks"] = metricity.total_message_blocks(user.id)                  return Response(data, status=status.HTTP_200_OK)              except NotFound:                  return Response(dict(detail="User not found in metricity"),  |