diff --git a/app/urls.py b/app/urls.py index f1b38b2..b1fc77f 100644 --- a/app/urls.py +++ b/app/urls.py @@ -19,6 +19,12 @@ from django.contrib import admin from django.urls import include, path from django.views.generic import TemplateView +from core.api.views.threshold import ( + ThresholdChans, + ThresholdInfoModal, + ThresholdOnline, + ThresholdUsers, +) from core.ui.views.drilldown import Drilldown from core.views import Billing, Cancel, Home, Order, Portal, Signup from core.views.callbacks import Callback @@ -44,5 +50,9 @@ urlpatterns = [ path("accounts/signup/", Signup.as_view(), name="signup"), path("ui/drilldown/", Drilldown.as_view(), name="drilldown"), path("parts/search/", Search.as_view(), name="search"), + path("modal/info/", ThresholdInfoModal.as_view(), name="modal_info"), + path("api/chans/", ThresholdChans.as_view(), name="chans"), + path("api/users/", ThresholdUsers.as_view(), name="users"), + path("api/online/", ThresholdOnline.as_view(), name="online"), path("api/search/", APISearch.as_view(), name="api_search"), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/api/views/__init__.py b/core/api/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/api/views/threshold.py b/core/api/views/threshold.py new file mode 100644 index 0000000..03a8612 --- /dev/null +++ b/core/api/views/threshold.py @@ -0,0 +1,106 @@ +import logging + +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http import HttpResponse, JsonResponse +from django.shortcuts import render +from rest_framework.parsers import FormParser +from rest_framework.views import APIView + +from core.lib.threshold import annotate_online, get_chans, get_users + +logger = logging.getLogger(__name__) + + +class ThresholdChans(LoginRequiredMixin, APIView): + parser_classes = [FormParser] + plan_name = "drilldown" + + def post(self, request): + if not request.user.has_plan(self.plan_name): + return JsonResponse({"success": False}) + if "net" not in request.data: + return JsonResponse({"success": False}) + if "query" not in request.data: + return JsonResponse({"success": False}) + net = request.data["net"] + query = request.data["query"] + channels = get_chans(net, [query]) + if not channels: + return HttpResponse("") + channels_human = ", ".join(channels) + return HttpResponse(channels_human) + + +class ThresholdUsers(LoginRequiredMixin, APIView): + parser_classes = [FormParser] + plan_name = "drilldown" + + def post(self, request): + if not request.user.has_plan(self.plan_name): + return JsonResponse({"success": False}) + if "net" not in request.data: + return JsonResponse({"success": False}) + if "query" not in request.data: + return JsonResponse({"success": False}) + net = request.data["net"] + query = request.data["query"] + users = get_users(net, [query]) + if not users: + return HttpResponse("") + users_human = ", ".join(users) + return HttpResponse(users_human) + + +class ThresholdOnline(LoginRequiredMixin, APIView): + parser_classes = [FormParser] + plan_name = "drilldown" + + def post(self, request): + if not request.user.has_plan(self.plan_name): + return JsonResponse({"success": False}) + if "net" not in request.data: + return JsonResponse({"success": False}) + if "query" not in request.data: + return JsonResponse({"success": False}) + net = request.data["net"] + query = request.data["query"] + online_info = annotate_online(net, query) + return JsonResponse(online_info) + + +class ThresholdInfoModal(LoginRequiredMixin, APIView): + parser_classes = [FormParser] + plan_name = "drilldown" + template_name = "modals/info.html" + + def post(self, request): + if not request.user.has_plan(self.plan_name): + return JsonResponse({"success": False}) + if "net" not in request.data: + return JsonResponse({"success": False}) + if "nick" not in request.data: + return JsonResponse({"success": False}) + if "channel" not in request.data: + return JsonResponse({"success": False}) + net = request.data["net"] + nick = request.data["nick"] + channel = request.data["channel"] + channels = get_chans(net, [nick]) + users = get_users(net, [channel]) + if channels: + inter_users = get_users(net, channels) + else: + inter_users = [] + if users: + inter_chans = get_chans(net, users) + else: + inter_chans = [] + context = { + "nick": nick, + "channel": channel, + "chans": channels, + "users": users, + "inter_chans": inter_chans, + "inter_users": inter_users, + } + return render(request, self.template_name, context) diff --git a/core/lib/threshold.py b/core/lib/threshold.py new file mode 100644 index 0000000..51ac704 --- /dev/null +++ b/core/lib/threshold.py @@ -0,0 +1,76 @@ +import logging +from json import dumps + +import requests +from django.conf import settings +from requests.exceptions import JSONDecodeError + +logger = logging.getLogger(__name__) + + +def escape(obj): + chars = ["[", "]", "^", "-", "*", "?"] + if isinstance(obj, str): + obj = obj.replace("\\", "\\\\") + for i in chars: + obj = obj.replace(i, "\\" + i) + elif isinstance(obj, list): + for i in obj: + i = escape(i) + elif isinstance(obj, dict): + for key in obj: + obj[key] = escape(obj[key]) + return obj + + +def threshold_request(url, data): + headers = { + "ApiKey": settings.THRESHOLD_API_KEY, + "Token": settings.THRESHOLD_API_TOKEN, + } + for key in data: + data[key] = escape(data[key]) + r = requests.post( + f"{settings.THRESHOLD_ENDPOINT}/{url}/", data=dumps(data), headers=headers + ) + if not r.headers.get("Counter") == settings.THRESHOLD_API_COUNTER: + logger.error( + ( + f"Threshold API counter mismatch: " + f"{r.headers.get('Counter')} != " + f"{settings.THRESHOLD_API_COUNTER}" + ) + ) + return False + try: + response = r.json() + except JSONDecodeError: + logging.error(f"Invalid JSON response: {r.text}") + return response + + +def get_chans(net, query): + url = "chans" + payload = {"net": net, "query": query} + channels = threshold_request(url, payload) + if not channels: + return [] + return channels["chans"] + + +def get_users(net, query): + url = "users" + payload = {"net": net, "query": query} + users = threshold_request(url, payload) + if not users: + return [] + return users["users"] + + +def annotate_online(net, query): + url = "online" + payload = {"net": net, "query": query} + online_info = threshold_request(url, payload) + if not online_info: + return {} + return online_info diff --git a/core/templates/base.html b/core/templates/base.html index 61086a0..9c21169 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -9,6 +9,7 @@