you can send messages

This commit is contained in:
trinkey 2024-12-23 13:21:56 -05:00
parent aeb052f26e
commit 103998c0e6
9 changed files with 236 additions and 12 deletions

6
tmessage/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class DBConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "tmessage"

View file

@ -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'),
),
]

View file

48
tmessage/models.py Normal file
View file

@ -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:
...

View file

@ -26,7 +26,8 @@ INSTALLED_APPS = [
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages" "django.contrib.messages",
"tmessage.apps.DBConfig"
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -69,10 +70,10 @@ DATABASES = {
} }
AUTH_PASSWORD_VALIDATORS = [] AUTH_PASSWORD_VALIDATORS = []
LANGUAGE_CODE = "en-us" LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC" TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
USE_TZ = True USE_TZ = True
STATIC_URL = "/static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STATIC_ROOT = BASE_DIR / "collected-static"

View file

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block body %}
<h1>Hey there, {{ username }}!</h1>
<hr>
<p><a href="messages/">Read your messages</a> ({{ new }} new)</p>
<p>
<div>Your message link: <code class="cursor-pointer" id="message-link">{{ config.services.message.url.pub }}/m/{{ username }}/</code></div>
<div><small>(click to copy)</small></div>
</p>
<hr class="sub">
<small>
<a href="{{ config.services.auth.url.pub }}/logout/?to=message">Log out</a> -
<a href="{{ config.services.auth.url.pub }}{{ login_token }}">Other services</a>
</small>
<script>
document.getElementById("message-link").addEventListener("click", function() {
navigator.clipboard.writeText(this.innerText);
this.classList.remove("success-anim");
setTimeout(() => { this.classList.add("success-anim"); }, 10);
});
</script>
{% endblock %}

View file

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block body %}
<h1>Send a message to {{ username }}</h1>
<div id="error">{{ error }}</div>
<hr>
<form method="POST">
{% csrf_token %}
<p><textarea name="message" id="message" required maxlength="10000" placeholder="Your message"></textarea></p>
{% if self_username %}
Logged in as <b>{{ self_username }}</b>
<div>
<input name="anonymous" id="anonymous" type="checkbox">
<label data-fake-checkbox for="anonymous">Don't attach your username</label>
</div>
{% 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 %}
<p><input type="submit" value="Send message"></p>
</form>
{% endblock %}

View file

@ -6,6 +6,7 @@ from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse from django.http import HttpResponse
from django.template import loader from django.template import loader
from tmessage.models import tMUser
from tmessage.settings import config from tmessage.settings import config
COLORS = ["rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach", "yellow", "green", "teal", "sky", "sapphire", "blue", "lavender"] COLORS = ["rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach", "yellow", "green", "teal", "sky", "sapphire", "blue", "lavender"]
@ -21,7 +22,8 @@ def render_template(
) -> HttpResponse: ) -> HttpResponse:
c = { c = {
"accent": random.choice(COLORS), "accent": random.choice(COLORS),
"config": config "config": config,
"login_token": f"/auth/?sessionid={request.COOKIES.get('sessionid')}"
} }
for key, val in context.items(): for key, val in context.items():
@ -38,6 +40,28 @@ def render_template(
return resp 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() 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")

View file

@ -3,7 +3,10 @@ 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 .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: def auth(request: WSGIRequest) -> HttpResponseRedirect:
@ -16,8 +19,15 @@ def auth(request: WSGIRequest) -> HttpResponseRedirect:
return resp return resp
def index(request: WSGIRequest) -> HttpResponse: def index(request: WSGIRequest) -> HttpResponse:
if is_logged_in(request): username = get_username(request)
return dashboard(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( return render_template(
request, "noauth/index.html" request, "noauth/index.html"
@ -27,7 +37,40 @@ def profile(request: WSGIRequest, username: str) -> HttpResponse:
... ...
def message(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: error = ""
return render_template(request, "base.html") 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)
)