merge experimental #1
@ -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']
|
||||
|
@ -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()
|
||||
|
0
app/gotify/management/__init__.py
Normal file
0
app/gotify/management/__init__.py
Normal file
0
app/gotify/management/commands/__init__.py
Normal file
0
app/gotify/management/commands/__init__.py
Normal file
24
app/gotify/management/commands/notify_all.py
Normal file
24
app/gotify/management/commands/notify_all.py
Normal 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)
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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">
|
||||
|
BIN
app/static/medwings/images/logo/medwings-logo.png
Normal file
BIN
app/static/medwings/images/logo/medwings-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
92
app/static/medwings/images/logo/medwings-logo.svg
Normal file
92
app/static/medwings/images/logo/medwings-logo.svg
Normal 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> <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" /> <g
|
||||
id="g3"
|
||||
style="fill:url(#linearGradient8);fill-opacity:1"
|
||||
transform="translate(1.3144378e-4,-3.0572798e-4)"> <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" /> <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" /> <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" /> </g> </svg>
|
After Width: | Height: | Size: 4.2 KiB |
@ -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"]
|
||||
|
@ -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:
|
||||
|
17
development.supervisord.conf
Normal file
17
development.supervisord.conf
Normal 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
1
django.crontab
Normal file
@ -0,0 +1 @@
|
||||
0 10,13,16,19,22 * * * python /app/manage.py notify_all
|
Loading…
x
Reference in New Issue
Block a user