From 76822224a0166a05aa1264ce00d05d3cdd2058a1 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 27 Jul 2025 01:28:27 -0300 Subject: [PATCH] feat: check and notify users of new versions checks are done against github's API with one request every 12 hours --- app/WYGIWYH/settings.py | 2 + app/apps/common/apps.py | 5 +++ app/apps/common/procrastinate.py | 6 +++ app/apps/common/tasks.py | 47 ++++++++++++++++++++ app/apps/common/templatetags/cache_access.py | 17 +++++++ app/templates/includes/navbar.html | 6 ++- 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 app/apps/common/procrastinate.py create mode 100644 app/apps/common/templatetags/cache_access.py diff --git a/app/WYGIWYH/settings.py b/app/WYGIWYH/settings.py index 79d3e89..f05c8f3 100644 --- a/app/WYGIWYH/settings.py +++ b/app/WYGIWYH/settings.py @@ -487,6 +487,8 @@ else: CACHALOT_UNCACHABLE_TABLES = ("django_migrations", "procrastinate_jobs") +# Procrastinate +PROCRASTINATE_ON_APP_READY = "apps.common.procrastinate.on_app_ready" # PWA PWA_APP_NAME = SITE_TITLE diff --git a/app/apps/common/apps.py b/app/apps/common/apps.py index 76c08b2..213972d 100644 --- a/app/apps/common/apps.py +++ b/app/apps/common/apps.py @@ -1,4 +1,5 @@ from django.apps import AppConfig +from django.core.cache import cache class CommonConfig(AppConfig): @@ -18,3 +19,7 @@ class CommonConfig(AppConfig): admin.site.unregister(SocialAccount) admin.site.unregister(SocialApp) admin.site.unregister(SocialToken) + + # Delete the cache for update checks to prevent false-positives when the app is restarted + # this will be recreated by the check_for_updates task + cache.delete("update_check") diff --git a/app/apps/common/procrastinate.py b/app/apps/common/procrastinate.py new file mode 100644 index 0000000..c6f3c3d --- /dev/null +++ b/app/apps/common/procrastinate.py @@ -0,0 +1,6 @@ +import procrastinate + + +def on_app_ready(app: procrastinate.App): + """This function is ran upon procrastinate initialization.""" + ... diff --git a/app/apps/common/tasks.py b/app/apps/common/tasks.py index 2218d45..b462a38 100644 --- a/app/apps/common/tasks.py +++ b/app/apps/common/tasks.py @@ -1,13 +1,17 @@ import logging +from packaging.version import parse as parse_version, InvalidVersion from asgiref.sync import sync_to_async from django.conf import settings from django.core import management from django.db import DEFAULT_DB_ALIAS +from django.core.cache import cache from procrastinate import builtin_tasks from procrastinate.contrib.django import app +import requests + logger = logging.getLogger(__name__) @@ -79,3 +83,46 @@ def reset_demo_data(timestamp=None): except Exception as e: logger.exception(f"Error during daily demo data reset: {e}") raise + + +@app.periodic(cron="0 */12 * * *") # Every 12 hours +@app.task( + name="check_for_updates", +) +def check_for_updates(timestamp=None): + url = "https://api.github.com/repos/eitchtee/WYGIWYH/releases/latest" + + try: + response = requests.get(url, timeout=60) + response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx) + + data = response.json() + latest_version = data.get("tag_name") + + if latest_version: + try: + current_v = parse_version(settings.APP_VERSION) + except InvalidVersion: + current_v = parse_version("0.0.0") + try: + latest_v = parse_version(latest_version) + except InvalidVersion: + latest_v = parse_version("0.0.0") + + update_info = { + "update_available": False, + "current_version": str(current_v), + "latest_version": str(latest_v), + } + + if latest_v > current_v: + update_info["update_available"] = True + + # Cache the entire dictionary + cache.set("update_check", update_info, 60 * 60 * 25) + logger.info(f"Update check complete. Result: {update_info}") + else: + logger.warning("Could not find 'tag_name' in GitHub API response.") + + except requests.exceptions.RequestException as e: + logger.error(f"Failed to fetch updates from GitHub: {e}") diff --git a/app/apps/common/templatetags/cache_access.py b/app/apps/common/templatetags/cache_access.py new file mode 100644 index 0000000..154986f --- /dev/null +++ b/app/apps/common/templatetags/cache_access.py @@ -0,0 +1,17 @@ +# core/templatetags/update_tags.py +from django import template +from django.core.cache import cache + +register = template.Library() + + +@register.simple_tag +def get_update_check(): + """ + Retrieves the update status dictionary from the cache. + Returns a default dictionary if nothing is found. + """ + return cache.get("update_check") or { + "update_available": False, + "latest_version": "N/A", + } diff --git a/app/templates/includes/navbar.html b/app/templates/includes/navbar.html index d38b920..c0509e7 100644 --- a/app/templates/includes/navbar.html +++ b/app/templates/includes/navbar.html @@ -1,3 +1,4 @@ +{% load cache_access %} {% load settings %} {% load static %} {% load i18n %} @@ -162,9 +163,12 @@