user page (finished?!?!)

This commit is contained in:
trinkey 2024-12-24 12:14:38 -05:00
parent 17fc3d6ffe
commit 79379ee7b2
10 changed files with 131 additions and 22 deletions

View file

@ -3,7 +3,7 @@ let timelineElement = document.getElementById("messages");
let moreButton = document.getElementById("more-button"); let moreButton = document.getElementById("more-button");
function escapeHTML(content) { function escapeHTML(content) {
return content.replaceAll("&", "&amp;").replaceAll("<", "&gt;").replaceAll(">", "&lt;"); return content.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
} }
function getMessageHTML(messageJSON, canRespond) { function getMessageHTML(messageJSON, canRespond) {
@ -11,7 +11,7 @@ function getMessageHTML(messageJSON, canRespond) {
el.classList.add("message-container"); el.classList.add("message-container");
el.dataset.messageId = messageJSON.id; el.dataset.messageId = messageJSON.id;
el.innerHTML = ` el.innerHTML = `
<p>Messaging <a href="/u/${messageJSON.to}/">${messageJSON.to}</a>: ${messageJSON.response ? `<small>${timeSince(messageJSON.response_timestamp)}</small>` : ""}</p> <p>Messaging <a href="/u/${messageJSON.to}/">${messageJSON.to}</a>: <small data-timestamp-here>${messageJSON.response ? timeSince(messageJSON.response_timestamp) : ""}</small></p>
<blockquote class="message"> <blockquote class="message">
<div><${messageJSON.from ? "a" : "b"} href="/u/${messageJSON.from}/">${messageJSON.from || "Anony&shy;mous"}</${messageJSON.from ? "a" : "b"}> writes: <small>${timeSince(messageJSON.timestamp)}</small></div> <div><${messageJSON.from ? "a" : "b"} href="/u/${messageJSON.from}/">${messageJSON.from || "Anony&shy;mous"}</${messageJSON.from ? "a" : "b"}> writes: <small>${timeSince(messageJSON.timestamp)}</small></div>
<a class="fake-link" href="/msg/${messageJSON.id}/"><pre class="not-code">${escapeHTML(messageJSON.content)}</pre></a> <a class="fake-link" href="/msg/${messageJSON.id}/"><pre class="not-code">${escapeHTML(messageJSON.content)}</pre></a>
@ -25,7 +25,7 @@ function getMessageHTML(messageJSON, canRespond) {
${messageJSON.response ? "" : `<button class="reply-button" onclick="replyTo(${messageJSON.id})">Reply</button>`} ${messageJSON.response ? "" : `<button class="reply-button" onclick="replyTo(${messageJSON.id})">Reply</button>`}
<button onclick="deleteMessage(${messageJSON.id})">Delete</button> <button onclick="deleteMessage(${messageJSON.id})">Delete</button>
</div> </div>
` : `<i>No response</i>` ` : (messageJSON.response ? "" : `<i>No response</i>`)
}`; }`;
return el return el
@ -48,8 +48,13 @@ function replyTo(messageID) {
.then((response) => (response.json())) .then((response) => (response.json()))
.then((json) => { .then((json) => {
if (json.success) { if (json.success) {
let response = document.createElement("div"); let response = document.createElement("pre");
response.innerHTML = `<small>${timeSince(json.timestamp)}</small><pre class="not-code no-margin">${escapeHTML(json.content)}</pre>`; response.classList.add("not-code");
response.classList.add("no-margin");
response.innerText = json.content;
msgContainer.querySelector("[data-timestamp-here]").innerHTML = timeSince(json.timestamp);
err.innerHTML = ""; err.innerHTML = "";
msgContainer.querySelector("textarea").replaceWith(response); msgContainer.querySelector("textarea").replaceWith(response);

View file

@ -1,5 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Page Not Found - {% endblock %}
{% block body %} {% block body %}
<h1>Hmm. That doesn't look right.</h1> <h1>Hmm. That doesn't look right.</h1>
Make sure the URL is correct and try again. Make sure the URL is correct and try again.

View file

@ -15,7 +15,7 @@
</div> </div>
{% if username %} {% if username %}
Logged in as <a href="/u/{{ username }}/">{{ username }}</b> Logged in as <a href="/u/{{ username }}/">{{ username }}</a>
<p><a href="/messages/">View your messages</a></p> <p><a href="/messages/">View your messages</a></p>
{% else %} {% else %}
Not logged in. Not logged in.

View file

@ -8,7 +8,7 @@
{% csrf_token %} {% csrf_token %}
<p><textarea class="auto-size" name="message" id="message" required maxlength="10000" placeholder="Your message"></textarea></p> <p><textarea class="auto-size" name="message" id="message" required maxlength="10000" placeholder="Your message"></textarea></p>
{% if self_username %} {% if self_username %}
Logged in as <a href="/u/{{ username }}/">{{ username }}</b> Logged in as <a href="/u/{{ username }}/">{{ username }}</a>
<div> <div>
<input name="anonymous" id="anonymous" type="checkbox"> <input name="anonymous" id="anonymous" type="checkbox">
<label data-fake-checkbox for="anonymous">Don't attach your username</label> <label data-fake-checkbox for="anonymous">Don't attach your username</label>

View file

@ -2,7 +2,7 @@
{% block head %} {% block head %}
<link rel="stylesheet" href="/static/css/messages.css"> <link rel="stylesheet" href="/static/css/messages.css">
<script>let url = "/api/messages/";</script> <script>let url = "/api/messages/?unread";</script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
@ -16,11 +16,11 @@
<p><button id="refresh" onclick="fetchMessages(true);">Refresh</button></p> <p><button id="refresh" onclick="fetchMessages(true);">Refresh</button></p>
<p id="switch"> <p id="switch">
<b data-url="/api/messages/" data-id="all" href="javascript:(() => { updateURL('all'); })()">All messages</b> - <a data-url="/api/messages/" data-id="all" class="not-bold" href="javascript:(() => { updateURL('all'); })()">All messages</a> -
<a data-url="/api/messages/?unread" data-id="unread" class="not-bold" href="javascript:(() => { updateURL('unread'); })()">Not responded</a> <b data-url="/api/messages/?unread" data-id="unread" href="javascript:(() => { updateURL('unread'); })()">Not responded</b>
</p> </p>
<div id="messages"><i class="delete-if-more-messages">Loading...</i></div> <div id="messages"><i class="delete-if-more-messages">Loading...</i></div>
<button hidden id="more-button" onclick="fetchMessages(false);">Load more</button> <p><button hidden id="more-button" onclick="fetchMessages(false);">Load more</button><p>
<script src="{{ config.services.common.url.pub }}/static/js/base.js?v={{ config.version_str }}"></script> <script src="{{ config.services.common.url.pub }}/static/js/base.js?v={{ config.version_str }}"></script>
<script src="/static/js/messages.js?v={{ config.version_str }}"></script> <script src="/static/js/messages.js?v={{ config.version_str }}"></script>
<script> <script>

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block head %}
<link rel="stylesheet" href="/static/css/messages.css">
<script>let url = "/api/user/{{ username }}/?resp";</script>
{% endblock %}
{% block body %}
<h1>{{ username }}'s Messages</h1>
<p><a href="/m/{{ username }}">Message {{ username }}</a></p>
<hr>
<p><button id="refresh" onclick="fetchMessages(true);">Refresh</button></p>
<p id="switch">
<a data-url="/api/user/{{ username }}/?all" data-id="all" class="not-bold" href="javascript:(() => { updateURL('all'); })()">All messages</a> -
<b data-url="/api/user/{{ username }}/" data-id="resp" href="javascript:(() => { updateURL('resp'); })()">With response</b>
</p>
<div id="messages"><i class="delete-if-more-messages">Loading...</i></div>
<p><button hidden id="more-button" onclick="fetchMessages(false);">Load more</button><p>
<hr>
{% if self_username %}
<p>Logged in as <a href="/u/{{ self_username }}/">{{ self_username }}</a></p>
<p><a href="/messages/">View your messages</a></p>
{% else %}
Not logged in.
{% if config.new_users %}<a href="{{ config.services.auth.url.pub }}/signup/">Sign up</a>{% else %}<a href="{{ config.services.auth.url.pub }}/login/">Log in</a>{% endif %}?
{% endif %}
<script src="{{ config.services.common.url.pub }}/static/js/base.js?v={{ config.version_str }}"></script>
<script src="/static/js/messages.js?v={{ config.version_str }}"></script>
{% endblock %}

View file

@ -1,8 +1,8 @@
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from .views import (api_messages, auth, index, message, message_page, messages, from .views import (api_messages, api_user, auth, index, message, message_page,
profile) messages, profile)
urlpatterns = [ urlpatterns = [
path("", index), path("", index),
@ -12,6 +12,7 @@ urlpatterns = [
path("m/<str:username>/", message), path("m/<str:username>/", message),
path("msg/<int:message_id>/", message_page), path("msg/<int:message_id>/", message_page),
path("api/messages/", api_messages), path("api/messages/", api_messages),
path("api/user/<str:username>/", api_user),
path("static/", include("tmessage.views.static")), path("static/", include("tmessage.views.static")),
path("django-admin/", admin.site.urls) path("django-admin/", admin.site.urls)
] ]

View file

@ -1,3 +1,3 @@
from .api import api_messages # noqa: F401 from .api import api_messages, api_user # noqa: F401
from .templates import (auth, index, message, message_page, # noqa: F401 from .templates import (auth, index, message, message_page, # noqa: F401
messages, profile) messages, profile)

View file

@ -3,10 +3,11 @@ import math
import time import time
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.db.models import Q
from django.http import HttpResponse from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from tmessage.models import tMMessage from tmessage.models import tMMessage, tMUser
from .helper import get_user_object, get_username from .helper import get_user_object, get_username
@ -18,6 +19,11 @@ def _json_response(data: dict | list, /, *, status: int=200, content_type: str="
content_type=content_type content_type=content_type
) )
def _add_qF(original: None | Q, new: Q) -> Q:
if original is None:
return new
return original | new
RESPONSE_400 = _json_response({ "success": False }, status=400) RESPONSE_400 = _json_response({ "success": False }, status=400)
RESPONSE_401 = _json_response({ "success": False }, status=401) RESPONSE_401 = _json_response({ "success": False }, status=401)
RESPONSE_403 = _json_response({ "success": False }, status=403) RESPONSE_403 = _json_response({ "success": False }, status=403)
@ -35,19 +41,23 @@ def api_messages(request: WSGIRequest) -> HttpResponse:
body = json.loads(request.body) body = json.loads(request.body)
reply = body["content"].strip() reply = body["content"].strip()
message_id = body["id"] message_id = body["id"]
print(reply)
if not (isinstance(message_id, int) and isinstance(reply, str)): if not (isinstance(message_id, int) and isinstance(reply, str) and reply):
print("1")
return RESPONSE_400 return RESPONSE_400
try: try:
message = tMMessage.objects.get(message_id=message_id) message = tMMessage.objects.get(message_id=message_id)
except tMMessage.DoesNotExist: except tMMessage.DoesNotExist:
print("2")
return RESPONSE_400 return RESPONSE_400
if username != message.u_to.username: if username != message.u_to.username:
return RESPONSE_403 return RESPONSE_403
if message.response is not None: if message.response is not None:
print("3")
return RESPONSE_400 return RESPONSE_400
message.response = reply message.response = reply
@ -81,16 +91,16 @@ def api_messages(request: WSGIRequest) -> HttpResponse:
"success": True "success": True
}) })
queryFilter = {} queryFilter = None
if "offset" in request.GET and (request.GET.get("offset") or "").isdigit(): if "offset" in request.GET and (request.GET.get("offset") or "").isdigit():
queryFilter["message_id__lt"] = int(request.GET.get("offset") or "") queryFilter = _add_qF(queryFilter, Q(message_id__lt=int(request.GET.get("offset") or "")))
if "unread" in request.GET: if "unread" in request.GET:
queryFilter["response"] = None queryFilter = _add_qF(queryFilter, Q(response=None))
if queryFilter: if queryFilter:
msgObjects = user.received.filter(**queryFilter) msgObjects = user.received.filter(queryFilter) # type: ignore
else: else:
msgObjects = user.received.all() # type: ignore msgObjects = user.received.all() # type: ignore
@ -116,3 +126,47 @@ def api_messages(request: WSGIRequest) -> HttpResponse:
"messages": output, "messages": output,
"more": msgObjects.count() > 50 "more": msgObjects.count() > 50
}) })
def api_user(request: WSGIRequest, username: str) -> HttpResponse:
queryFilter = {}
try:
user = get_user_object(username)
except tMUser.DoesNotExist:
return RESPONSE_400
queryFilter = None
if "offset" in request.GET and (request.GET.get("offset") or "").isdigit():
queryFilter = _add_qF(queryFilter, Q(message_id__lt=int(request.GET.get("offset") or "")))
if "all" not in request.GET:
queryFilter = _add_qF(queryFilter, ~Q(response=None))
if queryFilter:
msgObjects = user.received.filter(queryFilter) # type: ignore
else:
msgObjects = user.received.all() # type: ignore
output = []
messages = msgObjects.order_by("-sent_timestamp" if "all" in request.GET else "-response_timestamp")[:50].values_list(
"message_id", "content", "response", "anonymous", "u_from", "u_to", "sent_timestamp", "response_timestamp"
)
for message in messages:
output.append({
"id": message[0],
"content": message[1],
"response": message[2],
"from": message[4] if not message[3] and message[4] else None,
"to": message[5],
"timestamp": message[6],
"response_timestamp": message[7]
})
return _json_response({
"success": True,
"canRespond": username == get_username(request),
"messages": output,
"more": msgObjects.count() > 50
})

View file

@ -5,7 +5,7 @@ from datetime import datetime
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from tmessage.models import tMMessage from tmessage.models import tMMessage, tMUser
from .helper import (get_user_object, get_username, render_template, from .helper import (get_user_object, get_username, render_template,
username_exists) username_exists)
@ -36,7 +36,21 @@ def index(request: WSGIRequest) -> HttpResponse:
) )
def profile(request: WSGIRequest, username: str) -> HttpResponse: def profile(request: WSGIRequest, username: str) -> HttpResponse:
... try:
get_user_object(username)
except tMUser.DoesNotExist:
return render_template(
request, "404.html"
)
self_username = get_username(request)
return render_template(
request, "user.html",
title=username,
username=username,
self_username=self_username
)
def message_page(request: WSGIRequest, message_id: int) -> HttpResponse: def message_page(request: WSGIRequest, message_id: int) -> HttpResponse:
try: try: