Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
scoop-dev.pem

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -75,4 +76,5 @@ env/
venv/
ENV/
env.bak/
venv.bak/
venv.bak/
credentials.json
1 change: 1 addition & 0 deletions envrc.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export AUTH_PASSWORD_SALT=FILL_IN
export DJANGO_SECRET_KEY=FILL_IN
export DJANGO_DEBUG=FILL_IN
export FCM_API_KEY=FILL_IN
export GOOGLE_DEBUG=FILL_IN
export IMAGE_BUCKET_NAME=FILL_IN
export IMAGE_UPLOAD_URL=FILL_IN
16 changes: 16 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
asgiref==3.5.0
attrs==23.1.0
black==22.1.0
CacheControl==0.13.1
cachetools==5.0.0
certifi==2021.10.8
cffi==1.16.0
cfgv==3.3.1
charset-normalizer==2.0.12
click==8.0.3
coreapi==2.3.3
coreschema==0.0.4
cryptography==41.0.4
distlib==0.3.4
Django==4.0.2
django-rest-framework==0.1.0
django-rest-swagger==2.2.0
djangorestframework==3.13.1
drf-spectacular==0.26.4
fcm-django==2.0.0
filelock==3.4.2
firebase-admin==6.0.1
flake8==4.0.1
flake8-import-order==0.18.1
geographiclib==2.0
Expand All @@ -23,7 +28,14 @@ google-api-core==2.5.0
google-api-python-client==2.37.0
google-auth==2.6.0
google-auth-httplib2==0.1.0
google-cloud-core==2.3.3
google-cloud-firestore==2.5.3
google-cloud-storage==2.11.0
google-crc32c==1.5.0
google-resumable-media==2.6.0
googleapis-common-protos==1.54.0
grpcio==1.59.0
grpcio-status==1.48.2
httplib2==0.20.4
identify==2.4.8
idna==3.3
Expand All @@ -34,18 +46,22 @@ jsonschema==4.19.0
jsonschema-specifications==2023.7.1
MarkupSafe==2.1.3
mccabe==0.6.1
msgpack==1.0.7
mypy-extensions==0.4.3
nodeenv==1.6.0
openapi-codec==1.3.2
pathspec==0.9.0
platformdirs==2.4.1
pre-commit==2.17.0
proto-plus==1.22.3
protobuf==3.19.4
psycopg2==2.9.6
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycodestyle==2.8.0
pycparser==2.21
pyflakes==2.4.0
PyJWT==2.8.0
pyparsing==3.0.7
pytz==2021.3
PyYAML==6.0
Expand Down
14 changes: 10 additions & 4 deletions src/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
from django.urls import include
from django.urls import path
from django.urls import re_path
from django.urls import include, path, re_path
from person.views import AuthenticateView
from person.views import DeveloperView
from person.views import MeView
from person.views import SendMessageView
from ride.views import RidesView
from ride.views import RideView
from ride.views import SearchView
from rest_framework_swagger.views import get_swagger_view
from rest_framework.routers import DefaultRouter
from fcm_django.api.rest_framework import FCMDeviceViewSet

schema_view = get_swagger_view(title='Pastebin API')

router = DefaultRouter()
router.register(r'devices', FCMDeviceViewSet)

urlpatterns = [
path("authenticate/", AuthenticateView.as_view(), name="authenticate"),
path("dev/", DeveloperView.as_view(), name="dev"),
path("me/", MeView.as_view(), name="me"),
path("users/<int:id>/message/", SendMessageView.as_view(), name="message"),
path("rides/<int:id>/", RideView.as_view(), name="ride"),
path("rides/", RidesView.as_view(), name="rides"),
path("search/", SearchView.as_view(), name="search"),
re_path(r"^requests/", include("request.urls")),
re_path(r"^prompts/", include("prompts.urls"))
re_path(r"^prompts/", include("prompts.urls")),
re_path(r"^", include(router.urls)),
]
45 changes: 45 additions & 0 deletions src/person/controllers/send_message_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from api.utils import failure_response
from api.utils import success_response
from django.contrib.auth.models import User
from fcm_django.models import FCMDevice
from firebase_admin.messaging import Message, Notification
from rest_framework import status


class SendMessageController:
def __init__(self, user, data, receiving_user_id):
self._user = user
self._data = data
self._receiving_user_id = receiving_user_id

def process(self):
message = self._data.get("message")
if not message:
return failure_response("Message is required", status.HTTP_400_BAD_REQUEST)
receiving_user = User.objects.filter(id=self._receiving_user_id)
if not receiving_user:
return failure_response(
"Receiving user not found", status.HTTP_404_NOT_FOUND
)
receiving_user = receiving_user[0]
if not receiving_user.person.fcm_registration_token:
# Receiving user does not have notifications enabled, but message will still be delivered through Firebase
# device = FCMDevice()
# device.registration_id = ...
return success_response(status=status.HTTP_200_OK)
device = FCMDevice.objects.get(
registration_id=receiving_user.person.fcm_registration_token
)
response = device.send_message(
Message(
notification=Notification(
title=f"New Ride Request from {self._user.first_name}", body=message
)
)
)
if response.get("failure") == 1:
return failure_response(
f"Failed to send message, FCM Response: {response}",
status.HTTP_502_BAD_GATEWAY,
)
return success_response(status=status.HTTP_201_CREATED)
14 changes: 14 additions & 0 deletions src/person/controllers/update_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from api.utils import update
from django.contrib.auth.models import User
from prompts.models import Prompt
from fcm_django.models import FCMDevice

from ..utils import remove_profile_pic
from ..utils import upload_profile_pic
Expand All @@ -21,6 +22,7 @@ def process(self):
netid = self._data.get("netid")
first_name = self._data.get("first_name")
last_name = self._data.get("last_name")
fcm_registration_token = self._data.get("fcm_registration_token")
grade = self._data.get("grade")
phone_number = self._data.get("phone_number")
profile_pic_base64 = self._data.get("profile_pic_base64")
Expand All @@ -44,6 +46,18 @@ def process(self):
self._person.prompt_questions.set(prompt_ids)
update(self._person, "prompt_answers", json.dumps(prompt_answers))

if fcm_registration_token is not None and self._person.fcm_registration_token != fcm_registration_token:
FCMDevice.objects.filter(
registration_id=self._person.fcm_registration_token
).delete()
self._person.fcm_registration_token = fcm_registration_token
fcm_device = FCMDevice.objects.create(
registration_id=fcm_registration_token,
cloud_message_type="FCM",
user=self._user,
)
self._user.fcm_device = fcm_device

update(self._person, "netid", netid)
update(self._user, "first_name", first_name)
update(self._user, "last_name", last_name)
Expand Down
18 changes: 18 additions & 0 deletions src/person/migrations/0002_person_fcm_registration_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.2 on 2023-10-05 18:24

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('person', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='person',
name='fcm_registration_token',
field=models.TextField(default=None, null=True),
),
]
2 changes: 2 additions & 0 deletions src/person/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ class Person(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, unique=True, default=None
)
fcm_registration_token = models.TextField(default=None, null=True)
grade = models.CharField(max_length=20, default=None, null=True)
profile_pic_url = models.TextField(default=None, null=True)
pronouns = models.CharField(max_length=20, default=None, null=True)
prompt_questions = models.ManyToManyField(Prompt, default=None, blank=True)
prompt_answers = models.TextField(default=None, null=True)

6 changes: 6 additions & 0 deletions src/person/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .controllers.authenticate_controller import AuthenticateController
from .controllers.developer_controller import DeveloperController
from .controllers.update_controller import UpdatePersonController
from .controllers.send_message_controller import SendMessageController
from .serializers import AuthenticateSerializer
from .serializers import UserSerializer

Expand Down Expand Up @@ -64,3 +65,8 @@ def post(self, request):
except json.JSONDecodeError:
data = request.data
return DeveloperController(request, data, self.serializer_class, id).process()

class SendMessageView(generics.GenericAPIView):
def post(self, request, id):
"""Send push notification to user by user id."""
return SendMessageController(request.user, request.data, id).process()
20 changes: 20 additions & 0 deletions src/request/migrations/0002_alter_request_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.0.2 on 2023-10-05 18:24

import datetime
from django.db import migrations, models
from django.utils.timezone import utc


class Migration(migrations.Migration):

dependencies = [
('request', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='request',
name='timestamp',
field=models.DateTimeField(default=datetime.datetime(2023, 10, 5, 18, 24, 37, 364420, tzinfo=utc)),
),
]
20 changes: 20 additions & 0 deletions src/request/migrations/0003_alter_request_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.0.2 on 2023-11-08 22:56

import datetime
from django.db import migrations, models
from django.utils.timezone import utc


class Migration(migrations.Migration):

dependencies = [
('request', '0002_alter_request_timestamp'),
]

operations = [
migrations.AlterField(
model_name='request',
name='timestamp',
field=models.DateTimeField(default=datetime.datetime(2023, 11, 8, 22, 56, 23, 143367, tzinfo=utc)),
),
]
20 changes: 20 additions & 0 deletions src/request/migrations/0004_alter_request_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.0.2 on 2023-11-12 17:34

import datetime
from django.db import migrations, models
from django.utils.timezone import utc


class Migration(migrations.Migration):

dependencies = [
('request', '0003_alter_request_timestamp'),
]

operations = [
migrations.AlterField(
model_name='request',
name='timestamp',
field=models.DateTimeField(default=datetime.datetime(2023, 11, 12, 17, 34, 6, 28835, tzinfo=utc)),
),
]
30 changes: 30 additions & 0 deletions src/rideshare/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@

from os import environ
from pathlib import Path
from firebase_admin import initialize_app, credentials
from google.auth import load_credentials_from_file
from google.oauth2.service_account import Credentials


# create a custom Credentials class to load a non-default google service account JSON
class CustomFirebaseCredentials(credentials.ApplicationDefault):
def __init__(self, account_file_path: str):
super().__init__()
self._account_file_path = account_file_path

def _load_credential(self):
if not self._g_credential:
self._g_credential, self._project_id = load_credentials_from_file(self._account_file_path,
scopes=credentials._scopes)


# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand Down Expand Up @@ -42,6 +58,7 @@
"rest_framework.authtoken",
"rest_framework",
"drf_spectacular",
"fcm_django",
"person",
"ride",
"path",
Expand Down Expand Up @@ -91,6 +108,19 @@
'TITLE': 'Scooped API',
}

FIREBASE_APP = initialize_app()

custom_credentials = CustomFirebaseCredentials(environ.get('CUSTOM_GOOGLE_APPLICATION_CREDENTIALS'))
FIREBASE_MESSAGING_APP = initialize_app(custom_credentials, name='messaging')

FCM_DJANGO_SETTINGS = {
"APP_VERBOSE_NAME": "scooped",
"DEFAULT_FIREBASE_APP": FIREBASE_MESSAGING_APP,
# devices to which notifications cannot be sent,
# are deleted upon receiving error response from FCM
# default: False
"DELETE_INACTIVE_DEVICES": True,
}

# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
Expand Down
Loading