diff --git a/ktvmanager/account_checker.py b/ktvmanager/account_checker.py index 1b273c2..87289e1 100644 --- a/ktvmanager/account_checker.py +++ b/ktvmanager/account_checker.py @@ -3,7 +3,7 @@ import sys from dotenv import load_dotenv import mysql.connector 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 # 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 -def send_expiry_notifications() -> None: +def send_expiry_notifications(app) -> None: """ Sends notifications to users with accounts expiring in the next 30 days. """ - now = datetime.now() - thirty_days_later = now + timedelta(days=30) - now_timestamp = int(now.timestamp()) - thirty_days_later_timestamp = int(thirty_days_later.timestamp()) + with app.app_context(): + now = datetime.now() + thirty_days_later = now + timedelta(days=30) + now_timestamp = int(now.timestamp()) + thirty_days_later_timestamp = int(thirty_days_later.timestamp()) - query = """ - SELECT u.id as user_id, ua.username, ua.expiaryDate - FROM users u - JOIN userAccounts ua ON u.id = ua.userID - WHERE ua.expiaryDate BETWEEN %s AND %s - """ - expiring_accounts = _execute_query(query, (now_timestamp, thirty_days_later_timestamp)) + query = """ + SELECT u.id as user_id, ua.username, ua.expiaryDate + FROM users u + JOIN userAccounts ua ON u.id = ua.userID + WHERE ua.expiaryDate BETWEEN %s AND %s + """ + expiring_accounts = _execute_query(query, (now_timestamp, thirty_days_later_timestamp)) - for account in expiring_accounts: - 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 + for account in expiring_accounts: + expiry_date = datetime.fromtimestamp(account['expiaryDate']) + days_to_expiry = (expiry_date - now).days - if last_notified and last_notified > now - timedelta(days=1): - continue + if days_to_expiry == 30 or days_to_expiry == 7: + 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')}." - send_notification(sub['subscription_json'], message) + if last_notified and last_notified.date() == now.date(): + continue - # 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'])) + message = f"Your account {account['username']} is due to expire in {days_to_expiry} days." + send_notification(sub['subscription_json'], message) + + # 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: diff --git a/ktvmanager/lib/notifications.py b/ktvmanager/lib/notifications.py new file mode 100644 index 0000000..436c1d4 --- /dev/null +++ b/ktvmanager/lib/notifications.py @@ -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 \ No newline at end of file diff --git a/ktvmanager/main.py b/ktvmanager/main.py index d2b4f98..ca4d7d9 100644 --- a/ktvmanager/main.py +++ b/ktvmanager/main.py @@ -4,6 +4,8 @@ from dotenv import load_dotenv from ktvmanager.config import DevelopmentConfig, ProductionConfig from routes.api import api_blueprint 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(): app = Flask(__name__) @@ -17,6 +19,12 @@ def create_app(): with app.app_context(): 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 app.register_blueprint(api_blueprint) diff --git a/requirements.txt b/requirements.txt index 2a6e3c9..0aa6f04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,4 +38,5 @@ mysql-connector-python python-dotenv python-dotenv pywebpush==1.13.0 -stem==1.8.2 \ No newline at end of file +stem==1.8.2 +APScheduler==3.10.4 \ No newline at end of file diff --git a/routes/api.py b/routes/api.py index 443fcc6..5698151 100644 --- a/routes/api.py +++ b/routes/api.py @@ -18,7 +18,7 @@ import re import base64 from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec -from pywebpush import webpush, WebPushException +from ktvmanager.lib.notifications import send_notification api_blueprint = Blueprint("api", __name__) @@ -196,20 +196,6 @@ def save_subscription(username: str, password: str) -> Response: 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"])