From ed8d8ad8ee178590d58e7534212dfcac0d23893b Mon Sep 17 00:00:00 2001 From: Julian Lobbes Date: Tue, 1 Aug 2023 00:07:24 +0200 Subject: [PATCH] feat: add automated notifications via cron --- app/authentication/views.py | 1 + app/gotify/api.py | 17 ++++ app/gotify/management/__init__.py | 0 app/gotify/management/commands/__init__.py | 0 app/gotify/management/commands/notify_all.py | 24 +++++ app/gotify/models.py | 67 ++++++++++++- app/medwings/templates/medwings/index.html | 1 - .../templates/medwings/mews-init.html | 8 ++ .../medwings/images/logo/medwings-logo.png | Bin 0 -> 10575 bytes .../medwings/images/logo/medwings-logo.svg | 92 ++++++++++++++++++ development.django.Dockerfile | 14 ++- development.docker-compose.yml | 6 +- development.supervisord.conf | 17 ++++ django.crontab | 1 + 14 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 app/gotify/management/__init__.py create mode 100644 app/gotify/management/commands/__init__.py create mode 100644 app/gotify/management/commands/notify_all.py create mode 100644 app/static/medwings/images/logo/medwings-logo.png create mode 100644 app/static/medwings/images/logo/medwings-logo.svg create mode 100644 development.supervisord.conf create mode 100644 django.crontab diff --git a/app/authentication/views.py b/app/authentication/views.py index 70c0d4a..244f181 100644 --- a/app/authentication/views.py +++ b/app/authentication/views.py @@ -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'] diff --git a/app/gotify/api.py b/app/gotify/api.py index 766e9fa..b5a82e3 100644 --- a/app/gotify/api.py +++ b/app/gotify/api.py @@ -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() diff --git a/app/gotify/management/__init__.py b/app/gotify/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/gotify/management/commands/__init__.py b/app/gotify/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/gotify/management/commands/notify_all.py b/app/gotify/management/commands/notify_all.py new file mode 100644 index 0000000..d57e810 --- /dev/null +++ b/app/gotify/management/commands/notify_all.py @@ -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) diff --git a/app/gotify/models.py b/app/gotify/models.py index 523b844..0f43d5e 100644 --- a/app/gotify/models.py +++ b/app/gotify/models.py @@ -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() diff --git a/app/medwings/templates/medwings/index.html b/app/medwings/templates/medwings/index.html index 4865289..4afd42c 100644 --- a/app/medwings/templates/medwings/index.html +++ b/app/medwings/templates/medwings/index.html @@ -8,7 +8,6 @@

Welcome to Medwings

Your personal health guardian - {% comment %}A withings thermometer.{% endcomment %}

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. diff --git a/app/medwings/templates/medwings/mews-init.html b/app/medwings/templates/medwings/mews-init.html index 932c853..4e1e583 100644 --- a/app/medwings/templates/medwings/mews-init.html +++ b/app/medwings/templates/medwings/mews-init.html @@ -8,6 +8,14 @@ {% block content %}

Record your health status

+
+
+ A Withings BPM core. + A Withings Thermo. + A Withings Scanwatch. +
+

Before you begin, please have your Withings devices ready for taking measurements.

+
{% csrf_token %}
diff --git a/app/static/medwings/images/logo/medwings-logo.png b/app/static/medwings/images/logo/medwings-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5007d8dfd376f74bd63b9d70102ba972070ee7e4 GIT binary patch literal 10575 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 literal 0 HcmV?d00001 diff --git a/app/static/medwings/images/logo/medwings-logo.svg b/app/static/medwings/images/logo/medwings-logo.svg new file mode 100644 index 0000000..527290d --- /dev/null +++ b/app/static/medwings/images/logo/medwings-logo.svg @@ -0,0 +1,92 @@ + + + + diff --git a/development.django.Dockerfile b/development.django.Dockerfile index a5bfd4c..31ebed1 100644 --- a/development.django.Dockerfile +++ b/development.django.Dockerfile @@ -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"] diff --git a/development.docker-compose.yml b/development.docker-compose.yml index 87ff978..a87c8b4 100644 --- a/development.docker-compose.yml +++ b/development.docker-compose.yml @@ -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: diff --git a/development.supervisord.conf b/development.supervisord.conf new file mode 100644 index 0000000..01b0b58 --- /dev/null +++ b/development.supervisord.conf @@ -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 diff --git a/django.crontab b/django.crontab new file mode 100644 index 0000000..5d88230 --- /dev/null +++ b/django.crontab @@ -0,0 +1 @@ +0 10,13,16,19,22 * * * python /app/manage.py notify_all