Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

21 push notification #145

Merged
merged 23 commits into from Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions app_backend/app/app.py
@@ -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
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
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
@@ -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
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
@@ -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
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.