diff --git a/apps/authentication/admin.py b/apps/authentication/admin.py index 556933e..f1d1773 100644 --- a/apps/authentication/admin.py +++ b/apps/authentication/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin -from .models import DiscordUser +from .models import DiscordUser, ServerMember @admin.register(DiscordUser) @@ -10,3 +10,8 @@ class DiscordUserAdmin(admin.ModelAdmin): list_display = ["id", "username", "global_name", "last_login", "is_staff", "is_superuser", "is_staff"] list_filter = ["is_staff", "is_superuser", "is_active"] + + +@admin.register(ServerMember) +class ServerMemberAdmin(admin.ModelAdmin): + pass diff --git a/apps/authentication/migrations/0005_alter_servermember_nick.py b/apps/authentication/migrations/0005_alter_servermember_nick.py new file mode 100644 index 0000000..2b0955c --- /dev/null +++ b/apps/authentication/migrations/0005_alter_servermember_nick.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-09-23 20:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0004_servermember'), + ] + + operations = [ + migrations.AlterField( + model_name='servermember', + name='nick', + field=models.CharField(blank=True, max_length=32, null=True), + ), + ] diff --git a/apps/authentication/migrations/0006_remove_servermember_nick_servermember_is_owner.py b/apps/authentication/migrations/0006_remove_servermember_nick_servermember_is_owner.py new file mode 100644 index 0000000..bcb5092 --- /dev/null +++ b/apps/authentication/migrations/0006_remove_servermember_nick_servermember_is_owner.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-09-23 20:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0005_alter_servermember_nick'), + ] + + operations = [ + migrations.RemoveField( + model_name='servermember', + name='nick', + ), + migrations.AddField( + model_name='servermember', + name='is_owner', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 0b78fc1..e3c102e 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -169,11 +169,11 @@ class ServerMember(models.Model): 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) + is_owner = models.BooleanField(default=False) def __str__(self): - return f"{self.server.name} · {self.user}({self.nick})" + return f"{self.server.name} · {self.user}" def has_permission(self, flag: int) -> bool: """Check that the member has a givern permission. diff --git a/apps/authentication/views.py b/apps/authentication/views.py index 2b6a928..e2cbc93 100644 --- a/apps/authentication/views.py +++ b/apps/authentication/views.py @@ -49,7 +49,8 @@ class DiscordLoginRedirect(View): discord_user = authenticate(request, discord_user_data=raw_user_data) login(request, discord_user) - return redirect("home:index") + redirect_url = request.GET.get("redirect") or "home:index" + return redirect(redirect_url) def exchange_code(self, code: str) -> dict: """ diff --git a/apps/home/migrations/0027_alter_r_server_icon_hash.py b/apps/home/migrations/0027_alter_r_server_icon_hash.py new file mode 100644 index 0000000..bb0e41e --- /dev/null +++ b/apps/home/migrations/0027_alter_r_server_icon_hash.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-09-23 20:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0026_temp_testonly_migration'), + ] + + operations = [ + migrations.AlterField( + model_name='r_server', + name='icon_hash', + field=models.CharField(blank=True, max_length=128, null=True), + ), + ] diff --git a/apps/home/models.py b/apps/home/models.py index 1d4f7ed..c6f8a49 100644 --- a/apps/home/models.py +++ b/apps/home/models.py @@ -421,8 +421,7 @@ class UniqueContentRule(models.Model): class r_Server(models.Model): id = models.PositiveIntegerField(primary_key=True) name = models.CharField(max_length=128) - icon_hash = models.CharField(max_length=128) - owner_id = models.PositiveBigIntegerField() + icon_hash = models.CharField(max_length=128, blank=True, null=True) active = models.BooleanField(default=True) @property diff --git a/apps/home/urls.py b/apps/home/urls.py index c8aa3c9..5476f11 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -3,8 +3,10 @@ from django.urls import path from django.contrib.auth.decorators import login_required -from .views import IndexView +from .views import IndexView, guilds urlpatterns = [ path("", login_required(IndexView.as_view()), name="index"), + + path("user-guilds", guilds, name="user-guilds") ] diff --git a/apps/home/views.py b/apps/home/views.py index 90f779a..0f35a43 100644 --- a/apps/home/views.py +++ b/apps/home/views.py @@ -1,7 +1,17 @@ # -*- encoding: utf-8 -*- +import httpx + +from django.conf import settings +from django.utils import timezone +from asgiref.sync import sync_to_async +from django.shortcuts import redirect +from django.http import JsonResponse, HttpResponse from django.views.generic import TemplateView +from apps.home.models import r_Server +from apps.authentication.models import DiscordUser, ServerMember + class IndexView(TemplateView): """ @@ -9,3 +19,73 @@ class IndexView(TemplateView): """ template_name = "home/index.html" + + +@sync_to_async +def get_user_access_token(user: DiscordUser) -> str: + return user.access_token + +@sync_to_async +def is_user_authenticated(user: DiscordUser) -> bool: + return user.is_authenticated + +async def delete_server(server_id: int): + # member will be auto deleted via CASCADE, so no need to handle that + + try: + server = await r_Server.objects.aget(id=server_id) + await server.adelete() + except r_Server.DoesNotExist: + pass + +async def setup_server(user: DiscordUser, data: dict) -> dict | None: + is_owner = data["owner"] + permissions = data["permissions"] + admin_perm = 1 << 3 + + # Ignore servers where the user isn't an administrator or owner + if not ((int(permissions) & admin_perm) == admin_perm or is_owner): + await delete_server(data["id"]) + return + + server = await r_Server.objects.aget_or_create( + id=data["id"], + name=data["name"], + icon_hash=data["icon"] + ) + + await ServerMember.objects.aupdate_or_create( + user=user, + server=server[0], + defaults={ + "permissions": permissions, + "is_owner": is_owner + } + ) + + return data + + +async def guilds(request): + if not await is_user_authenticated(request.user): + return redirect("/oauth2/login") + + access_token = await get_user_access_token(request.user) + + async with httpx.AsyncClient() as client: + response = await client.get( + url=f"{settings.DISCORD_API_URL}/users/@me/guilds", + headers={"Authorization": f"Bearer {access_token}"} + ) + guild_data = response.json() + + # guilds where the user either administrates or owns + cleaned_guild_data = [] + + for item in guild_data: + cleaned_data = await setup_server(request.user, item) + if cleaned_data: + cleaned_guild_data.append(cleaned_data) + + + return JsonResponse(guild_data, safe=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2b96f9a..c8d4152 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +anyio==4.6.0 asgiref==3.8.1 bump2version==1.0.1 certifi==2024.2.2 @@ -7,6 +8,9 @@ django-environ==0.11.2 django-filter==24.2 djangorestframework==3.15.1 gunicorn==23.0.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.2 idna==3.7 packaging==24.1 psycopg2==2.9.9