Skip to content

Commit

Permalink
Merge pull request #145 from Bestagons/21_Push_notification
Browse files Browse the repository at this point in the history
21 push notification
  • Loading branch information
DDVD233 committed Nov 29, 2021
2 parents adb322a + c06c884 commit 84c643f
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 37 deletions.
5 changes: 5 additions & 0 deletions app_backend/app/app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from fastapi import FastAPI
from .routers import favorites, friends, user
from fastapi.middleware.cors import CORSMiddleware
import firebase_admin
from firebase_admin import credentials


app = FastAPI()

cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
Expand Down
2 changes: 2 additions & 0 deletions app_backend/app/auth_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from dotenv import load_dotenv
import os
import jwt
from bson.objectid import ObjectId

load_dotenv()

JWT_SECRET = os.getenv("SECRET")
JWT_ALGORITHM = os.getenv("ALGORITHM")


def token_response(token: str):
return {
"access_token": token
Expand Down
62 changes: 56 additions & 6 deletions app_backend/app/routers/friends.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from ..models.user_models import AddFriend
from ..auth_bearer import JWTBearer
from ..auth_handler import decodeJWT
from models.user import User
from database import db
import json
from bson import json_util
Expand Down Expand Up @@ -32,21 +33,68 @@
The status code for the request
"""
@router.post("/new-friend/", status_code=status.HTTP_201_CREATED)
async def add_friend(resp: Response, friend: AddFriend):
# TODO: Verify email/user exists
async def add_friend(resp: Response, friend: AddFriend, token: HTTPAuthorizationCredentials = Security(security)):
payload: dict = decodeJWT(token)
user_id = payload["user_id"]
user: User = User().get_user_by_id(user_id)

if friend.friend_email == "":
resp.status_code = status.HTTP_400_BAD_REQUEST
return {"err": "Invalid Friend UUID"}

# check if the uuid does not already contain fuuid
uuid_friends = ["test.friend@emory.edu"] # TODO: get uuid's friends here
uuid_friends = user.friends
if friend.friend_email in uuid_friends:
resp.status_code = status.HTTP_412_PRECONDITION_FAILED
return {"err" : "FUUID already linked to UUID"}
return {"err": "FUUID already linked to UUID"}

# add fuuid
uuid_friends.append(friend.friend_email)
return {"msg" : "FUUID has been successfully linked to UUID"}
# update user
user.friends = uuid_friends
user.save()
return {"msg": "FUUID has been successfully linked to UUID"}


"""
remove_friends implements the /remove-friend route
resp: Response
The response to send back to the user which contains the status code and body
fuuid: str
The UUID of the friend to be added
returns Response
Response.body: dict
Provides any msgs/errs for the request
Response.status: int
The status code for the request
"""
@router.post("/remove-friend/", status_code=status.HTTP_200_OK)
async def remove_friend(resp: Response, friend: AddFriend, token: HTTPAuthorizationCredentials = Security(security)):
payload: dict = decodeJWT(token)
user_id = payload["user_id"]
user: User = User().get_user_by_id(user_id)

if friend.friend_email == "":
resp.status_code = status.HTTP_400_BAD_REQUEST
return {"err": "Invalid Friend UUID"}

# check if the uuid does not already contain fuuid
uuid_friends = user.friends
if friend.friend_email not in uuid_friends:
resp.status_code = status.HTTP_412_PRECONDITION_FAILED
return {"err": "FUUID not linked to UUID"}

# remove fuuid
uuid_friends.remove(friend.friend_email)
# update user
user.friends = uuid_friends
user.save()

return {"msg": "FUUID has been successfully removed from UUID"}


@router.post("/get-friends/", status_code=status.HTTP_200_OK, dependencies=[Depends(security)])
async def get_friends(resp: Response, token: HTTPAuthorizationCredentials = Security(security)):
Expand All @@ -57,7 +105,9 @@ async def get_friends(resp: Response, token: HTTPAuthorizationCredentials = Secu
friends = []
for friend_email in user["friends"]:
fu = db.get_user_by_email(friend_email)
friends.append(json.loads(json_util.dumps(fu)))
if fu is None:
fu = {"email": friend_email, "name": "", "uuid": "", "friends": []}
friends.append(fu)
return friends
else:
resp.status_code = status.HTTP_401_UNAUTHORIZED
Expand Down
83 changes: 83 additions & 0 deletions app_backend/app/routers/notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from fastapi import APIRouter, Response, status, Depends, Security
from ..auth_bearer import JWTBearer
from fastapi.security import HTTPAuthorizationCredentials
from database import db
from models.user import User
from app.auth_handler import decodeJWT

router = APIRouter(prefix="/notification")

security = JWTBearer()

"""
add_device binds a device to a user
resp: Response
The response to send back to the user which contains the status code and body
device_code: str
The UUID of the friend to be added
returns Response
Response.body: dict
Provides any msgs/errs for the request
Response.status: int
The status code for the request
"""
@router.post("/add_device", dependencies=[Depends(security)])
async def add_device(resp: Response, device_code: str, token: HTTPAuthorizationCredentials = Security(security)):
payload: dict = decodeJWT(token)
user_id = payload["user_id"]
user: User = User().get_user_by_id(user_id)

# Add the device to the user
devices = user.devices
if devices is None:
devices = [device_code]
elif device_code in devices: # Allows multiple add.
resp.body = {"msg": "Device already added"}
return resp
else:
devices.append(device_code)
# Update the user
db.db["users"].update_one({"_id": user.id}, {"$set": {"devices": devices}})

resp.status = status.HTTP_200_OK
resp.body = {"msg": "Device added successfully"}
return resp


"""
remove_device removes a bind device from a user
resp: Response
The response to send back to the user which contains the status code and body
device_code: str
The UUID of the friend to be added
returns Response
Response.body: dict
Provides any msgs/errs for the request
Response.status: int
The status code for the request
"""
@router.post("/remove_device")
async def remove_device(resp: Response, device_code: str, token: HTTPAuthorizationCredentials = Security(security)):
payload: dict = decodeJWT(token)
user_id = payload["user_id"]
user: User = User().get_user_by_id(user_id)

# Add the device to the user
devices = user.devices
if devices is None or device_code not in devices:
resp.body = {"msg": "Device not found"}
return resp
else:
devices.remove(device_code)
# Update the user
db.db["users"].update_one({"_id": user.id}, {"$set": {"devices": devices}})

resp.status = status.HTTP_200_OK
resp.body = {"msg": "Device removed successfully"}
return resp
34 changes: 10 additions & 24 deletions app_backend/app/routers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,15 @@
import re
import json
from bson import BSON
from bson import json_util
from bson import json_util, ObjectId

from app.auth_bearer import JWTBearer
from app.auth_handler import signJWT
from database import db
from models.user import User

router = APIRouter(prefix="/user")


"""
UserLogin model keeps track of the user login information
username: str
The username of the user
email: str
The email of the user
password: str
The password of the user
"""
class User(BaseModel):
name: str
email: str
password: str


"""
login implements the route /login/
Expand Down Expand Up @@ -56,13 +40,15 @@ async def login(login: User, resp: Response):
"password": password
}

db_user = db.get_user(user)
db_user = json.loads(json_util.dumps(db_user))
db_user: dict = db.get_user(user)

if db_user is None:
resp.status_code = status.HTTP_400_BAD_REQUEST
return {"err": "This user does not exist. Please try different credentials."}, {}, None

return {"msg": "Successfully logged in"}, signJWT(db_user['_id']['$oid'], db_user["email"]), json.dumps(db_user, sort_keys=True, indent=4, default=json_util.default)
db_user["_id"] = str(db_user['_id'])

return {"msg": "Successfully logged in"}, signJWT(db_user['_id'], email), json.dumps(db_user, sort_keys=True, indent=4, default=json_util.default)

"""
register implements the /register/ route
Expand Down Expand Up @@ -98,12 +84,12 @@ async def register(login: User, resp: Response):
# check if email is a valid emory email
if not (re.search(validEmail, email)):
resp.status_code = status.HTTP_400_BAD_REQUEST
return {"err" : "Invalid email address. It must be an emory.edu email."}
return {"err": "Invalid email address. It must be an emory.edu email."}

# check if password is a valid password
if not (re.search(validPassword, password)):
resp.status_code = status.HTTP_400_BAD_REQUEST
return {"err" : "Invalid password. It must be 8 to 20 characters and have at least one upper case letter, one lower case letter, and one digit."}
return {"err": "Invalid password. It must be 8 to 20 characters and have at least one upper case letter, one lower case letter, and one digit."}

# adds user to the database
new_user = {
Expand All @@ -116,7 +102,7 @@ async def register(login: User, resp: Response):
if "err" in msg:
resp.status_code = status.HTTP_400_BAD_REQUEST
else:
return msg
return signJWT(msg, email)

return msg

Empty file.
41 changes: 41 additions & 0 deletions app_backend/app/utils/notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from firebase_admin import messaging


def notify_one(token: str, title: str, body: str) -> str:
"""
:param token: The registration token of the device to which the message should be sent
:param title: Notification title
:param body: Notification body
:return:
Returns:
string: A message ID string that uniquely identifies the sent message.
"""
message = messaging.Message(
notification=messaging.Notification(
title=title,
body=body
),
token=token
)
return messaging.send(message)


def notify_multiple(tokens: list, title: str, body: str) -> str:
"""
:param tokens: The registration token of the device to which the message should be sent
:param title: Notification title
:param body: Notification body
:return:
Returns:
string: A message ID string that uniquely identifies the sent message.
"""
message = messaging.MulticastMessage(
tokens=tokens,
notification=messaging.Notification(
title=title,
body=body
)
)
return messaging.send(message)
17 changes: 11 additions & 6 deletions app_backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,20 @@ def save_user_in_db(self, login_info: dict):
if users.count_documents({"email": login_info['email']}, limit = 1) > 0:
return {"err": "Email already exists. Use a different email."}

login_info["verified"] = False
login_info["friends"] = []
login_info["is_sharing_loc"] = False
login_info["loc"] = "None"

if not self.test_mode:
print("[Database.py] Dry run not updating database")
login_info["verified"] = False
login_info["friends"] = []
login_info["is_sharing_loc"] = False
login_info["loc"] = "None"
users.insert_one(login_info)
else:
print("[Database.py] Dry run not updating database")
return login_info['email']


return {"msg": "Successfully registered new user."}
# return inserted user id from database
return str(users.find_one({"email": login_info['email']})["_id"])

def get_user_by_uuid(self, uuid):
users = self.db["users"]
Expand Down
Empty file added app_backend/models/__init__.py
Empty file.
Loading

0 comments on commit 84c643f

Please sign in to comment.