From edf047f148a7f6c61c46841c5a15b2d8f9f8be42 Mon Sep 17 00:00:00 2001 From: Corban-Lee Date: Tue, 24 Sep 2024 14:07:47 +0100 Subject: [PATCH] rewrite generating and loading servers --- apps/api/permissions.py | 23 +- apps/api/serializers.py | 9 +- apps/api/urls.py | 3 - apps/api/views.py | 73 +--- apps/home/admin.py | 59 ++- apps/home/urls.py | 3 +- apps/static/js/api.js | 10 + apps/static/js/home/index.js | 24 +- apps/static/js/home/servers.js | 379 ++++++------------ apps/templates/home/includes/servermodal.html | 34 -- apps/templates/home/index.html | 9 +- 11 files changed, 225 insertions(+), 401 deletions(-) delete mode 100644 apps/templates/home/includes/servermodal.html diff --git a/apps/api/permissions.py b/apps/api/permissions.py index 38e8cc2..d86eb08 100644 --- a/apps/api/permissions.py +++ b/apps/api/permissions.py @@ -2,22 +2,19 @@ from rest_framework.permissions import BasePermission +from apps.home.models import r_Server +from apps.authentication.models import ServerMember -class UserHasDiscordPermissions(BasePermission): + +class HasServerAccess(BasePermission): """ - Permission to ensure that the user is permitted to make - changes on behalf of the server they are representing. + An object permission class, the object must have a 'server' attribute. """ + message = "You lack administrator access to this server" + def has_object_permission(self, request, view, obj): + if not hasattr(obj, "server"): + raise Exception(f"obj '{obj}' must have attr 'server'") - -# class SubscriptionServerMember(BasePermission): -# """ -# Permission for each subscription that omits the sub if -# the request user isn't a member of it's server. -# """ - -# def has_object_permission(self, request, view, obj): - -# return obj.server in request.user.servers \ No newline at end of file + return ServerMember.objects.filter(user=request.user, server=obj.server).exists() diff --git a/apps/api/serializers.py b/apps/api/serializers.py index 5c794da..2e766dc 100644 --- a/apps/api/serializers.py +++ b/apps/api/serializers.py @@ -259,14 +259,7 @@ class TrackedContentSerializer_POST(DynamicModelSerializer): fields = ("id", "guid", "title", "url", "subscription", "channel_id", "message_id", "blocked", "creation_datetime") - - - -# rewrite - -class DiscordServerIdSerializer(serializers.Serializer): - server_id = serializers.IntegerField() - +#region rewrite class r_ServerSerializer(DynamicModelSerializer): class Meta: diff --git a/apps/api/urls.py b/apps/api/urls.py index a60552e..9e289b0 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -23,7 +23,6 @@ from .views import ( UniqueContentRule_DetailView, #rewrite - CreateDiscordServerView, r_Server_ListView, r_Server_DetailView, r_ContentFilter_ListView, @@ -90,8 +89,6 @@ urlpatterns = [ #region rewrite - path("discord-servers/", CreateDiscordServerView.as_view()), - path("r_servers/", include([ path("", r_Server_ListView.as_view()), path("/", r_Server_DetailView.as_view()) diff --git a/apps/api/views.py b/apps/api/views.py index 0f31db9..b75c018 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -47,7 +47,6 @@ from .serializers import ( UniqueContentRuleSerializer, #rewrite - DiscordServerIdSerializer, r_ServerSerializer, r_ContentFilterSerializer, r_MessageMutatorSerializer, @@ -56,6 +55,7 @@ from .serializers import ( r_ContentSerializer, r_UniqueContentRuleSerializer ) +from .permissions import HasServerAccess from .errors import NotAMemberError log = logging.getLogger(__name__) @@ -712,74 +712,7 @@ 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: - if raw.get("code") == 0: - log.warning("Failed to get member data, does the oauth url contain the correct permissions?") - - 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"], - owner_id=raw["owner_id"] - ) - - 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(ListView): filterset_fields = [] search_fields = [] ordering_fields = [] @@ -789,7 +722,7 @@ class r_Server_ListView(ListCreateView): # maybe change to ListView only later, 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(DetailView): serializer_class = r_ServerSerializer def get_queryset(self): diff --git a/apps/home/admin.py b/apps/home/admin.py index b75d20b..08d447e 100644 --- a/apps/home/admin.py +++ b/apps/home/admin.py @@ -75,34 +75,79 @@ class GuildSettingsAdmin(admin.ModelAdmin): @admin.register(r_Server) class r_ServerAdmin(admin.ModelAdmin): - pass + list_display = ["id", "name", "icon_hash", "active"] @admin.register(r_ContentFilter) class r_ContentFilterAdmin(admin.ModelAdmin): - pass + list_display = [ + "id", + "server", + "name", + "match", + "matching_algorithm", + "is_insensitive", + "is_whitelist" + ] @admin.register(r_MessageMutator) class r_MessageMutatorAdmin(admin.ModelAdmin): - pass + list_display = [ + "id", + "name", + "value" + ] @admin.register(r_MessageStyle) class r_MessageStyleAdmin(admin.ModelAdmin): - pass + list_display = [ + "id", + "server", + "is_embed", + "is_hyperlinked", + "show_author", + "show_timestamp", + "show_images", + "fetch_images", + "title_mutator", + "description_mutator" + ] @admin.register(r_Subscription) class r_Subscription(admin.ModelAdmin): - pass + list_display = [ + "id", + "server", + "name", + "url", + "created_at", + "updated_at", + "extra_notes", + "active", + "message_style" + ] @admin.register(r_Content) class r_ContentAdmin(admin.ModelAdmin): - pass + list_display = [ + "id", + "subscription", + "item_id", + "item_guid", + "item_url", + "item_title", + "item_content_hash" + ] @admin.register(r_UniqueContentRule) class r_UniqueContentRule(admin.ModelAdmin): - pass + list_display = [ + "id", + "name", + "value" + ] diff --git a/apps/home/urls.py b/apps/home/urls.py index 3b12342..368a017 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -7,6 +7,5 @@ from .views import IndexView, GuildsView urlpatterns = [ path("", login_required(IndexView.as_view()), name="index"), - - path("user-guilds", GuildsView.as_view(), name="user-guilds") + path("generate-servers/", GuildsView.as_view(), name="generate-servers") ] diff --git a/apps/static/js/api.js b/apps/static/js/api.js index 3b8db52..b94656d 100644 --- a/apps/static/js/api.js +++ b/apps/static/js/api.js @@ -183,3 +183,13 @@ async function getUniqueContentRule(ruleId) { return await ajaxRequest(`/api/unique-content-rule/${ruleId}/`, "GET"); } + +//#region rewrite + +async function generateServers() { + return ajaxRequest("/generate-servers/", "GET"); +} + +async function getServers() { + return ajaxRequest("/api/r_servers/", "GET"); +} \ No newline at end of file diff --git a/apps/static/js/home/index.js b/apps/static/js/home/index.js index e150dac..30d2b9d 100644 --- a/apps/static/js/home/index.js +++ b/apps/static/js/home/index.js @@ -5,8 +5,7 @@ $(document).ready(async function() { $("#subscriptionsTab").click(); - await loadSavedGuilds(); - await loadServerOptions(); + await loadServers(); }); $(document).on("selectedServerChange", function() { @@ -189,4 +188,25 @@ function arrayToHtmlList(array, bold=false) { }); return $ul; +} + +function logError(error) { + if (error instanceof Error) { + // Logs typical error properties like message and stack + console.error({ + message: error.message, + stack: error.stack, + name: error.name, + }); + } else if (typeof error === 'object' && error !== null) { + // Try to stringify if it's an object + try { + console.error(JSON.stringify(error, null, 2)); + } catch (stringifyError) { + console.error('Could not stringify the error:', error); + } + } else { + // Fallback for any other types (string, number, etc.) + console.error('Error:', error); + } } \ No newline at end of file diff --git a/apps/static/js/home/servers.js b/apps/static/js/home/servers.js index 1b9e408..c385df2 100644 --- a/apps/static/js/home/servers.js +++ b/apps/static/js/home/servers.js @@ -1,263 +1,178 @@ // #region Loaded Servers -var loadedServers = {}; +var _loadedServers = [] +var selectedServer = null; -// Returns the currently active server, or null if none are active. -function getCurrentlyActiveServer() { - const activeServerAndId = Object.entries(loadedServers).find(([id, server]) => server.currentlyActive); - if (activeServerAndId === undefined) - return null; +function getLoadedServer(options) { + let servers = _loadedServers.filter(item => { - var [id, activeServer] = activeServerAndId; - activeServer.id = id; - - return activeServer; -} - -// Returns the requested server from the provided snowflake id -function getServerFromSnowflake(guildId) { - const serverAndId = Object.entries(loadedServers).find(([id, server]) => server.guild_id == guildId); - if (serverAndId === undefined) - return null; - - var [id, server] = serverAndId; - server.id = id; - - return server; -} - -function addToLoadedServers(server, selectNew=true) { - // Remove the 'id' property and add the 'currentlyActive' property - ({id, ...rest} = server, server = {...rest, currentlyActive: false}) - - // Save the server as loaded - loadedServers[id] = server; - - // Display the loaded server - addServerTemplate(id, sanitise(server.guild_id), sanitise(server.name), sanitise(server.icon), sanitise(server.permissions), sanitise(server.owner)); - - // Select the newly added server - if (selectNew) { - selectServer(id); - } -} - -function removeFromLoadedServers(serverPrimaryKey) { - delete loadedServers[serverPrimaryKey]; - removeServerTemplate(serverPrimaryKey); - - $("#backToSelectServer").click(); -} - -// #endregion - -// #region Server Back Btn - -$("#backToSelectServer").on("click", function() { - $("#noSelectedServer").show(); - $("#selectedServerContainer").hide(); -}); - -// #endregion - -// #region Server Modal - -$("#serverOptionsRefreshBtn").on("click", async function() { - await loadServerOptions(); -}); - -// Load server options into the 'Add Server' dropdown -async function loadServerOptions() { - - // Disable controls while loading - $("#serverOptions").prop("disabled", true); - $("#serverOptionsRefreshBtn").prop("disabled", true).find("i.bi").addClass("spinning-360"); - - // Remove existing options - $("#serverOptions option").each(function() { - if ($(this).val()) { - $(this).remove(); + for (let key in options) { + if (item[key] !== options[key]) { + return false + } } + + return true; }); - // Deselect any selected option - $("#serverOptions").val(null).trigger("change"); + return servers || []; +} - // Fetch and append the server options - try { - const servers = await loadGuilds(); - servers.forEach(server => { - $("#serverOptions").append($("