server setup via api
This commit is contained in:
parent
345f70d30f
commit
ecfd4a37f3
3
apps/api/errors.py
Normal file
3
apps/api/errors.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
class NotAMemberError(Exception):
|
||||||
|
pass
|
@ -264,8 +264,11 @@ class TrackedContentSerializer_POST(DynamicModelSerializer):
|
|||||||
|
|
||||||
# rewrite
|
# rewrite
|
||||||
|
|
||||||
|
class DiscordServerIdSerializer(serializers.Serializer):
|
||||||
|
server_id = serializers.IntegerField()
|
||||||
|
|
||||||
class r_ServerSerialiszer(DynamicModelSerializer):
|
|
||||||
|
class r_ServerSerializer(DynamicModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = r_Server
|
model = r_Server
|
||||||
fields = ("id", "name", "icon_hash", "active")
|
fields = ("id", "name", "icon_hash", "active")
|
||||||
@ -309,6 +312,11 @@ class r_MessageStyleSerializer(DynamicModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class r_SubscriptionSerializer(DynamicModelSerializer):
|
class r_SubscriptionSerializer(DynamicModelSerializer):
|
||||||
|
filters = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=r_ContentFilter.objects.all(),
|
||||||
|
many=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = r_Subscription
|
model = r_Subscription
|
||||||
fields = (
|
fields = (
|
||||||
@ -324,6 +332,28 @@ class r_SubscriptionSerializer(DynamicModelSerializer):
|
|||||||
"message_style"
|
"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 r_ContentSerializer(DynamicModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -23,6 +23,7 @@ from .views import (
|
|||||||
UniqueContentRule_DetailView,
|
UniqueContentRule_DetailView,
|
||||||
|
|
||||||
#rewrite
|
#rewrite
|
||||||
|
CreateDiscordServerView,
|
||||||
r_Server_ListView,
|
r_Server_ListView,
|
||||||
r_Server_DetailView,
|
r_Server_DetailView,
|
||||||
r_ContentFilter_ListView,
|
r_ContentFilter_ListView,
|
||||||
@ -86,40 +87,43 @@ urlpatterns = [
|
|||||||
path("<int:pk>/", UniqueContentRule_DetailView.as_view(), name="unique-content-rule-detail")
|
path("<int:pk>/", UniqueContentRule_DetailView.as_view(), name="unique-content-rule-detail")
|
||||||
])),
|
])),
|
||||||
|
|
||||||
|
|
||||||
#region rewrite
|
#region rewrite
|
||||||
|
|
||||||
|
path("discord-servers/", CreateDiscordServerView.as_view()),
|
||||||
|
|
||||||
path("r_servers/", include([
|
path("r_servers/", include([
|
||||||
path("", r_Server_ListView.as_view()),
|
path("", r_Server_ListView.as_view()),
|
||||||
path("<int:pk>/", r_Server_DetailView.as_view())
|
path("<int:pk>/", r_Server_DetailView.as_view())
|
||||||
])),
|
])),
|
||||||
|
|
||||||
# path("r_content-filters/", include([
|
path("r_content-filters/", include([
|
||||||
# path(""),
|
path("", r_ContentFilter_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_ContentFilter_DetailView.as_view())
|
||||||
# ])),
|
])),
|
||||||
|
|
||||||
# path("r_message-mutators/", include([
|
path("r_message-mutators/", include([
|
||||||
# path(""),
|
path("", r_MessageMutator_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_MessageMutator_DetailView.as_view())
|
||||||
# ])),
|
])),
|
||||||
|
|
||||||
# path("r_message-styles/", include([
|
path("r_message-styles/", include([
|
||||||
# path(""),
|
path("", r_MessageStyle_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_MessageStyle_DetailView.as_view())
|
||||||
# ])),
|
])),
|
||||||
|
|
||||||
# path("r_subscriptions/", include([
|
path("r_subscriptions/", include([
|
||||||
# path(""),
|
path("", r_Subscription_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_Subscription_DetailView.as_view())
|
||||||
# ])),
|
])),
|
||||||
|
|
||||||
# path("r_content/", include([
|
path("r_content/", include([
|
||||||
# path(""),
|
path("", r_Content_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_Content_DetailView.as_view())
|
||||||
# ])),
|
])),
|
||||||
|
|
||||||
# path("r_unique-content-rules/", include([
|
path("r_unique-content-rules/", include([
|
||||||
# path(""),
|
path("", r_UniqueContentRule_ListView.as_view()),
|
||||||
# path("<int:pk>/")
|
path("<int:pk>/", r_UniqueContentRule_DetailView.as_view())
|
||||||
# ]))
|
]))
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import Subquery
|
from django.db.models import Subquery
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
from django_filters import rest_framework as rest_filters
|
from django_filters import rest_framework as rest_filters
|
||||||
@ -30,7 +32,7 @@ from apps.home.models import (
|
|||||||
r_Content,
|
r_Content,
|
||||||
r_UniqueContentRule
|
r_UniqueContentRule
|
||||||
)
|
)
|
||||||
from apps.authentication.models import DiscordUser
|
from apps.authentication.models import DiscordUser, ServerMember
|
||||||
from .metadata import ExpandedMetadata
|
from .metadata import ExpandedMetadata
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
SubChannelSerializer,
|
SubChannelSerializer,
|
||||||
@ -45,7 +47,8 @@ from .serializers import (
|
|||||||
UniqueContentRuleSerializer,
|
UniqueContentRuleSerializer,
|
||||||
|
|
||||||
#rewrite
|
#rewrite
|
||||||
r_ServerSerialiszer,
|
DiscordServerIdSerializer,
|
||||||
|
r_ServerSerializer,
|
||||||
r_ContentFilterSerializer,
|
r_ContentFilterSerializer,
|
||||||
r_MessageMutatorSerializer,
|
r_MessageMutatorSerializer,
|
||||||
r_MessageStyleSerializer,
|
r_MessageStyleSerializer,
|
||||||
@ -53,6 +56,7 @@ from .serializers import (
|
|||||||
r_ContentSerializer,
|
r_ContentSerializer,
|
||||||
r_UniqueContentRuleSerializer
|
r_UniqueContentRuleSerializer
|
||||||
)
|
)
|
||||||
|
from .errors import NotAMemberError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -708,18 +712,81 @@ class DeletableDetailView(generics.RetrieveDestroyAPIView):
|
|||||||
parser_classes = [MultiPartParser, FormParser]
|
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?
|
class r_Server_ListView(ListCreateView): # maybe change to ListView only later, and create through secure backend means?
|
||||||
filterset_fields = []
|
filterset_fields = []
|
||||||
search_fields = []
|
search_fields = []
|
||||||
ordering_fields = []
|
ordering_fields = []
|
||||||
serializer_class = r_ServerSerialiszer
|
serializer_class = r_ServerSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return r_Server.objects.all()
|
return r_Server.objects.all()
|
||||||
|
|
||||||
|
|
||||||
class r_Server_DetailView(ChangableDetailView): # maybe change to ListView only later, and create through secure backend means?
|
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):
|
def get_queryset(self):
|
||||||
return r_Server.objects.all()
|
return r_Server.objects.all()
|
||||||
@ -742,7 +809,7 @@ class r_ContentFilter_DetailView(ChangableDetailView):
|
|||||||
return r_ContentFilter.objects.all()
|
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 = []
|
filterset_fields = []
|
||||||
search_fields = []
|
search_fields = []
|
||||||
ordering_fields = []
|
ordering_fields = []
|
||||||
|
26
apps/authentication/migrations/0004_servermember.py
Normal file
26
apps/authentication/migrations/0004_servermember.py
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -159,3 +159,25 @@ class DiscordUser(PermissionsMixin):
|
|||||||
self.refresh_token=raw["refresh_token"]
|
self.refresh_token=raw["refresh_token"]
|
||||||
|
|
||||||
self.save(force_update=True)
|
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
|
||||||
|
@ -517,20 +517,6 @@ class r_Subscription(models.Model):
|
|||||||
filters = models.ManyToManyField(to=r_ContentFilter, blank=True)
|
filters = models.ManyToManyField(to=r_ContentFilter, blank=True)
|
||||||
message_style = models.ForeignKey(to=r_MessageStyle, on_delete=models.SET_NULL, null=True, 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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user