diff --git a/.vscode/settings.json b/.vscode/settings.json index 5b3225f..966bcc8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "TAUTH", + "tcommon", "tmessage" ] } diff --git a/tmessage/settings.py b/tmessage/settings.py index 7132d3f..7ff0c76 100644 --- a/tmessage/settings.py +++ b/tmessage/settings.py @@ -19,6 +19,7 @@ SECRET_KEY = config["services"]["message"]["token"] BASE_DIR = Path(__file__).resolve().parent.parent +STATIC_DIR = BASE_DIR / "tmessage/static" ALLOWED_HOSTS = ["*"] INSTALLED_APPS = [ @@ -74,6 +75,4 @@ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True -STATIC_URL = "/static/" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -STATIC_ROOT = BASE_DIR / "collected-static" diff --git a/tmessage/static/css/messages.css b/tmessage/static/css/messages.css new file mode 100644 index 0000000..d490f7a --- /dev/null +++ b/tmessage/static/css/messages.css @@ -0,0 +1,17 @@ +.message-container { + width: calc(90vw - 40px); + max-width: 400px; + padding: 20px; + margin: 0 auto; + margin-bottom: 30px; + border-radius: 6px; + outline: 1px solid rgb(var(--accent)); + text-align: left; +} + +.message-container textarea { + width: calc(90vw - 52px); + max-width: 388px; + resize: vertical; + height: 100px; +} diff --git a/tmessage/static/js/messages.js b/tmessage/static/js/messages.js new file mode 100644 index 0000000..b08a081 --- /dev/null +++ b/tmessage/static/js/messages.js @@ -0,0 +1,80 @@ +let offset; +let timelineElement = document.getElementById("messages"); + +function escapeHTML(content) { + return content.replaceAll("&", "&").replaceAll("<", ">").replaceAll(">", "<"); +} + +function getMessageHTML(messageJSON, canRespond) { + return `
+
+
${messageJSON.from || "Anonymous"} writes:
+
${escapeHTML(messageJSON.content)}
+
+ ${messageJSON.response ? `
${escapeHTML(messageJSON.response)}
` : ( + canRespond ? ` +
+
+ ` : `No response` + )} +
`; +} + +function _updateURL_replaceElement(element, to) { + let newElement = document.createElement(to); + newElement.dataset.url = element.dataset.url; + newElement.dataset.id = element.dataset.id; + newElement.innerHTML = element.innerHTML; + newElement.setAttribute("href", element.getAttribute("href")); + + if (to == "a") { + newElement.classList.add("not-bold"); + } + + element.replaceWith(newElement); +} + +function updateURL(switchID) { + let switchElement = document.getElementById("switch").querySelector(`[data-id="${switchID}"]`); + + for (const el of document.getElementById("switch").querySelectorAll("b")) { + _updateURL_replaceElement(el, "a"); + } + + _updateURL_replaceElement(switchElement, "b"); + url = switchElement.dataset.url; + fetchMessages(true); +} + +function fetchMessages(fetchFromStart=false) { + document.getElementById("refresh").setAttribute("disabled", ""); + timelineElement.innerHTML = "Loading..." + + fetch(url + (fetchFromStart === true ? "" : `${url.includes("?") ? "&" : "?"}offset=${offset}`)) + .then((response) => (response.json())) + .then((json) => { + if (json.success) { + out = ""; + + if (json.messages.length == 0) { + out = "No messages"; + } else { + for (const message of json.messages) { + out += getMessageHTML(message, json.canRespond); + offset = message.id; + } + } + + timelineElement.innerHTML = out; + } else { + timelineElement.innerText = "Something went wrong!"; + } + }) + .catch((err) => { + timelineElement.innerText = `Couldn't fetch timeline: ${err}`; + document.getElementById("refresh").removeAttribute("disabled"); + }); +} + +fetchMessages(true); +document.getElementById("refresh").addEventListener("click", (() => { fetchMessages(true); })); diff --git a/tmessage/templates/message.html b/tmessage/templates/message.html index 8412010..b996a6d 100644 --- a/tmessage/templates/message.html +++ b/tmessage/templates/message.html @@ -6,7 +6,7 @@
{% csrf_token %} -

+

{% if self_username %} Logged in as {{ self_username }}
diff --git a/tmessage/templates/messages.html b/tmessage/templates/messages.html new file mode 100644 index 0000000..7131295 --- /dev/null +++ b/tmessage/templates/messages.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block head %} + + +{% endblock %} + +{% block body %} +

Your messages

+ Logged in as {{ username }} +
+ +

+ All messages - + Not responded +

+
Loading...
+ +{% endblock %} diff --git a/tmessage/urls.py b/tmessage/urls.py index c916af1..23f60bd 100644 --- a/tmessage/urls.py +++ b/tmessage/urls.py @@ -1,12 +1,15 @@ from django.contrib import admin -from django.urls import path +from django.urls import include, path -from .views import auth, index, message, profile +from .views import auth, get_message_list, index, message, messages, profile urlpatterns = [ path("", index), path("auth/", auth), + path("messages/", messages), path("u//", profile), path("m//", message), + path("api/messages/", get_message_list), + path("static/", include("tmessage.views.static")), path("django-admin/", admin.site.urls) ] diff --git a/tmessage/views/__init__.py b/tmessage/views/__init__.py index 34da215..7da553a 100644 --- a/tmessage/views/__init__.py +++ b/tmessage/views/__init__.py @@ -1 +1,2 @@ -from .templates import auth, index, message, profile # noqa: F401 +from .api import get_message_list # noqa: F401 +from .templates import auth, index, message, messages, profile # noqa: F401 diff --git a/tmessage/views/api.py b/tmessage/views/api.py new file mode 100644 index 0000000..9dd95e5 --- /dev/null +++ b/tmessage/views/api.py @@ -0,0 +1,56 @@ +import json + +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpResponse + +from .helper import get_user_object, get_username + + +def get_message_list(request: WSGIRequest) -> HttpResponse: + username = get_username(request) + if username is None: + return HttpResponse( + json.dumps({ + "success": False + }), + content_type="application/json", + status=401 + ) + + user = get_user_object(username, i_promise_this_user_exists=True) + + queryFilter = {} + + if "offset" in request.GET and (request.GET.get("offset") or "").isdigit(): + queryFilter["message_id__lt"] = int(request.GET.get("offset") or "") + + if "unread" in request.GET: + queryFilter["response"] = None + + if queryFilter: + msgObjects = user.received.filter(**queryFilter) + else: + msgObjects = user.received.all() # type: ignore + + output = [] + messages = msgObjects.order_by("-message_id")[:50].values_list( + "message_id", "content", "response", "anonymous", "u_from" + ) + + for message in messages: + output.append({ + "id": message[0], + "content": message[1], + "response": message[2], + "from": message[4].username if not message[3] and message[4] else None + }) + + return HttpResponse( + json.dumps({ + "success": True, + "canRespond": True, + "messages": output, + "more": msgObjects.count() > 50 + }), + content_type="application/json" + ) diff --git a/tmessage/views/helper.py b/tmessage/views/helper.py index 4cf732d..2efa7c4 100644 --- a/tmessage/views/helper.py +++ b/tmessage/views/helper.py @@ -23,7 +23,7 @@ def render_template( c = { "accent": random.choice(COLORS), "config": config, - "login_token": f"/auth/?sessionid={request.COOKIES.get('sessionid')}" + "login_token": f"/auth/?sessionid={request.COOKIES.get('session_id')}" } for key, val in context.items(): diff --git a/tmessage/views/static.py b/tmessage/views/static.py new file mode 100644 index 0000000..a349ef4 --- /dev/null +++ b/tmessage/views/static.py @@ -0,0 +1,26 @@ +from django.http import HttpResponse +from django.urls import path +from django.views.decorators.cache import cache_control + +from tmessage.settings import STATIC_DIR + + +def get_static_serve(path: str, content_type: str): + def x(request): + return HttpResponse( + open(STATIC_DIR / path, "rb").read(), + content_type=content_type + ) + + x.__name__ = path + return x + +file_associations = { + "js": "text/javascript", + "css": "text/css" +} + +urlpatterns = [path(i, cache_control(**{"max-age": 60 * 60 * 24 * 30})(get_static_serve(i, file_associations[i.split(".")[-1]]))) for i in [ + "js/messages.js", + "css/messages.css" +]] diff --git a/tmessage/views/templates.py b/tmessage/views/templates.py index c3b6604..ea7a57b 100644 --- a/tmessage/views/templates.py +++ b/tmessage/views/templates.py @@ -12,9 +12,9 @@ from .helper import (get_user_object, get_username, render_template, def auth(request: WSGIRequest) -> HttpResponseRedirect: resp = HttpResponseRedirect("/") if "remove" in request.GET: - resp.set_cookie("sessionid", "", max_age=0, expires=datetime(0, 0, 0)) + resp.set_cookie("session_id", "", max_age=0, expires=datetime(0, 0, 0)) else: - resp.set_cookie("sessionid", request.GET.get("sessionid") or "") + resp.set_cookie("session_id", request.GET.get("sessionid") or "") return resp @@ -74,3 +74,14 @@ def message(request: WSGIRequest, username: str) -> HttpResponse: error=error, self_username=get_username(request) ) + +def messages(request: WSGIRequest) -> HttpResponse: + username = get_username(request) + + if username is None: + return HttpResponseRedirect("/") + + return render_template( + request, "messages.html", + username=username + )