From 103998c0e63bedc9157f0ed39fcbfcc5e921e39d Mon Sep 17 00:00:00 2001
From: trinkey
Date: Mon, 23 Dec 2024 13:21:56 -0500
Subject: [PATCH] you can send messages
---
tmessage/apps.py | 6 ++++
tmessage/migrations/0001_initial.py | 56 +++++++++++++++++++++++++++++
tmessage/migrations/__init__.py | 0
tmessage/models.py | 48 +++++++++++++++++++++++++
tmessage/settings.py | 7 ++--
tmessage/templates/index.html | 24 +++++++++++++
tmessage/templates/message.html | 22 ++++++++++++
tmessage/views/helper.py | 30 ++++++++++++++--
tmessage/views/templates.py | 55 ++++++++++++++++++++++++----
9 files changed, 236 insertions(+), 12 deletions(-)
create mode 100644 tmessage/apps.py
create mode 100644 tmessage/migrations/0001_initial.py
create mode 100644 tmessage/migrations/__init__.py
create mode 100644 tmessage/models.py
create mode 100644 tmessage/templates/index.html
create mode 100644 tmessage/templates/message.html
diff --git a/tmessage/apps.py b/tmessage/apps.py
new file mode 100644
index 0000000..b69a924
--- /dev/null
+++ b/tmessage/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class DBConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "tmessage"
diff --git a/tmessage/migrations/0001_initial.py b/tmessage/migrations/0001_initial.py
new file mode 100644
index 0000000..539b51a
--- /dev/null
+++ b/tmessage/migrations/0001_initial.py
@@ -0,0 +1,56 @@
+# Generated by Django 5.0.7 on 2024-12-23 15:14
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='tMMessage',
+ fields=[
+ ('message_id', models.IntegerField(primary_key=True, serialize=False)),
+ ('content', models.CharField(max_length=10000)),
+ ('response', models.CharField(blank=True, max_length=10000, null=True)),
+ ('anonymous', models.BooleanField()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='tMUser',
+ fields=[
+ ('username', models.CharField(max_length=30, primary_key=True, serialize=False, unique=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='tMM2MLike',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='likes_obj', to='tmessage.tmmessage')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='liked_obj', to='tmessage.tmuser')),
+ ],
+ options={
+ 'unique_together': {('user', 'message')},
+ },
+ ),
+ migrations.AddField(
+ model_name='tmmessage',
+ name='likes',
+ field=models.ManyToManyField(related_name='liked', through='tmessage.tMM2MLike', to='tmessage.tmuser'),
+ ),
+ migrations.AddField(
+ model_name='tmmessage',
+ name='u_from',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sent', to='tmessage.tmuser'),
+ ),
+ migrations.AddField(
+ model_name='tmmessage',
+ name='u_to',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received', to='tmessage.tmuser'),
+ ),
+ ]
diff --git a/tmessage/migrations/__init__.py b/tmessage/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tmessage/models.py b/tmessage/models.py
new file mode 100644
index 0000000..682be24
--- /dev/null
+++ b/tmessage/models.py
@@ -0,0 +1,48 @@
+from typing import TYPE_CHECKING
+
+from django.contrib import admin as django_admin
+from django.contrib.admin.exceptions import AlreadyRegistered # type: ignore
+from django.db import models
+
+
+class tMUser(models.Model):
+ username = models.CharField(max_length=30, unique=True, primary_key=True)
+
+ if TYPE_CHECKING:
+ liked = models.Manager["tMMessage"]
+ sent = models.Manager["tMMessage"]
+ received = models.Manager["tMMessage"]
+
+ def __str__(self) -> str:
+ return self.username
+
+class tMMessage(models.Model):
+ message_id = models.IntegerField(primary_key=True)
+ content = models.CharField(max_length=10_000)
+ response = models.CharField(max_length=10_000, blank=True, null=True)
+ anonymous = models.BooleanField()
+ likes = models.ManyToManyField(tMUser, through="tMM2MLike", related_name="liked")
+ u_to = models.ForeignKey(tMUser, on_delete=models.CASCADE, related_name="received")
+
+ if TYPE_CHECKING:
+ u_from: tMUser | None
+ else:
+ u_from = models.ForeignKey(tMUser, on_delete=models.SET_NULL, blank=True, null=True, related_name="sent")
+
+ def __str__(self) -> str:
+ return f"({self.message_id}) {self.u_from.username if self.u_from else 'Anonymous'} messaged {self.u_to.username}"
+
+class tMM2MLike(models.Model):
+ user = models.ForeignKey(tMUser, on_delete=models.CASCADE, related_name="liked_obj")
+ message = models.ForeignKey(tMMessage, on_delete=models.CASCADE, related_name="likes_obj")
+
+ class Meta:
+ unique_together = ("user", "message")
+
+ def __str__(self) -> str:
+ return f"{self.user.username} liked message {self.message.message_id}"
+
+try:
+ django_admin.site.register((tMUser, tMMessage, tMM2MLike))
+except AlreadyRegistered:
+ ...
diff --git a/tmessage/settings.py b/tmessage/settings.py
index a3cf9f4..7132d3f 100644
--- a/tmessage/settings.py
+++ b/tmessage/settings.py
@@ -26,7 +26,8 @@ INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
- "django.contrib.messages"
+ "django.contrib.messages",
+ "tmessage.apps.DBConfig"
]
MIDDLEWARE = [
@@ -69,10 +70,10 @@ DATABASES = {
}
AUTH_PASSWORD_VALIDATORS = []
-
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/templates/index.html b/tmessage/templates/index.html
new file mode 100644
index 0000000..2326b0c
--- /dev/null
+++ b/tmessage/templates/index.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% block body %}
+ Hey there, {{ username }}!
+
+ Read your messages ({{ new }} new)
+
+
Your message link: {{ config.services.message.url.pub }}/m/{{ username }}/
+ (click to copy)
+
+
+
+ Log out -
+ Other services
+
+
+
+{% endblock %}
diff --git a/tmessage/templates/message.html b/tmessage/templates/message.html
new file mode 100644
index 0000000..8412010
--- /dev/null
+++ b/tmessage/templates/message.html
@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+
+{% block body %}
+ Send a message to {{ username }}
+ {{ error }}
+
+
+{% endblock %}
diff --git a/tmessage/views/helper.py b/tmessage/views/helper.py
index 85d2bcb..4cf732d 100644
--- a/tmessage/views/helper.py
+++ b/tmessage/views/helper.py
@@ -6,6 +6,7 @@ from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django.template import loader
+from tmessage.models import tMUser
from tmessage.settings import config
COLORS = ["rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach", "yellow", "green", "teal", "sky", "sapphire", "blue", "lavender"]
@@ -21,7 +22,8 @@ def render_template(
) -> HttpResponse:
c = {
"accent": random.choice(COLORS),
- "config": config
+ "config": config,
+ "login_token": f"/auth/?sessionid={request.COOKIES.get('sessionid')}"
}
for key, val in context.items():
@@ -38,6 +40,28 @@ def render_template(
return resp
-def is_logged_in(request: WSGIRequest) -> dict | None:
+def get_username(request: WSGIRequest) -> str | None:
resp = requests.get(config["services"]["auth"]["url"]["int"] + f"/api/authenticated/?token={url_escape(config['services']['message']['token'])}&service=message", cookies={**request.COOKIES}).json()
- return resp["success"] and resp["auth"]
+
+ if not resp["success"]:
+ raise Exception("Unable to communicate with tAuth")
+
+ return resp["username"]
+
+def username_exists(username: str) -> bool:
+ resp = requests.get(config["services"]["auth"]["url"]["int"] + f"/api/username/?token={url_escape(config['services']['message']['token'])}&service=message&username={url_escape(username)}").json()
+
+ if not resp["success"]:
+ raise Exception("Unable to communicate with tAuth")
+
+ return resp["exists"]
+
+def get_user_object(username: str, *, i_promise_this_user_exists: bool=False) -> tMUser:
+ if i_promise_this_user_exists or username_exists(username):
+ try:
+ return tMUser.objects.get(username=username)
+ except tMUser.DoesNotExist:
+ return tMUser.objects.create(username=username)
+
+ else:
+ raise tMUser.DoesNotExist(f"tAuth doesn't know who {username} is")
diff --git a/tmessage/views/templates.py b/tmessage/views/templates.py
index 1733ef5..c3b6604 100644
--- a/tmessage/views/templates.py
+++ b/tmessage/views/templates.py
@@ -3,7 +3,10 @@ from datetime import datetime
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse, HttpResponseRedirect
-from .helper import is_logged_in, render_template
+from tmessage.models import tMMessage
+
+from .helper import (get_user_object, get_username, render_template,
+ username_exists)
def auth(request: WSGIRequest) -> HttpResponseRedirect:
@@ -16,8 +19,15 @@ def auth(request: WSGIRequest) -> HttpResponseRedirect:
return resp
def index(request: WSGIRequest) -> HttpResponse:
- if is_logged_in(request):
- return dashboard(request)
+ username = get_username(request)
+ if username:
+ user = get_user_object(username, i_promise_this_user_exists=True)
+
+ return render_template(
+ request, "index.html",
+ username=username,
+ new=user.received.filter(response=None).count() # type: ignore
+ )
return render_template(
request, "noauth/index.html"
@@ -27,7 +37,40 @@ def profile(request: WSGIRequest, username: str) -> HttpResponse:
...
def message(request: WSGIRequest, username: str) -> HttpResponse:
- ...
+ if not username_exists(username):
+ return render_template(
+ request, "404.html"
+ )
-def dashboard(request: WSGIRequest) -> HttpResponse:
- return render_template(request, "base.html")
+ error = ""
+ if request.method == "POST":
+ content = (request.POST.get("message") or "").strip()
+ if len(content) > 10000:
+ error = "Invalid message"
+
+ else:
+ self_username = get_username(request)
+
+ if self_username is None:
+ anonymous = True
+ u_from = None
+ else:
+ anonymous = request.POST.get("anonymous") is not None
+ u_from = get_user_object(self_username, i_promise_this_user_exists=True)
+
+ tMMessage.objects.create(
+ content=content,
+ response=None,
+ anonymous=anonymous,
+ u_to=get_user_object(username, i_promise_this_user_exists=True),
+ u_from=u_from
+ )
+
+ error = "Sent!"
+
+ return render_template(
+ request, "message.html",
+ username=username,
+ error=error,
+ self_username=get_username(request)
+ )