extend notifications

This commit is contained in:
Karl 2025-07-18 17:51:48 +01:00
parent 93e9a1990a
commit c79a908281
5 changed files with 61 additions and 44 deletions

View File

@ -3,7 +3,7 @@ import sys
from dotenv import load_dotenv from dotenv import load_dotenv
import mysql.connector import mysql.connector
from datetime import datetime, timedelta from datetime import datetime, timedelta
from routes.api import send_notification from ktvmanager.lib.notifications import send_notification
from ktvmanager.lib.database import get_push_subscriptions, _execute_query from ktvmanager.lib.database import get_push_subscriptions, _execute_query
# Add the project root to the Python path # Add the project root to the Python path
@ -33,41 +33,46 @@ def get_all_accounts(db_connection: MySQLConnection) -> List[Dict[str, Any]]:
return accounts return accounts
def send_expiry_notifications() -> None: def send_expiry_notifications(app) -> None:
""" """
Sends notifications to users with accounts expiring in the next 30 days. Sends notifications to users with accounts expiring in the next 30 days.
""" """
now = datetime.now() with app.app_context():
thirty_days_later = now + timedelta(days=30) now = datetime.now()
now_timestamp = int(now.timestamp()) thirty_days_later = now + timedelta(days=30)
thirty_days_later_timestamp = int(thirty_days_later.timestamp()) now_timestamp = int(now.timestamp())
thirty_days_later_timestamp = int(thirty_days_later.timestamp())
query = """ query = """
SELECT u.id as user_id, ua.username, ua.expiaryDate SELECT u.id as user_id, ua.username, ua.expiaryDate
FROM users u FROM users u
JOIN userAccounts ua ON u.id = ua.userID JOIN userAccounts ua ON u.id = ua.userID
WHERE ua.expiaryDate BETWEEN %s AND %s WHERE ua.expiaryDate BETWEEN %s AND %s
""" """
expiring_accounts = _execute_query(query, (now_timestamp, thirty_days_later_timestamp)) expiring_accounts = _execute_query(query, (now_timestamp, thirty_days_later_timestamp))
for account in expiring_accounts: for account in expiring_accounts:
user_id = account['user_id'] expiry_date = datetime.fromtimestamp(account['expiaryDate'])
subscriptions = get_push_subscriptions(user_id) days_to_expiry = (expiry_date - now).days
for sub in subscriptions:
# Check if a notification has been sent recently
last_notified_query = "SELECT last_notified FROM push_subscriptions WHERE id = %s"
last_notified_result = _execute_query(last_notified_query, (sub['id'],))
last_notified = last_notified_result[0]['last_notified'] if last_notified_result and last_notified_result[0]['last_notified'] else None
if last_notified and last_notified > now - timedelta(days=1): if days_to_expiry == 30 or days_to_expiry == 7:
continue user_id = account['user_id']
subscriptions = get_push_subscriptions(user_id)
for sub in subscriptions:
# Check if a notification has been sent recently
last_notified_query = "SELECT last_notified FROM push_subscriptions WHERE id = %s"
last_notified_result = _execute_query(last_notified_query, (sub['id'],))
last_notified = last_notified_result[0]['last_notified'] if last_notified_result and last_notified_result[0]['last_notified'] else None
message = f"Your account {account['username']} is due to expire on {datetime.fromtimestamp(account['expiaryDate']).strftime('%d-%m-%Y')}." if last_notified and last_notified.date() == now.date():
send_notification(sub['subscription_json'], message) continue
# Update the last notified timestamp message = f"Your account {account['username']} is due to expire in {days_to_expiry} days."
update_last_notified_query = "UPDATE push_subscriptions SET last_notified = %s WHERE id = %s" send_notification(sub['subscription_json'], message)
_execute_query(update_last_notified_query, (now, sub['id']))
# Update the last notified timestamp
update_last_notified_query = "UPDATE push_subscriptions SET last_notified = %s WHERE id = %s"
_execute_query(update_last_notified_query, (now, sub['id']))
def main() -> None: def main() -> None:

View File

@ -0,0 +1,17 @@
from flask import current_app
from pywebpush import webpush, WebPushException
def send_notification(subscription_info, message_body):
try:
webpush(
subscription_info=subscription_info,
data=message_body,
vapid_private_key=current_app.config["VAPID_PRIVATE_KEY"],
vapid_claims={"sub": current_app.config["VAPID_CLAIM_EMAIL"]},
)
except WebPushException as ex:
print(f"Web push error: {ex}")
# You might want to remove the subscription if it's invalid
if ex.response and ex.response.status_code == 410:
print("Subscription is no longer valid, removing from DB.")
# Add logic to remove the subscription from your database

View File

@ -4,6 +4,8 @@ from dotenv import load_dotenv
from ktvmanager.config import DevelopmentConfig, ProductionConfig from ktvmanager.config import DevelopmentConfig, ProductionConfig
from routes.api import api_blueprint from routes.api import api_blueprint
from ktvmanager.lib.database import initialize_db_pool from ktvmanager.lib.database import initialize_db_pool
from ktvmanager.account_checker import send_expiry_notifications
from apscheduler.schedulers.background import BackgroundScheduler
def create_app(): def create_app():
app = Flask(__name__) app = Flask(__name__)
@ -17,6 +19,12 @@ def create_app():
with app.app_context(): with app.app_context():
initialize_db_pool() initialize_db_pool()
# Schedule the daily account check
scheduler = BackgroundScheduler()
# Pass the app instance to the job
scheduler.add_job(func=lambda: send_expiry_notifications(app), trigger="interval", days=1)
scheduler.start()
# Register blueprints # Register blueprints
app.register_blueprint(api_blueprint) app.register_blueprint(api_blueprint)

View File

@ -38,4 +38,5 @@ mysql-connector-python
python-dotenv python-dotenv
python-dotenv python-dotenv
pywebpush==1.13.0 pywebpush==1.13.0
stem==1.8.2 stem==1.8.2
APScheduler==3.10.4

View File

@ -18,7 +18,7 @@ import re
import base64 import base64
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import ec
from pywebpush import webpush, WebPushException from ktvmanager.lib.notifications import send_notification
api_blueprint = Blueprint("api", __name__) api_blueprint = Blueprint("api", __name__)
@ -196,20 +196,6 @@ def save_subscription(username: str, password: str) -> Response:
return jsonify({"message": "Subscription saved."}) return jsonify({"message": "Subscription saved."})
def send_notification(subscription_info, message_body):
try:
webpush(
subscription_info=subscription_info,
data=message_body,
vapid_private_key=current_app.config["VAPID_PRIVATE_KEY"],
vapid_claims={"sub": current_app.config["VAPID_CLAIM_EMAIL"]},
)
except WebPushException as ex:
print(f"Web push error: {ex}")
# You might want to remove the subscription if it's invalid
if ex.response and ex.response.status_code == 410:
print("Subscription is no longer valid, removing from DB.")
# Add logic to remove the subscription from your database
@api_blueprint.route("/send-expiry-notifications", methods=["POST"]) @api_blueprint.route("/send-expiry-notifications", methods=["POST"])