Browse Source

Refactored to class with long-running thread

pull/1/head
Matthew Raymer 1 year ago
parent
commit
2b048c5a77
  1. 151
      app.py

151
app.py

@ -1,26 +1,30 @@
from cryptography.hazmat.primitives import serialization
from flask import Flask, request, jsonify
from models import db, VAPIDKey, Subscription
from pywebpush import webpush, WebPushException
from typing import Dict, Tuple, Union, Optional
from flask import Flask, request, jsonify, Response
from models import db, VAPIDKey, Subscription
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from pywebpush import webpush, WebPushException
import base64
import binascii
import json
import os
import re
import threading
import time
def create_app(config_name):
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/webpush.db'
db.init_app(app)
class WebPushService:
def __init__(self, config_name: str) -> None:
self.app = Flask(__name__)
self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/webpush.db'
db.init_app(self.app)
with self.app.app_context():
self._initialize()
self.daily_notification_thread = threading.Thread(target=self._send_daily_notifications)
self.daily_notification_thread.start()
def generate_and_save_vapid_keys():
def _generate_and_save_vapid_keys(self) -> None:
try:
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
public_key_bytes = private_key.public_key().public_bytes(
@ -28,91 +32,84 @@ def create_app(config_name):
format=serialization.PublicFormat.UncompressedPoint
)
public_key_base64 = base64.b64encode(public_key_bytes).decode()
private_key_bytes = private_key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
private_key_base64 = base64.b64encode(private_key_bytes).decode()
key = VAPIDKey(public_key=public_key_base64, private_key=private_key_base64)
db.session.add(key)
db.session.commit()
except Exception as e:
print(f"Error generating VAPID keys: {e}")
def initialize():
def _initialize(self) -> None:
if not VAPIDKey.query.first():
generate_and_save_vapid_keys()
self._generate_and_save_vapid_keys()
def send_push_notification(subscription_info, message, vapid_key):
result = True
try:
private_key_base64 = vapid_key.private_key
def _send_push_notification(self, subscription_info: Dict, message: Dict, vapid_key: VAPIDKey) -> bool:
try:
webpush(
subscription_info=subscription_info,
data=json.dumps(message),
vapid_private_key=private_key_base64,
vapid_claims={
"sub": "mailto:info@timesafari.org"
}
vapid_private_key=vapid_key.private_key,
vapid_claims={"sub": "mailto:admin@yourdomain.com"}
)
except Exception as e:
result = False
print(f"Failed to send notification: {e}")
return result
def is_valid_base64_url(s):
return re.match(r'^[A-Za-z0-9_-]*$', s) is not None
def is_valid_server_key(server_key):
if not is_valid_base64_url(server_key):
return False
return len(server_key) == 88
@app.route('/web-push/clear_subscriptions', methods=['POST'])
def clear_subscriptions():
try:
Subscription.query.delete()
return jsonify(message='Subscriptions cleared')
return True
except Exception as e:
return jsonify(error=f'Error clearing subscriptions: {str(e)}'), 500
except WebPushException as ex:
print(f"Failed to send push notification: {ex}")
return False
@app.route('/web-push/regenerate_vapid_keys', methods=['POST'])
def regenerate_vapid_keys():
def _send_daily_notifications(self) -> None:
while True:
with self.app.app_context():
all_subscriptions = Subscription.query.all()
vapid_key = VAPIDKey.query.first()
message = {"title": "Daily Update", "message": "Here's your daily update!"}
for subscription in all_subscriptions:
subscription_info = {
"endpoint": subscription.endpoint,
"keys": {
"p256dh": subscription.p256dh,
"auth": subscription.auth
}
}
self._send_push_notification(subscription_info, message, vapid_key)
time.sleep(24 * 60 * 60)
# Route handlers and other methods would go here...
@app.route('/regenerate_vapid', methods=['POST'])
def regenerate_vapid(self) -> Tuple[str, int]:
try:
# Generate new VAPID keys
VAPIDKey.query.delete()
generate_and_save_vapid_keys()
return jsonify(message='VAPID keys regenerated successfully')
with self.app.app_context():
VAPIDKey.query.delete()
db.session.commit()
self._generate_and_save_vapid_keys()
return jsonify(success=True, message="VAPID keys regenerated successfully"), 200
except Exception as e:
return jsonify(error=f'Error regenerating VAPID keys: {str(e)}'), 500
@app.route('/web-push/vapid', methods=['GET'])
def get_vapid():
@app.route('/get_vapid')
def get_vapid(self) -> Response:
key = VAPIDKey.query.first()
return jsonify(vapidKey=key.public_key)
@app.route('/web-push/subscribe', methods=['POST'])
def subscribe():
@app.route('/subscribe', methods=['POST'])
def subscribe(self) -> Tuple[str, int]:
content = request.json
vapid_key = VAPIDKey.query.first()
@ -127,7 +124,7 @@ def create_app(config_name):
db.session.commit()
time.sleep(5)
subscription_info = {
"endpoint": subscription.endpoint,
"keys": {
@ -137,13 +134,13 @@ def create_app(config_name):
}
message = {"title": "Subscription Successful", "message": "Thank you for subscribing!"}
success = send_push_notification(subscription_info, message, vapid_key)
return jsonify(success=success, message=vapid_key.private_key)
success = self._send_push_notification(subscription_info, message, vapid_key)
return jsonify(success=success, message=vapid_key.private_key)
@app.route('/web-push/unsubscribe', methods=['DELETE'])
def unsubscribe():
@app.route('/unsubscribe', methods=['POST'])
def unsubscribe(self) -> Tuple[str, int]:
content = request.json
endpoint = content['endpoint']
subscription = Subscription.query.filter_by(endpoint=endpoint).first()
@ -152,11 +149,5 @@ def create_app(config_name):
db.session.delete(subscription)
db.session.commit()
return jsonify(success=True, message="Subscription deleted successfully")
else:
return jsonify(success=False, error="Subscription not found"), 404
with app.app_context():
initialize()
return app

Loading…
Cancel
Save