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 0000000..5007d8d Binary files /dev/null and b/app/static/medwings/images/logo/medwings-logo.png differ 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