Add push notification functionality with tests and digest scheduler
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m14s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m14s
- Implemented push subscription API with tests for subscribing and unsubscribing users. - Created web push notification tests triggered by child actions. - Added digest scheduler to send email digests to users at 9 PM local time. - Developed utility functions for creating and validating digest action tokens. - Integrated web push sender to handle sending notifications to users. - Added service worker for handling push notifications in the frontend. - Created a push opt-in component for user notification preferences. - Implemented tests for the push opt-in component to ensure correct behavior. - Updated frontend services to manage push subscriptions and permissions.
This commit is contained in:
@@ -17,6 +17,8 @@ from api.reward_api import reward_api
|
||||
from api.task_api import task_api
|
||||
from api.tracking_api import tracking_api
|
||||
from api.user_api import user_api
|
||||
from api.push_subscription_api import push_subscription_api
|
||||
from api.digest_action_api import digest_action_api
|
||||
from config.version import get_full_version
|
||||
|
||||
from db.default import initializeImages, createDefaultTasks, createDefaultRewards
|
||||
@@ -24,6 +26,7 @@ from events.broadcaster import Broadcaster
|
||||
from events.sse import sse_response_for_user, send_to_user
|
||||
from api.utils import get_current_user_id
|
||||
from utils.account_deletion_scheduler import start_deletion_scheduler
|
||||
from utils.digest_scheduler import start_digest_scheduler
|
||||
|
||||
# Configure logging once at application startup
|
||||
logging.basicConfig(
|
||||
@@ -54,6 +57,8 @@ app.register_blueprint(image_api)
|
||||
app.register_blueprint(auth_api, url_prefix='/auth')
|
||||
app.register_blueprint(user_api)
|
||||
app.register_blueprint(tracking_api)
|
||||
app.register_blueprint(push_subscription_api)
|
||||
app.register_blueprint(digest_action_api)
|
||||
|
||||
app.config.update(
|
||||
MAIL_SERVER='smtp.gmail.com',
|
||||
@@ -63,6 +68,7 @@ app.config.update(
|
||||
MAIL_PASSWORD='ruyj hxjf nmrz buar',
|
||||
MAIL_DEFAULT_SENDER='ryan.kegel@gmail.com',
|
||||
FRONTEND_URL=os.environ.get('FRONTEND_URL', 'https://localhost:5173'), # Dynamic via env var, defaults to localhost
|
||||
VAPID_CLAIMS_EMAIL=os.environ.get('VAPID_CLAIMS_EMAIL', 'admin@reward-app.local'),
|
||||
)
|
||||
|
||||
# Security: require SECRET_KEY and REFRESH_TOKEN_EXPIRY_DAYS from environment
|
||||
@@ -81,6 +87,24 @@ try:
|
||||
except ValueError:
|
||||
raise RuntimeError('REFRESH_TOKEN_EXPIRY_DAYS must be an integer.')
|
||||
|
||||
_digest_token_secret = os.environ.get('DIGEST_TOKEN_SECRET')
|
||||
if not _digest_token_secret:
|
||||
raise RuntimeError(
|
||||
'DIGEST_TOKEN_SECRET environment variable is required. '
|
||||
'Set it to a random string (e.g. python -c "import secrets; print(secrets.token_urlsafe(64))")')
|
||||
app.config['DIGEST_TOKEN_SECRET'] = _digest_token_secret
|
||||
|
||||
_vapid_public_key = os.environ.get('VAPID_PUBLIC_KEY')
|
||||
_vapid_private_key = os.environ.get('VAPID_PRIVATE_KEY')
|
||||
if not _vapid_public_key or not _vapid_private_key:
|
||||
raise RuntimeError(
|
||||
'VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY environment variables are required. '
|
||||
'Generate a key pair with: python -c "from py_vapid import Vapid; v=Vapid(); '
|
||||
'v.generate_keys(); print(v.public_key.public_bytes_raw().hex(), v.private_key.private_bytes_raw().hex())"'
|
||||
)
|
||||
app.config['VAPID_PUBLIC_KEY'] = _vapid_public_key
|
||||
app.config['VAPID_PRIVATE_KEY'] = _vapid_private_key
|
||||
|
||||
@app.route("/version")
|
||||
def api_version():
|
||||
return jsonify({"version": get_full_version()})
|
||||
@@ -115,6 +139,7 @@ createDefaultTasks()
|
||||
createDefaultRewards()
|
||||
start_background_threads()
|
||||
start_deletion_scheduler()
|
||||
start_digest_scheduler(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=False, host='0.0.0.0', port=5000, threaded=True)
|
||||
Reference in New Issue
Block a user