Skip to content

Commit

Permalink
Merge pull request #1247 from eciis/finish-push-notifications-setup
Browse files Browse the repository at this point in the history
Push notifications setup
  • Loading branch information
JuliePessoa authored Oct 15, 2018
2 parents bea2986 + 3dc8756 commit afa3bbd
Show file tree
Hide file tree
Showing 30 changed files with 1,146 additions and 49 deletions.
179 changes: 179 additions & 0 deletions backend/fcm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""Pyfcm is a library to enhance and make it easier the communication
with firebase cloud messaging - fcm - from python."""

from pyfcm import FCMNotification

from firebase_config import SERVER_KEY, FIREBASE_URL

from firebase import _get_http

import json

from utils import validate_object

FIREBASE_TOKENS_ENDPOINT = "%s/pushNotifications.json" % FIREBASE_URL

ICON_URL = "https://firebasestorage.googleapis.com/v0/b/eciis-splab.appspot.com/o/images%2FLOGO-E-CIS-1510941864112?alt=media&token=ca197614-ad60-408e-b21e-0ebe258c4a80"

# Instantiate a fcm service to the application server key
push_service = FCMNotification(api_key=SERVER_KEY)


def notify_single_user(data, user_key):
"""Notify a single user.
It sends a notification to each user's device.
Args:
data: An object that has the title, body and click_action
as properties. title is a string that represents the
notification's title. body is the body message of the notification,
the information you want pass to the users. click_action is the url
that the user is gonna be redirected when he click on the notification.
user_key: user's urlsafe key.
"""
tokens = get_single_user_tokens(user_key)
send_push_notifications(data, tokens)


def notify_multiple_users(data, user_keys):
"""Notify multiple users.
This function receives a list of user_keys
and use it to retrieve the tokens.
Args:
data: An object that has the title, body and click_action
as properties. title is a string that represents the
notification's title. body is the body message of the notification,
the information you want pass to the users. click_action is the url
that the user is gonna be redirected when he click on the notification.
user_keys: A list with all users' urlsafe keys that will
receive the notification.
"""
tokens = get_multiple_user_tokens(user_keys)
send_push_notifications(data, tokens)


def send_push_notifications(data, tokens):
"""It wraps the call to pyfcm notify function.
Args:
data: An object that has the title, body and click_action
as properties. title is a string that represents the
notification's title. body is the body message of the notification,
the information you want pass to the users. click_action is the url
that the user is gonna be redirected when he click on the notification.
tokens: The devices' tokens that will receive
the notification.
"""
validate_object(data, ['title', 'body', 'click_action'])

title = data['title']
body = data['body']
click_action = data['click_action']

if tokens:
result = push_service.notify_multiple_devices(
registration_ids=tokens, message_title=title,
message_body=body, message_icon=ICON_URL,
click_action=click_action
)
return result


def get_single_user_tokens(user_key):
"""Calls get_tokens_from_firebase()
to get all the tokens and then filter them.
Args:
user_key: The user ndb key.urlsafe().
Returns:
The user's tokens or an empty list when the user hasn't
enabled notifications yet.
"""
data = get_tokens_from_firebase(user_key)
tokens = filter_single_user_tokens(data)
return tokens


def get_multiple_user_tokens(users_keys):
"""This function calls get_all_tokens_from_firebase()
to get all the tokens and then filter them using
filter_multiple_user_tokens function.
Args:
users_keys: The users ndb key.urlsafe().
Returns:
The users' token or an empty list when None
of the users haven't enabled notifications yet.
"""
data = get_all_tokens_from_firebase()
tokens = filter_multiple_user_tokens(data, users_keys)
return tokens


def get_all_tokens_from_firebase():
"""This function only wraps the logic of
make a request to firebase to retrieve all
the tokens from the database.
Returns:
The request's content parsed to json.
"""
response, content = _get_http().request(FIREBASE_TOKENS_ENDPOINT, method='GET')
return json.loads(content)


def get_tokens_from_firebase(user_key):
"""It gets all tokens from the firebase
of the user whose key is user_key received as parameter.
Args:
user_key: The user's urlsafe key.
Returns:
The request's content parsed to json.
"""
firebase_endpoint = "%s/pushNotifications/%s.json" %(FIREBASE_URL, user_key)
response, content = _get_http().request(firebase_endpoint, method='GET')
return json.loads(content)


def filter_single_user_tokens(content):
"""It loops through the content keys
and for each object it appends the
token property to the token's list.
Args:
content: A json returned from firebase.
Returns:
The user's tokens.
"""
tokens = []
for key in content:
tokens.append(content[key]['token'])
return tokens


def filter_multiple_user_tokens(content, users_keys):
"""For each user, represented by user_key
It get the user's firebase objects and loops through
each object getting the token.
Args:
content: The json returned from firebase
users_keys: The users' keys who will receive the
notification.
Returns:
The users' tokens.
"""
tokens = []
for user_key in users_keys:
if user_key in content:
current_firebase_objects = content[user_key]
current_tokens = filter_single_user_tokens(current_firebase_objects)
map(lambda token: tokens.append(token), current_tokens)
return tokens
6 changes: 3 additions & 3 deletions backend/firebase.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

_FIREBASE_SCOPES = [
'https://www.googleapis.com/auth/firebase.database',
'https://www.googleapis.com/auth/userinfo.email']

'https://www.googleapis.com/auth/userinfo.email'
]

@lru_cache()
def _get_http():
Expand Down Expand Up @@ -44,4 +44,4 @@ def send_notification(user, message, entity_type, entity):
message['timestamp'] = datetime.datetime.now().isoformat()
message['entity_type'] = entity_type
message['entity'] = entity
firebase_post(url, value=json.dumps(message))
firebase_post(url, value=json.dumps(message))
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,25 @@
from models import Institution
from models import InviteFactory
from models import RequestInstitutionChildren
from service_entities import enqueue_task
from push_notification import NotificationType

__all__ = ['InstitutionChildrenRequestCollectionHandler']


def enqueue_push_notification(requested_inst_key):
"""Get the necessary parameters and insert
a new push notification in the queue.
"""
requested_inst = requested_inst_key.get()
receiver = requested_inst.admin.urlsafe()

enqueue_task('send-push-notification', {
'type': NotificationType.link.value,
'receivers': [receiver],
'entity': requested_inst_key.urlsafe()
})

class InstitutionChildrenRequestCollectionHandler(BaseHandler):
"""Institution Children Request Collection Handler."""

Expand Down Expand Up @@ -72,4 +88,6 @@ def post(self, user, institution_urlsafe):

request.send_invite(host, user.current_institution)

enqueue_push_notification(requested_inst_key)

self.response.write(json.dumps(request.make()))
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from util import Notification, NotificationsQueueManager
from service_entities import enqueue_task
from service_messages import create_message

from service_entities import enqueue_task
from push_notification import NotificationType

__all__ = ['InstitutionParentRequestCollectionHandler']

Expand All @@ -40,6 +41,20 @@ def remake_link(request, requested_inst_key, child_institution, user):

request.change_status('accepted')


def enqueue_push_notification(requested_inst_key):
"""Get the necessary parameters and insert
a new push notification in the queue.
"""
requested_inst = requested_inst_key.get()
receiver = requested_inst.admin.urlsafe()

enqueue_task('send-push-notification', {
'type': NotificationType.link.value,
'receivers': [receiver],
'entity': requested_inst_key.urlsafe()
})

class InstitutionParentRequestCollectionHandler(BaseHandler):
"""Institution Parent Collectcion Request Handler."""

Expand Down Expand Up @@ -125,4 +140,6 @@ def main_operations(request, requested_inst_key, child_institution, user, host):

request = main_operations(request, requested_inst_key, child_institution, user, host)

enqueue_push_notification(requested_inst_key)

self.response.write(json.dumps(request.make()))
14 changes: 11 additions & 3 deletions backend/handlers/invite_user_collection_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,16 @@ def process_invites(emails, invite, current_institution_key):
else:
invite = createInvite(invite)
invites.append({'email': invite.invitee, 'key': invite.key.urlsafe()})
enqueue_task('send-invite', {'invites_keys': json.dumps([invite.key.urlsafe()]), 'host': host,
'current_institution': user.current_institution.urlsafe()})
enqueue_task('send-invite', {
'invites_keys': json.dumps([invite.key.urlsafe()]),
'host': host,
'current_institution': user.current_institution.urlsafe()
})

enqueue_task('send-push-notification', {
'type': type_of_invite,
'invites': json.dumps(map(lambda invite: invite['key'], invites))
})

self.response.write(json.dumps(
{'msg': 'The invites are being processed.', 'invites' : invites}))
Expand All @@ -97,4 +105,4 @@ def createInvite(data):
invite = InviteFactory.create(data, data['type_of_invite'])
invite.put()

return invite
return invite
8 changes: 8 additions & 0 deletions backend/handlers/like_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ def post(self, user, post_key, comment_id=None, reply_id=None):

enqueue_task('post-notification', params)

is_first_like = post.get_number_of_likes() == 1
if is_first_like:
enqueue_task('send-push-notification', {
'type': entity_type,
'receivers': [subscriber.urlsafe() for subscriber in post.subscribers],
'entity': post.key.urlsafe()
})

@json_response
@login_required
def delete(self, user, post_key, comment_id=None, reply_id=None):
Expand Down
8 changes: 8 additions & 0 deletions backend/handlers/post_comment_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def post(self, user, post_key):
}
enqueue_task('post-notification', params)

is_first_comment = post.get_number_of_comment() == 1
if is_first_comment:
enqueue_task('send-push-notification', {
'type': entity_type,
'receivers': [subscriber.urlsafe() for subscriber in post.subscribers],
'entity': post.key.urlsafe()
})

self.response.write(json.dumps(Utils.toJson(comment)))

@json_response
Expand Down
5 changes: 2 additions & 3 deletions backend/models/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,9 +259,8 @@ def get_number_of_comment(self):
@ndb.transactional(retries=10)
def add_comment(self, comment):
"""Add a comment to the post."""
post = self.key.get()
post.comments[comment.id] = Utils.toJson(comment)
post.put()
self.comments[comment.id] = Utils.toJson(comment)
self.put()

def remove_comment(self, comment):
"""Remove a commet from post."""
Expand Down
10 changes: 10 additions & 0 deletions backend/push_notification/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Initialize push_notification module."""

from .push_notification_service import *
from .send_push_notification_worker_handler import *

notifications = [
push_notification_service, send_push_notification_worker_handler
]

__all__ = [prop for notification in notifications for prop in notification.__all__]
Loading

0 comments on commit afa3bbd

Please sign in to comment.