merge experimental #1

Merged
jlobbes merged 25 commits from experimental into master 2023-08-17 15:16:51 +01:00
14 changed files with 239 additions and 9 deletions
Showing only changes of commit ed8d8ad8ee - Show all commits

View File

@ -79,6 +79,7 @@ def register_continue(request):
user_password = request.POST.get('password1')
gotify_user_info = gotify.api.create_user(user.username, user_password)
gotify_app_info = gotify.api.create_application(user.username, user_password)
gotify.api.upload_application_picture(user.username, user_password, gotify_app_info['id'])
gotify_user = gotify.models.GotifyUser(
user=user,
id=gotify_user_info['id']

View File

@ -56,3 +56,20 @@ def create_application(username: str, password: str) -> dict:
response.raise_for_status()
return response.json()
def upload_application_picture(username: str, password: str, app_id: int):
with open('/app/static/medwings/images/logo/medwings-logo.png', 'rb') as image_file:
response = requests.post(
url=f"http://{settings.GOTIFY_CONFIG['HOST']}/application/{app_id}/image",
auth=HTTPBasicAuth(
username,
password,
),
files={
'file': image_file
}
)
if response is not None:
response.raise_for_status()

View File

View File

@ -0,0 +1,24 @@
from django.urls import reverse
from django.core.management.base import BaseCommand
from gotify import models
class Command(BaseCommand):
help = 'Notifies all users to take a vitals measurement now.'
def handle(self, *args, **kwargs):
applications = models.GotifyApplication.objects.all()
for application in applications:
message_text = f"Hello {application.user.user.first_name}. Please take your next vitals measurement now."
message_title = "Medwings Measurement Prompt"
url = reverse('mews-init')
message = models.GotifyMessage(
message=message_text,
title=message_title,
url=url
)
application.send_message(message)

View File

@ -1,6 +1,10 @@
from django.db import models
from enum import Enum
import json
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
import requests
class GotifyUser(models.Model):
@ -8,7 +12,68 @@ class GotifyUser(models.Model):
id = models.PositiveIntegerField(verbose_name="Gotify User ID")
class GotifyMessageType(Enum):
plain = "text/plain"
markdown = "text/markdown"
class GotifyMessage():
type: GotifyMessageType
message: str
title: str | None
priority: int
url: str | None
def __init__(self, message: str, title: str | None = None, priority: int = 5, url: str | None = None, type: str = 'text/plain'):
self.message = message
self.title = title
if not 0 <= priority <= 10:
raise ValueError(f"Priority must be 0 to 10.")
self.priority = priority
self.url = url
self.type = GotifyMessageType(type)
def as_dict(self) -> dict:
obj = {
"message": self.message,
"priority": self.priority,
"extras": {
"client::display": {
"contentType": self.type.value
}
}
}
if self.title:
obj["title"] = self.title
if self.url:
obj["extras"]["client::notification"] = {
"click": {
"url": self.url
}
}
return obj
class GotifyApplication(models.Model):
user = models.OneToOneField(GotifyUser, on_delete=models.CASCADE, primary_key=True)
id = models.PositiveIntegerField(verbose_name="Gotify Application ID")
token = models.CharField(max_length=256, verbose_name="Gotify Application Token")
def send_message(self, message: GotifyMessage):
endpoint_url = f"http://{settings.GOTIFY_CONFIG['HOST']}/message"
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
response = requests.post(
url=endpoint_url,
headers=headers,
data=json.dumps(message.as_dict())
)
if response is not None:
response.raise_for_status()

View File

@ -8,7 +8,6 @@
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
<h1>Welcome to Medwings</h1>
<p1>Your personal health guardian</p1>
{% comment %}<img src="{% static 'medwings/images/devices/withings-thermo.webp' %}" alt="A withings thermometer.">{% endcomment %}
<p>
We understand that after receiving medical care, you may still have concerns about your health, particularly if you're at
risk of sudden health changes.

View File

@ -8,6 +8,14 @@
{% block content %}
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
<h1>Record your health status</h1>
<div class="flex flex-col justify-center items-center gap-2">
<div class="flex justify-center items-center gap-4">
<img class="max-h-24 max-w-24 drop-shadow-lg saturate-50" src="{% static 'medwings/images/devices/withings-bpm-core.webp' %}" alt="A Withings BPM core.">
<img class="max-h-24 max-w-24 drop-shadow-lg saturate-50" src="{% static 'medwings/images/devices/withings-thermo.webp' %}" alt="A Withings Thermo.">
<img class="max-h-24 max-w-24 drop-shadow-lg saturate-50" src="{% static 'medwings/images/devices/withings-scanwatch.webp' %}" alt="A Withings Scanwatch.">
</div>
<p class="text-center sm:text-start font-semibold text-lg">Before you begin, please have your Withings devices ready for taking measurements.</p>
</div>
<form method="post">
{% csrf_token %}
<fieldset class="flex flex-col gap-4 items-center">

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
version="1.1"
id="Capa_1"
width="800.00897"
height="800.00897"
viewBox="0 0 45.968516 45.968516"
xml:space="preserve"
sodipodi:docname="medwings-logo.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
inkscape:export-filename="medwings-logo.png"
inkscape:export-xdpi="30.719656"
inkscape:export-ydpi="30.719656"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs4"><linearGradient
id="linearGradient7"
inkscape:collect="always"><stop
style="stop-color:#ec3636;stop-opacity:1;"
offset="0"
id="stop7" /><stop
style="stop-color:#e2df46;stop-opacity:1;"
offset="1"
id="stop8" /></linearGradient>&#10; &#10;<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7"
id="linearGradient8"
x1="-0.88695079"
y1="35.375"
x2="46.902077"
y2="9.6768303"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7"
id="linearGradient9"
gradientUnits="userSpaceOnUse"
x1="-0.88695079"
y1="35.375"
x2="46.902077"
y2="9.6768303" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7"
id="linearGradient10"
gradientUnits="userSpaceOnUse"
x1="-0.88695079"
y1="35.375"
x2="46.902077"
y2="9.6768303" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7"
id="linearGradient11"
gradientUnits="userSpaceOnUse"
x1="-0.88695079"
y1="35.375"
x2="46.902077"
y2="9.6768303" /></defs><sodipodi:namedview
id="namedview4"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.0275"
inkscape:cx="400"
inkscape:cy="371.77616"
inkscape:window-width="1900"
inkscape:window-height="1004"
inkscape:window-x="10"
inkscape:window-y="38"
inkscape:window-maximized="1"
inkscape:current-layer="Capa_1" />&#10;<g
id="g3"
style="fill:url(#linearGradient8);fill-opacity:1"
transform="translate(1.3144378e-4,-3.0572798e-4)">&#10; <path
d="m 44.887,10.992 c -1.108,-0.732 -2.603,-0.425 -3.33,0.683 -1.736,2.631 -4.229,3.869 -6.704,4.869 h -0.002 c 2.439,3 2.14,8.199 -0.928,11.267 -0.052,0.051 -1.104,1.101 -2.487,2.477 1.027,-0.073 2.096,-0.217 3.179,-0.461 3.148,-0.713 6.408,-2.269 9.085,-5.335 0.872,-1 0.77,-2.521 -0.231,-3.394 -0.013,-0.009 -0.023,-0.015 -0.036,-0.024 0.561,-0.555 1.097,-1.166 1.603,-1.842 0.796,-1.063 0.58,-2.57 -0.482,-3.366 -0.027,-0.021 -0.06,-0.033 -0.087,-0.052 0.386,-0.464 0.756,-0.958 1.105,-1.489 0.73,-1.11 0.423,-2.602 -0.685,-3.333 z"
id="path1"
style="fill:url(#linearGradient9);fill-opacity:1;stroke:none" />&#10; <path
d="m 9.478,21.624 c 0,-1.85 0.578,-3.606 1.636,-5.079 C 8.638,15.759 6.146,14.307 4.411,11.676 3.682,10.568 2.189,10.26 1.081,10.992 c -1.108,0.731 -1.415,2.223 -0.684,3.332 0.351,0.531 0.721,1.025 1.105,1.489 -0.027,0.019 -0.059,0.031 -0.086,0.052 -1.063,0.796 -1.279,2.303 -0.483,3.366 0.506,0.676 1.042,1.287 1.603,1.842 C 2.523,21.082 2.512,21.088 2.5,21.097 1.499,21.97 1.396,23.49 2.268,24.49 c 2.677,3.067 5.937,4.623 9.085,5.336 1.083,0.244 2.151,0.388 3.18,0.461 -1.386,-1.38 -2.442,-2.432 -2.493,-2.482 -1.653,-1.651 -2.562,-3.846 -2.562,-6.181 z"
id="path2"
style="fill:url(#linearGradient10);fill-opacity:1;stroke:none" />&#10; <path
d="m 32.483,16.857 c -1.316,-1.316 -3.042,-1.974 -4.769,-1.974 -1.717,0 -3.435,0.652 -4.748,1.956 -1.314,-1.304 -3.031,-1.956 -4.749,-1.956 -1.726,0 -3.45,0.658 -4.767,1.974 -2.633,2.632 -2.633,6.902 0,9.535 0.128,0.128 6.376,6.346 8.677,8.636 0.231,0.231 0.535,0.347 0.839,0.347 0.304,0 0.607,-0.115 0.839,-0.347 2.301,-2.29 8.549,-8.507 8.677,-8.636 2.633,-2.634 2.633,-6.903 10e-4,-9.535 z"
id="path3"
style="fill:url(#linearGradient11);fill-opacity:1;stroke:none" />&#10; </g>&#10;</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -2,6 +2,9 @@
FROM python:alpine
# Install cron daemon and supervisord
RUN apk add --no-cache dcron supervisor
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
@ -12,14 +15,19 @@ RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
ENV PATH "$PATH:/home/${CUSTOM_GROUPNAME}/.local/bin"
# Add supervisord conf
COPY development.supervisord.conf /etc/supervisord.conf
# Add cron job
COPY --chmod=600 django.crontab /etc/crontabs/django
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} app/ /app/
# Install dependencies
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
RUN pip install -r requirements.txt
# Run ASGI server
# Run supervisord
EXPOSE 8000/tcp
ENTRYPOINT ["python", "manage.py", "runserver", "0.0.0.0:8000"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

View File

@ -23,7 +23,7 @@ services:
restart: unless-stopped
depends_on:
- medwings-caddy
- medwings-postgres
- ${PG_HOST}
build:
context: .
dockerfile: ./development.django.Dockerfile
@ -70,8 +70,6 @@ services:
image: gotify/server
container_name: medwings-gotify
restart: unless-stopped
depends_on:
- medwings-postgres
ports:
- "8001:80"
volumes:
@ -89,7 +87,7 @@ services:
container_name: medwings-pgweb
restart: unless-stopped
depends_on:
- medwings-postgres
- ${PG_HOST}
ports:
- "8002:8081"
environment:

View File

@ -0,0 +1,17 @@
[supervisord]
nodaemon=true
user=root
[program:django]
command=sh -c 'python manage.py runserver 0.0.0.0:8000'
directory=/app
user=django
autostart=true
autorestart=true
redirect_stderr=true
[program:crond]
command=crond -f
autostart=true
autorestart=true
redirect_stderr=true

1
django.crontab Normal file
View File

@ -0,0 +1 @@
0 10,13,16,19,22 * * * python /app/manage.py notify_all