server setup via api

This commit is contained in:
Corban-Lee Jones 2024-09-20 00:10:27 +01:00
parent 345f70d30f
commit ecfd4a37f3
7 changed files with 182 additions and 44 deletions

3
apps/api/errors.py Normal file
View File

@ -0,0 +1,3 @@
class NotAMemberError(Exception):
pass

View File

@ -264,8 +264,11 @@ class TrackedContentSerializer_POST(DynamicModelSerializer):
# rewrite
class DiscordServerIdSerializer(serializers.Serializer):
server_id = serializers.IntegerField()
class r_ServerSerialiszer(DynamicModelSerializer):
class r_ServerSerializer(DynamicModelSerializer):
class Meta:
model = r_Server
fields = ("id", "name", "icon_hash", "active")
@ -309,6 +312,11 @@ class r_MessageStyleSerializer(DynamicModelSerializer):
class r_SubscriptionSerializer(DynamicModelSerializer):
filters = serializers.PrimaryKeyRelatedField(
queryset=r_ContentFilter.objects.all(),
many=True
)
class Meta:
model = r_Subscription
fields = (
@ -324,6 +332,28 @@ class r_SubscriptionSerializer(DynamicModelSerializer):
"message_style"
)
def validate(self, data):
server = data.get("server") or self.context.get("server")
if not server:
return data
# Prevent using filters from a different server
selected_filters = data.get("filters", [])
valid_filter_ids = r_ContentFilter.objects.filter(server=server).values_list("id", flat=True)
if any(fltr.id not in valid_filter_ids for fltr in selected_filters):
raise serializers.ValidationError(
{"filters": "All filters must belong to the specified server."}
)
# Prevent using message styles from a different server
message_style = data.get("message_style")
if message_style and message_style.server != server:
raise serializers.ValidationError(
{"message_style": "Message style must belong to the specified server."}
)
return data
class r_ContentSerializer(DynamicModelSerializer):
class Meta:

View File

@ -23,6 +23,7 @@ from .views import (
UniqueContentRule_DetailView,
#rewrite
CreateDiscordServerView,
r_Server_ListView,
r_Server_DetailView,
r_ContentFilter_ListView,
@ -86,40 +87,43 @@ urlpatterns = [
path("<int:pk>/", UniqueContentRule_DetailView.as_view(), name="unique-content-rule-detail")
])),
#region rewrite
path("discord-servers/", CreateDiscordServerView.as_view()),
path("r_servers/", include([
path("", r_Server_ListView.as_view()),
path("<int:pk>/", r_Server_DetailView.as_view())
])),
# path("r_content-filters/", include([
# path(""),
# path("<int:pk>/")
# ])),
path("r_content-filters/", include([
path("", r_ContentFilter_ListView.as_view()),
path("<int:pk>/", r_ContentFilter_DetailView.as_view())
])),
# path("r_message-mutators/", include([
# path(""),
# path("<int:pk>/")
# ])),
path("r_message-mutators/", include([
path("", r_MessageMutator_ListView.as_view()),
path("<int:pk>/", r_MessageMutator_DetailView.as_view())
])),
# path("r_message-styles/", include([
# path(""),
# path("<int:pk>/")
# ])),
path("r_message-styles/", include([
path("", r_MessageStyle_ListView.as_view()),
path("<int:pk>/", r_MessageStyle_DetailView.as_view())
])),
# path("r_subscriptions/", include([
# path(""),
# path("<int:pk>/")
# ])),
path("r_subscriptions/", include([
path("", r_Subscription_ListView.as_view()),
path("<int:pk>/", r_Subscription_DetailView.as_view())
])),
# path("r_content/", include([
# path(""),
# path("<int:pk>/")
# ])),
path("r_content/", include([
path("", r_Content_ListView.as_view()),
path("<int:pk>/", r_Content_DetailView.as_view())
])),
# path("r_unique-content-rules/", include([
# path(""),
# path("<int:pk>/")
# ]))
path("r_unique-content-rules/", include([
path("", r_UniqueContentRule_ListView.as_view()),
path("<int:pk>/", r_UniqueContentRule_DetailView.as_view())
]))
]

View File

@ -1,7 +1,9 @@
# -*- encoding: utf-8 -*-
import logging
import requests
from django.conf import settings
from django.db.models import Subquery
from django.db.utils import IntegrityError
from django_filters import rest_framework as rest_filters
@ -30,7 +32,7 @@ from apps.home.models import (
r_Content,
r_UniqueContentRule
)
from apps.authentication.models import DiscordUser
from apps.authentication.models import DiscordUser, ServerMember
from .metadata import ExpandedMetadata
from .serializers import (
SubChannelSerializer,
@ -45,7 +47,8 @@ from .serializers import (
UniqueContentRuleSerializer,
#rewrite
r_ServerSerialiszer,
DiscordServerIdSerializer,
r_ServerSerializer,
r_ContentFilterSerializer,
r_MessageMutatorSerializer,
r_MessageStyleSerializer,
@ -53,6 +56,7 @@ from .serializers import (
r_ContentSerializer,
r_UniqueContentRuleSerializer
)
from .errors import NotAMemberError
log = logging.getLogger(__name__)
@ -708,18 +712,81 @@ class DeletableDetailView(generics.RetrieveDestroyAPIView):
parser_classes = [MultiPartParser, FormParser]
class CreateDiscordServerView(generics.CreateAPIView):
serializer_class = DiscordServerIdSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
return Response()
server_id = serializer.validated_data["server_id"]
response = requests.get(
url=f"{settings.DISCORD_API_URL}/guilds/{server_id}",
headers={"Authorization": f"Bot {settings.BOT_TOKEN}"}
)
raw = response.json()
if not response.status_code == 200:
return Response(
status=response.status_code,
data=raw
)
server = r_Server.objects.filter(id=server_id)
if server.exists():
return self.create_member_for_server(server.first(), request.user)
else:
return self.create_server(raw, request.user)
def create_member_for_server(self, server: r_Server, user: DiscordUser) -> Response:
response = requests.get(
url=f"{settings.DISCORD_API_URL}/users/@me/guilds/{server.id}/member", # TODO: continue here
headers={"Authorization": f"Bearer {user.access_token}"} # the scope of the token doesnt cover membership, so
) # this needs to be updated against the bot from the
raw = response.json() # discord developers dashboard.
if response.status_code != 200:
return Response(
status=response.status_code,
data=raw
)
# TODO: might need to a case where this member already exists
ServerMember.objects.get_or_create(
server=server,
user=user,
nick=raw.get("nick"),
permissions=raw["permissions"]
)
return Response(
status=200,
data={"message": "success"}
)
def create_server(self, raw: dict, user: DiscordUser) -> Response:
server = r_Server.objects.create(
id=raw["id"],
name=raw["name"],
icon_hash=raw["icon"]
)
return self.create_member_for_server(server, user)
class r_Server_ListView(ListCreateView): # maybe change to ListView only later, and create through secure backend means?
filterset_fields = []
search_fields = []
ordering_fields = []
serializer_class = r_ServerSerialiszer
serializer_class = r_ServerSerializer
def get_queryset(self):
return r_Server.objects.all()
class r_Server_DetailView(ChangableDetailView): # maybe change to ListView only later, and create through secure backend means?
serializer_class = r_ServerSerialiszer
serializer_class = r_ServerSerializer
def get_queryset(self):
return r_Server.objects.all()
@ -742,7 +809,7 @@ class r_ContentFilter_DetailView(ChangableDetailView):
return r_ContentFilter.objects.all()
class r_MessageMutator_ListView(): # instances of this one are pre-defined ONLY
class r_MessageMutator_ListView(ListView): # instances of this one are pre-defined ONLY
filterset_fields = []
search_fields = []
ordering_fields = []

View File

@ -0,0 +1,26 @@
# Generated by Django 5.0.4 on 2024-09-19 20:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0003_discorduser_refresh_token'),
('home', '0026_temp_testonly_migration'),
]
operations = [
migrations.CreateModel(
name='ServerMember',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('nick', models.CharField(max_length=32)),
('permissions', models.CharField(max_length=32)),
('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.r_server')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -159,3 +159,25 @@ class DiscordUser(PermissionsMixin):
self.refresh_token=raw["refresh_token"]
self.save(force_update=True)
class ServerMember(models.Model):
"""
A link table, connecting users to servers, and storing their server-specific data.
"""
id = models.AutoField(primary_key=True)
user = models.ForeignKey(to=DiscordUser, on_delete=models.CASCADE)
server = models.ForeignKey(to="home.r_Server", on_delete=models.CASCADE)
nick = models.CharField(max_length=32, null=True, blank=True)
permissions = models.CharField(max_length=32)
def __str__(self):
return f"{self.server.name} · {self.user}({self.nick})"
def has_permission(self, flag: int) -> bool:
"""Check that the member has a givern permission.
https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
"""
permissions_int = int(self.permissions)
return (permissions_int & flag) == flag

View File

@ -517,20 +517,6 @@ class r_Subscription(models.Model):
filters = models.ManyToManyField(to=r_ContentFilter, blank=True)
message_style = models.ForeignKey(to=r_MessageStyle, on_delete=models.SET_NULL, null=True, blank=True)
def clean(self):
if self.message_style and self.message_style.server != self.server:
raise ValidationError("Cannot use any Message Style of another server.")
if any(fltr.server != self.server for fltr in self.filters.all()):
raise ValidationError("Cannot use any Content Filter of another server.")
def save(self, *args, **kwargs):
if not self.pk:
super().save(*args, **kwargs)
self.clean()
super().save(*args, **kwargs)
def __str__(self):
return self.name