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:
44
backend/models/digest_action_token.py
Normal file
44
backend/models/digest_action_token.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from dataclasses import dataclass
|
||||
from models.base import BaseModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class DigestActionToken(BaseModel):
|
||||
user_id: str
|
||||
child_id: str
|
||||
entity_id: str
|
||||
entity_type: str # 'chore' or 'reward'
|
||||
action: str # 'approve' or 'deny'
|
||||
expires_at: str # ISO timestamp
|
||||
used: bool = False
|
||||
signature: str = ''
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> 'DigestActionToken':
|
||||
return cls(
|
||||
user_id=d.get('user_id'),
|
||||
child_id=d.get('child_id'),
|
||||
entity_id=d.get('entity_id'),
|
||||
entity_type=d.get('entity_type'),
|
||||
action=d.get('action'),
|
||||
expires_at=d.get('expires_at'),
|
||||
used=d.get('used', False),
|
||||
signature=d.get('signature', ''),
|
||||
id=d.get('id'),
|
||||
created_at=d.get('created_at'),
|
||||
updated_at=d.get('updated_at'),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
base = super().to_dict()
|
||||
base.update({
|
||||
'user_id': self.user_id,
|
||||
'child_id': self.child_id,
|
||||
'entity_id': self.entity_id,
|
||||
'entity_type': self.entity_type,
|
||||
'action': self.action,
|
||||
'expires_at': self.expires_at,
|
||||
'used': self.used,
|
||||
'signature': self.signature,
|
||||
})
|
||||
return base
|
||||
29
backend/models/push_subscription.py
Normal file
29
backend/models/push_subscription.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass
|
||||
from models.base import BaseModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class PushSubscription(BaseModel):
|
||||
user_id: str
|
||||
endpoint: str
|
||||
keys: dict # {'p256dh': str, 'auth': str}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict) -> 'PushSubscription':
|
||||
return cls(
|
||||
user_id=d.get('user_id'),
|
||||
endpoint=d.get('endpoint'),
|
||||
keys=d.get('keys', {}),
|
||||
id=d.get('id'),
|
||||
created_at=d.get('created_at'),
|
||||
updated_at=d.get('updated_at'),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
base = super().to_dict()
|
||||
base.update({
|
||||
'user_id': self.user_id,
|
||||
'endpoint': self.endpoint,
|
||||
'keys': self.keys,
|
||||
})
|
||||
return base
|
||||
@@ -22,6 +22,8 @@ class User(BaseModel):
|
||||
deletion_attempted_at: str | None = None
|
||||
role: str = 'user'
|
||||
token_version: int = 0
|
||||
timezone: str | None = None
|
||||
email_digest_enabled: bool = True
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
@@ -45,6 +47,8 @@ class User(BaseModel):
|
||||
deletion_attempted_at=d.get('deletion_attempted_at'),
|
||||
role=d.get('role', 'user'),
|
||||
token_version=d.get('token_version', 0),
|
||||
timezone=d.get('timezone'),
|
||||
email_digest_enabled=d.get('email_digest_enabled', True),
|
||||
id=d.get('id'),
|
||||
created_at=d.get('created_at'),
|
||||
updated_at=d.get('updated_at')
|
||||
@@ -73,5 +77,7 @@ class User(BaseModel):
|
||||
'deletion_attempted_at': self.deletion_attempted_at,
|
||||
'role': self.role,
|
||||
'token_version': self.token_version,
|
||||
'timezone': self.timezone,
|
||||
'email_digest_enabled': self.email_digest_enabled,
|
||||
})
|
||||
return base
|
||||
|
||||
Reference in New Issue
Block a user