aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps/api
diff options
context:
space:
mode:
Diffstat (limited to 'pydis_site/apps/api')
-rw-r--r--pydis_site/apps/api/migrations/0080_add_aoc_tables.py32
-rw-r--r--pydis_site/apps/api/migrations/0081_bumpedthread.py22
-rw-r--r--pydis_site/apps/api/migrations/0082_otn_allow_big_solidus.py19
-rw-r--r--pydis_site/apps/api/models/__init__.py5
-rw-r--r--pydis_site/apps/api/models/bot/__init__.py5
-rw-r--r--pydis_site/apps/api/models/bot/aoc_completionist_block.py26
-rw-r--r--pydis_site/apps/api/models/bot/aoc_link.py21
-rw-r--r--pydis_site/apps/api/models/bot/bumped_thread.py22
-rw-r--r--pydis_site/apps/api/models/bot/off_topic_channel_name.py2
-rw-r--r--pydis_site/apps/api/serializers.py49
-rw-r--r--pydis_site/apps/api/tests/test_bumped_threads.py63
-rw-r--r--pydis_site/apps/api/urls.py19
-rw-r--r--pydis_site/apps/api/viewsets/__init__.py5
-rw-r--r--pydis_site/apps/api/viewsets/bot/__init__.py3
-rw-r--r--pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py73
-rw-r--r--pydis_site/apps/api/viewsets/bot/aoc_link.py71
-rw-r--r--pydis_site/apps/api/viewsets/bot/bumped_thread.py66
17 files changed, 497 insertions, 6 deletions
diff --git a/pydis_site/apps/api/migrations/0080_add_aoc_tables.py b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py
new file mode 100644
index 00000000..2c0c689a
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0080_add_aoc_tables.py
@@ -0,0 +1,32 @@
+# Generated by Django 3.1.14 on 2022-03-06 16:07
+
+from django.db import migrations, models
+import django.db.models.deletion
+import pydis_site.apps.api.models.mixins
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0079_merge_20220125_2022'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AocAccountLink',
+ fields=[
+ ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')),
+ ('aoc_username', models.CharField(help_text='The AoC username associated with the Discord User.', max_length=120)),
+ ],
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
+ ),
+ migrations.CreateModel(
+ name='AocCompletionistBlock',
+ fields=[
+ ('user', models.OneToOneField(help_text='The user that is blocked from getting the AoC Completionist Role', on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='api.user')),
+ ('is_blocked', models.BooleanField(default=True, help_text='Whether this user is actively being blocked from getting the AoC Completionist Role', verbose_name='Blocked')),
+ ('reason', models.TextField(help_text='The reason for the AoC Completionist Role Block.', null=True)),
+ ],
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0081_bumpedthread.py b/pydis_site/apps/api/migrations/0081_bumpedthread.py
new file mode 100644
index 00000000..03e66cc1
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0081_bumpedthread.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.1.14 on 2022-02-19 16:26
+
+import django.core.validators
+from django.db import migrations, models
+import pydis_site.apps.api.models.mixins
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0080_add_aoc_tables'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='BumpedThread',
+ fields=[
+ ('thread_id', models.BigIntegerField(help_text='The thread ID that should be bumped.', primary_key=True, serialize=False, validators=[django.core.validators.MinValueValidator(limit_value=0, message='Thread IDs cannot be negative.')], verbose_name='Thread ID')),
+ ],
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0082_otn_allow_big_solidus.py b/pydis_site/apps/api/migrations/0082_otn_allow_big_solidus.py
new file mode 100644
index 00000000..abbb98ec
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0082_otn_allow_big_solidus.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1.14 on 2022-04-21 23:29
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0081_bumpedthread'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='offtopicchannelname',
+ name='name',
+ field=models.CharField(help_text='The actual channel name that will be used on our Discord server.', max_length=96, primary_key=True, serialize=False, validators=[django.core.validators.RegexValidator(regex="^[a-z0-9\\U0001d5a0-\\U0001d5b9-ǃ?’'<>⧹⧸]+$")]),
+ ),
+ ]
diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py
index fd5bf220..a197e988 100644
--- a/pydis_site/apps/api/models/__init__.py
+++ b/pydis_site/apps/api/models/__init__.py
@@ -1,15 +1,18 @@
# flake8: noqa
from .bot import (
- FilterList,
BotSetting,
+ BumpedThread,
DocumentationLink,
DeletedMessage,
+ FilterList,
Infraction,
Message,
MessageDeletionContext,
Nomination,
NominationEntry,
OffensiveMessage,
+ AocAccountLink,
+ AocCompletionistBlock,
OffTopicChannelName,
Reminder,
Role,
diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py
index ac864de3..013bb85e 100644
--- a/pydis_site/apps/api/models/bot/__init__.py
+++ b/pydis_site/apps/api/models/bot/__init__.py
@@ -1,10 +1,13 @@
# flake8: noqa
-from .filter_list import FilterList
from .bot_setting import BotSetting
+from .bumped_thread import BumpedThread
from .deleted_message import DeletedMessage
from .documentation_link import DocumentationLink
+from .filter_list import FilterList
from .infraction import Infraction
from .message import Message
+from .aoc_completionist_block import AocCompletionistBlock
+from .aoc_link import AocAccountLink
from .message_deletion_context import MessageDeletionContext
from .nomination import Nomination, NominationEntry
from .off_topic_channel_name import OffTopicChannelName
diff --git a/pydis_site/apps/api/models/bot/aoc_completionist_block.py b/pydis_site/apps/api/models/bot/aoc_completionist_block.py
new file mode 100644
index 00000000..acbc0eba
--- /dev/null
+++ b/pydis_site/apps/api/models/bot/aoc_completionist_block.py
@@ -0,0 +1,26 @@
+from django.db import models
+
+from pydis_site.apps.api.models.bot.user import User
+from pydis_site.apps.api.models.mixins import ModelReprMixin
+
+
+class AocCompletionistBlock(ModelReprMixin, models.Model):
+ """A Discord user blocked from getting the AoC completionist Role."""
+
+ user = models.OneToOneField(
+ User,
+ on_delete=models.CASCADE,
+ help_text="The user that is blocked from getting the AoC Completionist Role",
+ primary_key=True
+ )
+
+ is_blocked = models.BooleanField(
+ default=True,
+ help_text="Whether this user is actively being blocked "
+ "from getting the AoC Completionist Role",
+ verbose_name="Blocked"
+ )
+ reason = models.TextField(
+ null=True,
+ help_text="The reason for the AoC Completionist Role Block."
+ )
diff --git a/pydis_site/apps/api/models/bot/aoc_link.py b/pydis_site/apps/api/models/bot/aoc_link.py
new file mode 100644
index 00000000..4e9d4882
--- /dev/null
+++ b/pydis_site/apps/api/models/bot/aoc_link.py
@@ -0,0 +1,21 @@
+from django.db import models
+
+from pydis_site.apps.api.models.bot.user import User
+from pydis_site.apps.api.models.mixins import ModelReprMixin
+
+
+class AocAccountLink(ModelReprMixin, models.Model):
+ """An AoC account link for a Discord User."""
+
+ user = models.OneToOneField(
+ User,
+ on_delete=models.CASCADE,
+ help_text="The user that is blocked from getting the AoC Completionist Role",
+ primary_key=True
+ )
+
+ aoc_username = models.CharField(
+ max_length=120,
+ help_text="The AoC username associated with the Discord User.",
+ blank=False
+ )
diff --git a/pydis_site/apps/api/models/bot/bumped_thread.py b/pydis_site/apps/api/models/bot/bumped_thread.py
new file mode 100644
index 00000000..cdf9a950
--- /dev/null
+++ b/pydis_site/apps/api/models/bot/bumped_thread.py
@@ -0,0 +1,22 @@
+from django.core.validators import MinValueValidator
+from django.db import models
+
+from pydis_site.apps.api.models.mixins import ModelReprMixin
+
+
+class BumpedThread(ModelReprMixin, models.Model):
+ """A list of thread IDs to be bumped."""
+
+ thread_id = models.BigIntegerField(
+ primary_key=True,
+ help_text=(
+ "The thread ID that should be bumped."
+ ),
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Thread IDs cannot be negative."
+ ),
+ ),
+ verbose_name="Thread ID",
+ )
diff --git a/pydis_site/apps/api/models/bot/off_topic_channel_name.py b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
index e9fec114..b380efad 100644
--- a/pydis_site/apps/api/models/bot/off_topic_channel_name.py
+++ b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
@@ -11,7 +11,7 @@ class OffTopicChannelName(ModelReprMixin, models.Model):
primary_key=True,
max_length=96,
validators=(
- RegexValidator(regex=r"^[a-z0-9\U0001d5a0-\U0001d5b9-ǃ?’'<>]+$"),
+ RegexValidator(regex=r"^[a-z0-9\U0001d5a0-\U0001d5b9-ǃ?’'<>⧹⧸]+$"),
),
help_text="The actual channel name that will be used on our Discord server."
)
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 745aff42..e53ccffa 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -13,7 +13,10 @@ from rest_framework.settings import api_settings
from rest_framework.validators import UniqueTogetherValidator
from .models import (
+ AocAccountLink,
+ AocCompletionistBlock,
BotSetting,
+ BumpedThread,
DeletedMessage,
DocumentationLink,
FilterList,
@@ -39,6 +42,32 @@ class BotSettingSerializer(ModelSerializer):
fields = ('name', 'data')
+class ListBumpedThreadSerializer(ListSerializer):
+ """Custom ListSerializer to override to_representation() when list views are triggered."""
+
+ def to_representation(self, objects: list[BumpedThread]) -> int:
+ """
+ Used by the `ListModelMixin` to return just the list of bumped thread ids.
+
+ Only the thread_id field is useful, hence it is unnecessary to create a nested dictionary.
+
+ Additionally, this allows bumped thread routes to simply return an
+ array of thread_id ints instead of objects, saving on bandwidth.
+ """
+ return [obj.thread_id for obj in objects]
+
+
+class BumpedThreadSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `BumpedThread` instances."""
+
+ class Meta:
+ """Metadata defined for the Django REST Framework."""
+
+ list_serializer_class = ListBumpedThreadSerializer
+ model = BumpedThread
+ fields = ('thread_id',)
+
+
class DeletedMessageSerializer(ModelSerializer):
"""
A class providing (de-)serialization of `DeletedMessage` instances.
@@ -250,6 +279,26 @@ class ReminderSerializer(ModelSerializer):
)
+class AocCompletionistBlockSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `AocCompletionistBlock` instances."""
+
+ class Meta:
+ """Metadata defined for the Django REST Framework."""
+
+ model = AocCompletionistBlock
+ fields = ("user", "is_blocked", "reason")
+
+
+class AocAccountLinkSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `AocAccountLink` instances."""
+
+ class Meta:
+ """Metadata defined for the Django REST Framework."""
+
+ model = AocAccountLink
+ fields = ("user", "aoc_username")
+
+
class RoleSerializer(ModelSerializer):
"""A class providing (de-)serialization of `Role` instances."""
diff --git a/pydis_site/apps/api/tests/test_bumped_threads.py b/pydis_site/apps/api/tests/test_bumped_threads.py
new file mode 100644
index 00000000..316e3f0b
--- /dev/null
+++ b/pydis_site/apps/api/tests/test_bumped_threads.py
@@ -0,0 +1,63 @@
+from django.urls import reverse
+
+from .base import AuthenticatedAPITestCase
+from ..models import BumpedThread
+
+
+class UnauthedBumpedThreadAPITests(AuthenticatedAPITestCase):
+ def setUp(self):
+ super().setUp()
+ self.client.force_authenticate(user=None)
+
+ def test_detail_lookup_returns_401(self):
+ url = reverse('api:bot:bumpedthread-detail', args=(1,))
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 401)
+
+ def test_list_returns_401(self):
+ url = reverse('api:bot:bumpedthread-list')
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 401)
+
+ def test_create_returns_401(self):
+ url = reverse('api:bot:bumpedthread-list')
+ response = self.client.post(url, {"thread_id": 3})
+
+ self.assertEqual(response.status_code, 401)
+
+ def test_delete_returns_401(self):
+ url = reverse('api:bot:bumpedthread-detail', args=(1,))
+ response = self.client.delete(url)
+
+ self.assertEqual(response.status_code, 401)
+
+
+class BumpedThreadAPITests(AuthenticatedAPITestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.thread1 = BumpedThread.objects.create(
+ thread_id=1234,
+ )
+
+ def test_returns_bumped_threads_as_flat_list(self):
+ url = reverse('api:bot:bumpedthread-list')
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), [1234])
+
+ def test_returns_204_for_existing_data(self):
+ url = reverse('api:bot:bumpedthread-detail', args=(1234,))
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 204)
+ self.assertEqual(response.content, b"")
+
+ def test_returns_404_for_non_existing_data(self):
+ url = reverse('api:bot:bumpedthread-detail', args=(42,))
+
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(response.json(), {"detail": "Not found."})
diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py
index b0ab545b..1e564b29 100644
--- a/pydis_site/apps/api/urls.py
+++ b/pydis_site/apps/api/urls.py
@@ -3,7 +3,10 @@ from rest_framework.routers import DefaultRouter
from .views import HealthcheckView, RulesView
from .viewsets import (
+ AocAccountLinkViewSet,
+ AocCompletionistBlockViewSet,
BotSettingViewSet,
+ BumpedThreadViewSet,
DeletedMessageViewSet,
DocumentationLinkViewSet,
FilterListViewSet,
@@ -19,14 +22,22 @@ from .viewsets import (
# https://www.django-rest-framework.org/api-guide/routers/#defaultrouter
bot_router = DefaultRouter(trailing_slash=False)
bot_router.register(
- 'filter-lists',
- FilterListViewSet
+ "aoc-account-links",
+ AocAccountLinkViewSet
+)
+bot_router.register(
+ "aoc-completionist-blocks",
+ AocCompletionistBlockViewSet
)
bot_router.register(
'bot-settings',
BotSettingViewSet
)
bot_router.register(
+ 'bumped-threads',
+ BumpedThreadViewSet
+)
+bot_router.register(
'deleted-messages',
DeletedMessageViewSet
)
@@ -35,6 +46,10 @@ bot_router.register(
DocumentationLinkViewSet
)
bot_router.register(
+ 'filter-lists',
+ FilterListViewSet
+)
+bot_router.register(
'infractions',
InfractionViewSet
)
diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py
index f133e77f..ec52416a 100644
--- a/pydis_site/apps/api/viewsets/__init__.py
+++ b/pydis_site/apps/api/viewsets/__init__.py
@@ -1,12 +1,15 @@
# flake8: noqa
from .bot import (
- FilterListViewSet,
BotSettingViewSet,
+ BumpedThreadViewSet,
DeletedMessageViewSet,
DocumentationLinkViewSet,
+ FilterListViewSet,
InfractionViewSet,
NominationViewSet,
OffensiveMessageViewSet,
+ AocAccountLinkViewSet,
+ AocCompletionistBlockViewSet,
OffTopicChannelNameViewSet,
ReminderViewSet,
RoleViewSet,
diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py
index 84b87eab..262aa59f 100644
--- a/pydis_site/apps/api/viewsets/bot/__init__.py
+++ b/pydis_site/apps/api/viewsets/bot/__init__.py
@@ -1,12 +1,15 @@
# flake8: noqa
from .filter_list import FilterListViewSet
from .bot_setting import BotSettingViewSet
+from .bumped_thread import BumpedThreadViewSet
from .deleted_message import DeletedMessageViewSet
from .documentation_link import DocumentationLinkViewSet
from .infraction import InfractionViewSet
from .nomination import NominationViewSet
from .off_topic_channel_name import OffTopicChannelNameViewSet
from .offensive_message import OffensiveMessageViewSet
+from .aoc_link import AocAccountLinkViewSet
+from .aoc_completionist_block import AocCompletionistBlockViewSet
from .reminder import ReminderViewSet
from .role import RoleViewSet
from .user import UserViewSet
diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py
new file mode 100644
index 00000000..3a4cec60
--- /dev/null
+++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py
@@ -0,0 +1,73 @@
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.mixins import (
+ CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
+)
+from rest_framework.viewsets import GenericViewSet
+
+from pydis_site.apps.api.models.bot import AocCompletionistBlock
+from pydis_site.apps.api.serializers import AocCompletionistBlockSerializer
+
+
+class AocCompletionistBlockViewSet(
+ GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin
+):
+ """
+ View providing management for Users blocked from gettign the AoC completionist Role.
+
+ ## Routes
+
+ ### GET /bot/aoc-completionist-blocks/
+ Returns all the AoC completionist blocks
+
+ #### Response format
+ >>> [
+ ... {
+ ... "user": 2,
+ ... "is_blocked": False,
+ ... "reason": "Too good to be true"
+ ... }
+ ... ]
+
+
+ ### GET /bot/aoc-completionist-blocks/<user__id:int>
+ Retrieve a single Block by User ID
+
+ #### Response format
+ >>>
+ ... {
+ ... "user": 2,
+ ... "is_blocked": False,
+ ... "reason": "Too good to be true"
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned if an AoC completionist block with the given `user__id` was not found.
+
+ ### POST /bot/aoc-completionist-blocks
+ Adds a single AoC completionist block
+
+ #### Request body
+ >>> {
+ ... "user": int,
+ ... "is_blocked": bool,
+ ... "reason": string
+ ... }
+
+ #### Status codes
+ - 204: returned on success
+ - 400: if one of the given fields is invalid
+
+ ### DELETE /bot/aoc-completionist-blocks/<user__id:int>
+ Deletes the AoC Completionist block item with the given `user__id`.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: returned if the AoC Completionist block with the given `user__id` was not found
+
+ """
+
+ serializer_class = AocCompletionistBlockSerializer
+ queryset = AocCompletionistBlock.objects.all()
+ filter_backends = (DjangoFilterBackend,)
+ filter_fields = ("user__id", "is_blocked")
diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py
new file mode 100644
index 00000000..c7a96629
--- /dev/null
+++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py
@@ -0,0 +1,71 @@
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.mixins import (
+ CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin
+)
+from rest_framework.viewsets import GenericViewSet
+
+from pydis_site.apps.api.models.bot import AocAccountLink
+from pydis_site.apps.api.serializers import AocAccountLinkSerializer
+
+
+class AocAccountLinkViewSet(
+ GenericViewSet, CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin
+):
+ """
+ View providing management for Users who linked their AoC accounts to their Discord Account.
+
+ ## Routes
+
+ ### GET /bot/aoc-account-links
+ Returns all the AoC account links
+
+ #### Response format
+ >>> [
+ ... {
+ ... "user": 2,
+ ... "aoc_username": "AoCUser1"
+ ... },
+ ... ...
+ ... ]
+
+
+ ### GET /bot/aoc-account-links/<user__id:int>
+ Retrieve a AoC account link by User ID
+
+ #### Response format
+ >>>
+ ... {
+ ... "user": 2,
+ ... "aoc_username": "AoCUser1"
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned if an AoC account link with the given `user__id` was not found.
+
+ ### POST /bot/aoc-account-links
+ Adds a single AoC account link block
+
+ #### Request body
+ >>> {
+ ... 'user': int,
+ ... 'aoc_username': str
+ ... }
+
+ #### Status codes
+ - 204: returned on success
+ - 400: if one of the given fields was invalid
+
+ ### DELETE /bot/aoc-account-links/<user__id:int>
+ Deletes the AoC account link item with the given `user__id`.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: returned if the AoC account link with the given `user__id` was not found
+
+ """
+
+ serializer_class = AocAccountLinkSerializer
+ queryset = AocAccountLink.objects.all()
+ filter_backends = (DjangoFilterBackend,)
+ filter_fields = ("user__id", "aoc_username")
diff --git a/pydis_site/apps/api/viewsets/bot/bumped_thread.py b/pydis_site/apps/api/viewsets/bot/bumped_thread.py
new file mode 100644
index 00000000..9d77bb6b
--- /dev/null
+++ b/pydis_site/apps/api/viewsets/bot/bumped_thread.py
@@ -0,0 +1,66 @@
+from rest_framework.mixins import (
+ CreateModelMixin, DestroyModelMixin, ListModelMixin
+)
+from rest_framework.request import Request
+from rest_framework.response import Response
+from rest_framework.viewsets import GenericViewSet
+
+from pydis_site.apps.api.models.bot import BumpedThread
+from pydis_site.apps.api.serializers import BumpedThreadSerializer
+
+
+class BumpedThreadViewSet(
+ GenericViewSet, CreateModelMixin, DestroyModelMixin, ListModelMixin
+):
+ """
+ View providing CRUD (Minus the U) operations on threads to be bumped.
+
+ ## Routes
+ ### GET /bot/bumped-threads
+ Returns all BumpedThread items in the database.
+
+ #### Response format
+ >>> list[int]
+
+ #### Status codes
+ - 200: returned on success
+ - 401: returned if unauthenticated
+
+ ### GET /bot/bumped-threads/<thread_id:int>
+ Returns whether a specific BumpedThread exists in the database.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: returned if a BumpedThread with the given thread_id was not found.
+
+ ### POST /bot/bumped-threads
+ Adds a single BumpedThread item to the database.
+
+ #### Request body
+ >>> {
+ ... 'thread_id': int,
+ ... }
+
+ #### Status codes
+ - 201: returned on success
+ - 400: if one of the given fields is invalid
+
+ ### DELETE /bot/bumped-threads/<thread_id:int>
+ Deletes the BumpedThread item with the given `thread_id`.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: if a BumpedThread with the given `thread_id` does not exist
+ """
+
+ serializer_class = BumpedThreadSerializer
+ queryset = BumpedThread.objects.all()
+
+ def retrieve(self, request: Request, *args, **kwargs) -> Response:
+ """
+ DRF method for checking if the given BumpedThread exists.
+
+ Called by the Django Rest Framework in response to the corresponding HTTP request.
+ """
+ self.get_object()
+ return Response(status=204)