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:
59
backend/api/push_subscription_api.py
Normal file
59
backend/api/push_subscription_api.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from flask import Blueprint, request, jsonify, current_app
|
||||
from tinydb import Query
|
||||
|
||||
from api.utils import get_validated_user_id
|
||||
from db.push_subscriptions import upsert_subscription, delete_by_endpoint
|
||||
from db.db import users_db
|
||||
|
||||
push_subscription_api = Blueprint('push_subscription_api', __name__)
|
||||
|
||||
|
||||
@push_subscription_api.route('/push-vapid-key', methods=['GET'])
|
||||
def get_vapid_public_key():
|
||||
"""Return the VAPID public key for the frontend to use when subscribing."""
|
||||
public_key = current_app.config.get('VAPID_PUBLIC_KEY', '')
|
||||
return jsonify({'public_key': public_key}), 200
|
||||
|
||||
|
||||
@push_subscription_api.route('/push-subscription', methods=['POST'])
|
||||
def subscribe():
|
||||
"""Upsert a push subscription for the current user."""
|
||||
user_id = get_validated_user_id()
|
||||
if not user_id:
|
||||
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
||||
|
||||
data = request.get_json() or {}
|
||||
endpoint = data.get('endpoint')
|
||||
keys = data.get('keys')
|
||||
timezone_str = data.get('timezone')
|
||||
|
||||
if not endpoint or not isinstance(keys, dict):
|
||||
return jsonify({'error': 'endpoint and keys are required', 'code': 'MISSING_FIELDS'}), 400
|
||||
if 'p256dh' not in keys or 'auth' not in keys:
|
||||
return jsonify({'error': 'keys must contain p256dh and auth', 'code': 'MISSING_FIELDS'}), 400
|
||||
|
||||
sub = upsert_subscription(user_id=user_id, endpoint=endpoint, keys=keys)
|
||||
|
||||
# Update user timezone if provided
|
||||
if timezone_str:
|
||||
UserQ = Query()
|
||||
users_db.update({'timezone': timezone_str}, UserQ.id == user_id)
|
||||
|
||||
return jsonify({'message': 'Subscription saved', 'id': sub.id}), 200
|
||||
|
||||
|
||||
@push_subscription_api.route('/push-subscription', methods=['DELETE'])
|
||||
def unsubscribe():
|
||||
"""Remove a push subscription for the current user by endpoint."""
|
||||
user_id = get_validated_user_id()
|
||||
if not user_id:
|
||||
return jsonify({'error': 'Unauthorized', 'code': 'UNAUTHORIZED'}), 401
|
||||
|
||||
data = request.get_json() or {}
|
||||
endpoint = data.get('endpoint')
|
||||
|
||||
if not endpoint:
|
||||
return jsonify({'error': 'endpoint is required', 'code': 'MISSING_FIELDS'}), 400
|
||||
|
||||
removed = delete_by_endpoint(user_id=user_id, endpoint=endpoint)
|
||||
return jsonify({'message': 'Subscription removed', 'removed': removed}), 200
|
||||
Reference in New Issue
Block a user