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.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"

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.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")

View file

@ -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)
)