├── .gitignore ├── README.md ├── api ├── .gitignore ├── README.md ├── deployment │ ├── azure-pipelines.yml │ └── scripts │ │ ├── CollectStatic.sh │ │ ├── CreateSU.sh │ │ ├── InstallPackages.sh │ │ ├── MigrateModels.sh │ │ ├── StartServices.sh │ │ └── StopServices.sh └── src │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json │ ├── ads │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── comments │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── common │ ├── abstract_api_views.py │ ├── abstract_factories.py │ ├── abstract_models.py │ ├── abstract_serializers.py │ ├── abstract_tests.py │ ├── constants │ │ └── choices.py │ ├── decorators.py │ ├── generics │ │ ├── generic_post_api_views.py │ │ └── generic_post_tests.py │ ├── model_extensions.py │ ├── serializer_extensions.py │ └── utils │ │ ├── create_resources.py │ │ └── local_authentication.py │ ├── events │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── jobs │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_job_creator.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── listings │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── manage.py │ ├── messaging │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── consumers.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_message_previous_message.py │ │ └── __init__.py │ ├── models.py │ ├── routing.py │ ├── serializers.py │ ├── templates │ │ ├── index.html │ │ └── room.html │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py │ ├── notifications │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ ├── 0003_notification_notification_type.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── utils.py │ ├── photos │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── polls │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ ├── 0003_poll_is_anonymous.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── posts │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20201128_2031.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py │ ├── pytest.ini │ ├── registration │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py │ ├── requirements.txt │ ├── tests │ ├── __init__.py │ ├── api_views_tests │ │ ├── __init__.py │ │ └── tests_notifications_api_views.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models_tests │ │ ├── __init__.py │ │ └── tests_messaging_models.py │ ├── serializers_tests │ │ ├── __init__.py │ │ └── tests_users_serializers.py │ └── utils_tests │ │ ├── __init__.py │ │ └── tests_notifications_utils.py │ ├── umigrate │ ├── __init__.py │ ├── asgi.py │ ├── routing.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py │ └── users │ ├── __init__.py │ ├── admin.py │ ├── api_views.py │ ├── apps.py │ ├── factories.py │ ├── filters.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── createtestdata.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20210703_1814.py │ ├── 0003_customuser_notification_preferences.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ └── urls.py ├── app ├── .gitignore ├── README.md ├── deployment │ ├── azure-pipelines.yml │ └── scripts │ │ ├── InstallExpo.sh │ │ └── Publish.sh └── src │ ├── .eslintrc.js │ ├── .expo-shared │ └── assets.json │ ├── .prettierrc.js │ ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json │ ├── App.jsx │ ├── app.json │ ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ ├── splash.png │ ├── templatedLogin.png │ └── templatedRegister.png │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ └── src │ ├── components │ ├── buttons │ │ ├── CommentBarButtons.jsx │ │ ├── MenuLogout.jsx │ │ ├── PostTypeOptionsButton.jsx │ │ └── ShowRepliesButton.jsx │ ├── common │ │ ├── BasicCreateForm.jsx │ │ ├── BasicModal.jsx │ │ ├── ButtonWithDownArrow.jsx │ │ ├── CommunitySelectModal.jsx │ │ ├── CreatePageTextInput.jsx │ │ ├── DropdownList.jsx │ │ ├── FeedHeader.jsx │ │ ├── IndividualWork.jsx │ │ ├── PickImage.jsx │ │ └── WorkEduSection.jsx │ ├── containers │ │ ├── FeedContainer.jsx │ │ ├── FeedContainer.test.jsx │ │ ├── SavedContainer.jsx │ │ └── SearchContainer.jsx │ ├── modals │ │ ├── CreateItemModal.jsx │ │ ├── CreateItemModal.test.jsx │ │ ├── CreateWorkEduModal.jsx │ │ ├── ErrorModal.jsx │ │ ├── ErrorModal.test.jsx │ │ ├── PickADayModal.jsx │ │ ├── PickADayTimeModal.jsx │ │ └── UserViewModal.jsx │ └── views │ │ ├── AdView.jsx │ │ ├── CommentBar.jsx │ │ ├── CommentView.jsx │ │ ├── CommentView.test.jsx │ │ ├── EditProfileView.jsx │ │ ├── EventView.jsx │ │ ├── Header.jsx │ │ ├── ImageCollectionView.jsx │ │ ├── ListingView.jsx │ │ ├── PollView.jsx │ │ ├── PostView.jsx │ │ ├── PostView.test.jsx │ │ ├── ProfilePhotoView.jsx │ │ ├── ProfileView.jsx │ │ ├── ReplyView.jsx │ │ ├── ReplyView.test.jsx │ │ ├── SearchView.jsx │ │ └── UserView.jsx │ ├── contexts │ ├── AuthContext.jsx │ ├── CreateItemContext.jsx │ ├── ErrorContext.jsx │ ├── StackNavContext.jsx │ └── UserViewContext.jsx │ ├── navigators │ ├── AuthNavigator.jsx │ ├── MenuNavigator.jsx │ └── TabNavigator.jsx │ ├── screens │ ├── authentication │ │ ├── EmailSentScreen.jsx │ │ ├── LoadingScreen.jsx │ │ ├── LoginScreen.jsx │ │ ├── PasswordResetScreen.jsx │ │ └── RegistrationScreen.jsx │ ├── comments │ │ └── CommentsScreen.jsx │ ├── createItem │ │ ├── CreateCommunityScreen.jsx │ │ ├── CreateCommunityScreen.test.jsx │ │ ├── CreateHousingScreen.jsx │ │ └── CreateMarketScreen.jsx │ ├── likes │ │ └── LikesScreen.jsx │ ├── menu │ │ ├── CalendarScreen.jsx │ │ ├── EditProfileScreen.jsx │ │ ├── EditProfileScreen.test.jsx │ │ ├── MenuHomeScreen.jsx │ │ ├── ProfileScreen.jsx │ │ └── SettingsScreen.jsx │ ├── messaging │ │ └── MessagingScreen.jsx │ ├── notifications │ │ └── NotificationsScreen.jsx │ ├── saved │ │ ├── SavedHomeScreen.jsx │ │ └── SavedItemsScreen.jsx │ ├── search │ │ └── SearchScreen.jsx │ └── tabs │ │ ├── CommunityScreen.jsx │ │ ├── CreateItemScreen.jsx │ │ ├── HousingScreen.jsx │ │ ├── MarketScreen.jsx │ │ └── MenuScreen.jsx │ ├── setupTests.js │ ├── stylesheets │ ├── createItem │ │ └── createItem.jsx │ ├── likesAndComments │ │ └── likesAndComments.jsx │ ├── menu │ │ └── menu.jsx │ ├── messaging │ │ └── messaging.jsx │ ├── tabs │ │ └── tabs.jsx │ └── views │ │ └── views.jsx │ └── utils │ ├── FormatDate │ ├── toMonthDayYearInWords.jsx │ ├── toMonthYear.jsx │ ├── toYearMonthDayInNumbers.jsx │ └── toYearMonthDayTimeInNumbers.jsx │ ├── choices.js │ ├── endpoints.js │ ├── endpoints.test.js │ ├── fetchDataHelpers.js │ ├── mockData.js │ ├── mockEndpoints.js │ ├── pushNotificationHelpers.js │ ├── routes.js │ └── storageAccess.js └── infrastructure ├── README.md ├── deployment ├── azure-pipelines.yml └── scripts │ ├── Certbot.sh │ ├── Crontab.sh │ ├── DBBackup.sh │ ├── DBRestore.sh │ ├── DownloadAzCopy.sh │ ├── EnableServices.sh │ ├── InstallApps.sh │ ├── Nginx.sh │ ├── Postgres.sh │ ├── Redis.sh │ ├── Security.sh │ ├── StartServices.sh │ ├── StopServices.sh │ ├── SystemConfig.sh │ ├── UFW.sh │ ├── Update.sh │ └── Virtualenv.sh ├── release-pipelines ├── umigrate-api.json ├── umigrate-app.json └── umigrate-infrastructure.json ├── src ├── crontab │ └── umigratecron ├── services │ ├── daphne@.service │ └── gunicorn.service ├── sites │ ├── dev.umigrate.ca │ └── umigrate.ca └── sockets │ └── gunicorn.socket └── tests └── SystemTests ├── SystemTests.csproj ├── SystemTests.sln └── UnitTest1.cs /api/README.md: -------------------------------------------------------------------------------- 1 | Test 2 | -------------------------------------------------------------------------------- /api/deployment/scripts/CollectStatic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Collect static files 4 | 5 | # Make the website dir if it doesnt exist 6 | WEBSITE_DIR="/home/umigrate/website/" 7 | if [ ! -d "$WEBSITE_DIR" ]; then 8 | mkdir /home/umigrate/website 9 | fi 10 | 11 | # Make the static dir if it doesnt exist 12 | STATIC_DIR="/home/umigrate/website/static/" 13 | if [ ! -d "$STATIC_DIR" ]; then 14 | mkdir /home/umigrate/website/static 15 | fi 16 | 17 | source /home/umigrate/venv/bin/activate 18 | py /home/umigrate/api/manage.py collectstatic --noinput 19 | deactivate 20 | -------------------------------------------------------------------------------- /api/deployment/scripts/CreateSU.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SUPERUSER_EMAIL=$1 4 | SUPERUSER_PASSWORD=$2 5 | 6 | # Create super user 7 | # Create only if SU doesn't exist (do in python) 8 | source /home/umigrate/venv/bin/activate 9 | export $(egrep -v '^#' /home/umigrate/venv/.env | xargs) && py /home/umigrate/api/manage.py shell -c "from users.models import CustomUser; users = CustomUser.objects.all().filter(is_superuser=True, email='$SUPERUSER_EMAIL'); CustomUser.objects.create_superuser('$SUPERUSER_EMAIL', '$SUPERUSER_PASSWORD') if len(users) == 0 else None" 10 | deactivate 11 | -------------------------------------------------------------------------------- /api/deployment/scripts/InstallPackages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install python packages 4 | py -m pip install -r /home/umigrate/api/requirements.txt 5 | source /home/umigrate/venv/bin/activate 6 | /home/umigrate/venv/bin/pip install -r /home/umigrate/api/requirements.txt 7 | deactivate 8 | -------------------------------------------------------------------------------- /api/deployment/scripts/MigrateModels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Migrate models to database 4 | source /home/umigrate/venv/bin/activate 5 | export $(egrep -v '^#' /home/umigrate/venv/.env | xargs) && py /home/umigrate/api/manage.py migrate 6 | deactivate 7 | 8 | -------------------------------------------------------------------------------- /api/deployment/scripts/StartServices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start services 4 | systemctl start gunicorn.socket 5 | systemctl start gunicorn 6 | systemctl start daphne@{0..2} 7 | systemctl start redis-server 8 | systemctl start nginx 9 | 10 | -------------------------------------------------------------------------------- /api/deployment/scripts/StopServices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stopping nginx, redis-server, daphne, gunicorn 4 | # Stop only if services exist. 5 | STATUS_NGINX="$(systemctl is-active nginx)" 6 | STATUS_REDIS="$(systemctl is-active redis-server)" 7 | STATUS_DAPHNE0="$(systemctl is-active daphne@0)" 8 | STATUS_DAPHNE1="$(systemctl is-active daphne@1)" 9 | STATUS_DAPHNE2="$(systemctl is-active daphne@2)" 10 | STATUS_GUNICORNSOCK="$(systemctl is-active gunicorn.socket)" 11 | STATUS_GUNICORN="$(systemctl is-active gunicorn)" 12 | 13 | if [ "$STATUS_NGINX" = "active" ]; then 14 | systemctl stop nginx 15 | fi 16 | 17 | if [ "$STATUS_REDIS" = "active" ]; then 18 | systemctl stop redis-server 19 | fi 20 | 21 | if [ "$STATUS_DAPHNE0" = "active" ] && [ "$STATUS_DAPHNE1" = "active" ] && [ "$STATUS_DAPHNE2" = "active" ]; then 22 | systemctl stop daphne@{0..2} 23 | fi 24 | 25 | if [ "$STATUS_GUNICORNSOCK" = "active" ]; then 26 | systemctl stop gunicorn.socket 27 | fi 28 | 29 | if [ "$STATUS_GUNICORN" = "active" ]; then 30 | systemctl stop gunicorn 31 | fi 32 | -------------------------------------------------------------------------------- /api/src/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "donjayamanne.python-extension-pack", 4 | "pachwenko.django-test-runner", 5 | "ms-azuretools.vscode-docker", 6 | "ckolkman.vscode-postgres" 7 | ] 8 | } -------------------------------------------------------------------------------- /api/src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Django", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${workspaceFolder}\\manage.py", 12 | "args": [ 13 | "runserver" 14 | ], 15 | "django": true 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /api/src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "env\\Scripts\\python.exe", 3 | "python.linting.pylintArgs": ["--load-plugins=pylint_django"], 4 | "python.formatting.provider": "black", 5 | "editor.formatOnSave": true, 6 | "editor.formatOnSaveMode": "file", 7 | "editor.formatOnType": true, 8 | "editor.formatOnPaste": true 9 | } -------------------------------------------------------------------------------- /api/src/ads/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/ads/__init__.py -------------------------------------------------------------------------------- /api/src/ads/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Ad 3 | 4 | 5 | # Register the Ad model with the admin site 6 | admin.site.register(Ad) 7 | -------------------------------------------------------------------------------- /api/src/ads/api_views.py: -------------------------------------------------------------------------------- 1 | from common.abstract_api_views import ( 2 | AbstractModelViewSet, 3 | AbstractAddRemoveUser, 4 | AbstractLikedUsers, 5 | ) 6 | from .filters import AdFilterSet 7 | from .models import Ad 8 | from .serializers import AdSerializer, AdDetailSerializer 9 | from django.utils.decorators import method_decorator 10 | from drf_yasg.utils import swagger_auto_schema 11 | from common.decorators import ( 12 | model_view_set_swagger_decorator, 13 | api_view_swagger_decorator, 14 | ) 15 | 16 | 17 | @model_view_set_swagger_decorator(["Ads"]) 18 | class AdViewSet(AbstractModelViewSet): 19 | queryset = Ad.objects.all() 20 | serializer_class = AdSerializer 21 | detail_serializer_class = AdDetailSerializer 22 | filterset_class = AdFilterSet 23 | 24 | 25 | @api_view_swagger_decorator(["Ads"]) 26 | class LikedAds(AbstractAddRemoveUser): 27 | query_string = "liked_ads" 28 | serializer_class = AdSerializer 29 | model_class = Ad 30 | 31 | 32 | @api_view_swagger_decorator(["Ads"]) 33 | class SavedAds(AbstractAddRemoveUser): 34 | query_string = "saved_ads" 35 | serializer_class = AdSerializer 36 | model_class = Ad 37 | 38 | 39 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["Ads"])) 40 | class AdLikes(AbstractLikedUsers): 41 | model_class = Ad 42 | -------------------------------------------------------------------------------- /api/src/ads/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AdsConfig(AppConfig): 5 | name = "ads" 6 | -------------------------------------------------------------------------------- /api/src/ads/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from common.abstract_factories import AbstractFactory 3 | from .models import Ad 4 | from common.constants.choices import Choices, get_length 5 | from string import ascii_uppercase 6 | from users.factories import UserFactory 7 | import random 8 | from django.db.models.query import QuerySet 9 | 10 | 11 | class AdFactory(AbstractFactory): 12 | class Meta: 13 | model = Ad 14 | 15 | is_service = factory.Faker("boolean") 16 | is_buying = factory.Faker("boolean") 17 | category = factory.Faker( 18 | "random_int", min=0, max=get_length(Choices.AD_CATEGORY_CHOICES) - 1 19 | ) 20 | postal_code = factory.Faker("bothify", text="?#?#?#", letters=ascii_uppercase) 21 | price = factory.Faker("pydecimal", left_digits=6, right_digits=2) 22 | quantity = factory.Faker("random_int") 23 | 24 | @factory.post_generation 25 | def contacted_users(self, create, extracted, **kwargs): 26 | if create: 27 | if isinstance(extracted, (list, QuerySet)): 28 | for user in extracted: 29 | self.contacted_users.add(user) 30 | else: 31 | rand_int = random.randint(0, 2) 32 | for i in range(rand_int): 33 | self.contacted_users.add( 34 | UserFactory(connected_users=[], blocked_users=[]) 35 | ) 36 | 37 | @factory.post_generation 38 | def confirmed_users(self, create, extracted, **kwargs): 39 | if create: 40 | if isinstance(extracted, (list, QuerySet)): 41 | for user in extracted: 42 | self.confirmed_users.add(user) 43 | else: 44 | rand_int = random.randint(0, 2) 45 | for i in range(rand_int): 46 | self.confirmed_users.add( 47 | UserFactory(connected_users=[], blocked_users=[]) 48 | ) 49 | -------------------------------------------------------------------------------- /api/src/ads/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Ad 3 | 4 | 5 | class AdFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the Ad model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | min_price = filters.NumberFilter(field_name="price", lookup_expr="gte") 17 | max_price = filters.NumberFilter(field_name="price", lookup_expr="lte") 18 | 19 | class Meta: 20 | model = Ad 21 | fields = [ 22 | "creator", 23 | "min_datetime_created", 24 | "max_datetime_created", 25 | "community", 26 | "is_service", 27 | "is_buying", 28 | "category", 29 | "postal_code", 30 | "min_price", 31 | "max_price", 32 | "quantity", 33 | ] 34 | -------------------------------------------------------------------------------- /api/src/ads/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Ad", 15 | fields=[ 16 | ("id", models.AutoField(primary_key=True, serialize=False)), 17 | ("title", models.CharField(max_length=100)), 18 | ("content", models.CharField(blank=True, max_length=1000)), 19 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 20 | ( 21 | "community", 22 | models.PositiveSmallIntegerField( 23 | choices=[ 24 | (0, "Waterloo"), 25 | (1, "Toronto"), 26 | (2, "Brampton"), 27 | (3, "Ottawa"), 28 | ] 29 | ), 30 | ), 31 | ("is_service", models.BooleanField(default=False)), 32 | ("is_buying", models.BooleanField(default=False)), 33 | ( 34 | "category", 35 | models.PositiveSmallIntegerField( 36 | choices=[ 37 | (0, "Electronics"), 38 | (1, "Books"), 39 | (2, "Food"), 40 | (3, "Other"), 41 | ], 42 | default=0, 43 | ), 44 | ), 45 | ("postal_code", models.CharField(blank=True, max_length=6)), 46 | ( 47 | "price", 48 | models.DecimalField(decimal_places=2, default=0.0, max_digits=8), 49 | ), 50 | ("quantity", models.PositiveSmallIntegerField(default=0)), 51 | ], 52 | options={ 53 | "ordering": ["-datetime_created"], 54 | "abstract": False, 55 | }, 56 | ), 57 | ] 58 | -------------------------------------------------------------------------------- /api/src/ads/migrations/0002_auto_20201128_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ("ads", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="ad", 20 | name="confirmed_users", 21 | field=models.ManyToManyField( 22 | blank=True, related_name="confirmed_ads", to=settings.AUTH_USER_MODEL 23 | ), 24 | ), 25 | migrations.AddField( 26 | model_name="ad", 27 | name="contacted_users", 28 | field=models.ManyToManyField( 29 | blank=True, related_name="contacted_ads", to=settings.AUTH_USER_MODEL 30 | ), 31 | ), 32 | migrations.AddField( 33 | model_name="ad", 34 | name="creator", 35 | field=models.ForeignKey( 36 | blank=True, 37 | on_delete=django.db.models.deletion.CASCADE, 38 | related_name="created_ads", 39 | to=settings.AUTH_USER_MODEL, 40 | ), 41 | ), 42 | migrations.AddField( 43 | model_name="ad", 44 | name="liked_users", 45 | field=models.ManyToManyField( 46 | blank=True, related_name="liked_ads", to=settings.AUTH_USER_MODEL 47 | ), 48 | ), 49 | migrations.AddField( 50 | model_name="ad", 51 | name="saved_users", 52 | field=models.ManyToManyField( 53 | blank=True, related_name="saved_ads", to=settings.AUTH_USER_MODEL 54 | ), 55 | ), 56 | migrations.AddField( 57 | model_name="ad", 58 | name="tagged_users", 59 | field=models.ManyToManyField( 60 | blank=True, related_name="tagged_ads", to=settings.AUTH_USER_MODEL 61 | ), 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /api/src/ads/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/ads/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/ads/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from common.model_extensions import PhotoCollectionExtension 3 | from common.abstract_models import AbstractPostModel 4 | from common.constants.choices import Choices 5 | from users.models import CustomUser 6 | 7 | 8 | class Ad(AbstractPostModel, PhotoCollectionExtension): 9 | """ 10 | A model class that represents an ad. 11 | """ 12 | 13 | is_service = models.BooleanField(default=False) 14 | is_buying = models.BooleanField(default=False) 15 | category = models.PositiveSmallIntegerField( 16 | choices=Choices.AD_CATEGORY_CHOICES, default=0 17 | ) 18 | postal_code = models.CharField(max_length=6, blank=True) 19 | price = models.DecimalField(max_digits=8, decimal_places=2, default=0.0) 20 | quantity = models.PositiveSmallIntegerField(default=0) 21 | contacted_users = models.ManyToManyField( 22 | to=CustomUser, related_name="contacted_ads", blank=True 23 | ) 24 | confirmed_users = models.ManyToManyField( 25 | to=CustomUser, related_name="confirmed_ads", blank=True 26 | ) 27 | -------------------------------------------------------------------------------- /api/src/ads/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ( 2 | AbstractPostSerializer, 3 | AbstractPostDetailSerializer, 4 | ) 5 | from .models import Ad 6 | 7 | 8 | class AdSerializer(AbstractPostSerializer): 9 | """ 10 | A serializer class for the Ad model. 11 | """ 12 | 13 | class Meta: 14 | model = Ad 15 | fields = "__all__" 16 | exclude_fields = ["saved_users", "liked_users"] 17 | 18 | 19 | class AdDetailSerializer(AdSerializer, AbstractPostDetailSerializer): 20 | """ 21 | A detailed serializer class for the Ad model. 22 | """ 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /api/src/ads/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import AdViewSet, LikedAds, SavedAds, AdLikes 4 | 5 | router = DefaultRouter(trailing_slash=False) 6 | router.register(r"", AdViewSet, basename="ads") 7 | 8 | urlpatterns = router.urls + [ 9 | path("liked", LikedAds.as_view()), 10 | path("saved", SavedAds.as_view()), 11 | path("/likes", AdLikes.as_view()), 12 | ] 13 | """ 14 | Ads url patterns. 15 | """ 16 | -------------------------------------------------------------------------------- /api/src/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/comments/__init__.py -------------------------------------------------------------------------------- /api/src/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Comment, Reply 3 | 4 | 5 | # Register the comment and reply models with the admin site 6 | admin.site.register(Comment) 7 | admin.site.register(Reply) 8 | -------------------------------------------------------------------------------- /api/src/comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | name = "comments" 6 | -------------------------------------------------------------------------------- /api/src/comments/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Comment, Reply 3 | 4 | 5 | class CommentFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the Comment model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | 17 | class Meta: 18 | model = Comment 19 | fields = [ 20 | "creator", 21 | "min_datetime_created", 22 | "max_datetime_created", 23 | "content_type", 24 | "object_id", 25 | ] 26 | 27 | 28 | class ReplyFilterSet(filters.FilterSet): 29 | """ 30 | A filter set class for the Reply model. 31 | """ 32 | 33 | min_datetime_created = filters.DateTimeFilter( 34 | field_name="datetime_created", lookup_expr="gte" 35 | ) 36 | max_datetime_created = filters.DateTimeFilter( 37 | field_name="datetime_created", lookup_expr="lte" 38 | ) 39 | 40 | class Meta: 41 | model = Reply 42 | fields = [ 43 | "creator", 44 | "min_datetime_created", 45 | "max_datetime_created", 46 | "comment", 47 | ] 48 | -------------------------------------------------------------------------------- /api/src/comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="Comment", 16 | fields=[ 17 | ("id", models.AutoField(primary_key=True, serialize=False)), 18 | ("content", models.CharField(max_length=1000)), 19 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 20 | ("object_id", models.PositiveIntegerField()), 21 | ], 22 | options={ 23 | "ordering": ["-datetime_created"], 24 | }, 25 | ), 26 | migrations.CreateModel( 27 | name="Reply", 28 | fields=[ 29 | ("id", models.AutoField(primary_key=True, serialize=False)), 30 | ("content", models.CharField(max_length=1000)), 31 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 32 | ( 33 | "comment", 34 | models.ForeignKey( 35 | on_delete=django.db.models.deletion.CASCADE, 36 | related_name="replies", 37 | to="comments.comment", 38 | ), 39 | ), 40 | ], 41 | options={ 42 | "ordering": ["-datetime_created"], 43 | }, 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /api/src/comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/comments/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/comments/serializers.py: -------------------------------------------------------------------------------- 1 | from .models import Comment, Reply 2 | from rest_framework import serializers 3 | from common.abstract_serializers import ( 4 | AbstractSharedItemSerializer, 5 | AbstractSharedItemDetailSerializer, 6 | ) 7 | 8 | 9 | class CommentSerializer( 10 | AbstractSharedItemSerializer 11 | ): # todo: make more abstract model serializers for any field shared with the abstract model serializer 12 | """ 13 | A serializer class for the Comment model. 14 | """ 15 | 16 | replies = serializers.SerializerMethodField() 17 | # Todo: Maybe in the future 18 | # most_liked_reply = serializers.SerializerMethodField() 19 | 20 | class Meta: 21 | model = Comment 22 | fields = "__all__" 23 | exclude_fields = ["saved_users", "liked_users"] 24 | 25 | def get_replies(self, instance): 26 | return instance.replies.count() 27 | 28 | # def get_most_liked_reply(self, instance): 29 | # # Retrieve the first most liked reply 30 | # most_liked_reply: Reply = ( 31 | # instance.replies.annotate(likes=Count("liked_users")) 32 | # .order_by("-likes", "-datetime_created") 33 | # .first() 34 | # ) 35 | # 36 | # if most_liked_reply is None: 37 | # return None 38 | # 39 | # most_liked_reply_serializer = ReplyDetailSerializer( 40 | # most_liked_reply, context=self.context 41 | # ) 42 | # 43 | # return most_liked_reply_serializer.data 44 | 45 | 46 | class CommentDetailSerializer(CommentSerializer, AbstractSharedItemDetailSerializer): 47 | """ 48 | A detailed serializer class for the Comment model. 49 | """ 50 | 51 | pass 52 | 53 | 54 | class ReplySerializer(AbstractSharedItemSerializer): 55 | """ 56 | A serializer class for the Reply model. 57 | """ 58 | 59 | class Meta: 60 | model = Reply 61 | fields = "__all__" 62 | exclude_fields = ["saved_users", "liked_users"] 63 | 64 | 65 | class ReplyDetailSerializer(ReplySerializer, AbstractSharedItemDetailSerializer): 66 | """ 67 | A detailed serializer class for the Reply model. 68 | """ 69 | 70 | pass 71 | -------------------------------------------------------------------------------- /api/src/comments/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import ( 4 | CommentViewSet, 5 | ReplyViewSet, 6 | LikedComments, 7 | LikedReplies, 8 | SavedComments, 9 | SavedReplies, 10 | CommentLikes, 11 | ReplyLikes, 12 | ) 13 | 14 | router = DefaultRouter(trailing_slash=False) 15 | router.register(r"", CommentViewSet, basename="comments") 16 | 17 | replies_router = DefaultRouter(trailing_slash=False) 18 | replies_router.register(r"", ReplyViewSet, basename="replies") 19 | 20 | urlpatterns = router.urls + [ 21 | path("liked", LikedComments.as_view()), 22 | path("saved", SavedComments.as_view()), 23 | path("/likes", CommentLikes.as_view()), 24 | path("replies/", include(replies_router.urls)), 25 | path("replies/liked", LikedReplies.as_view()), 26 | path("replies/saved", SavedReplies.as_view()), 27 | path("replies//likes", ReplyLikes.as_view()), 28 | ] 29 | """ 30 | Comments url patterns. 31 | """ 32 | -------------------------------------------------------------------------------- /api/src/common/abstract_models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.contenttypes.fields import GenericRelation 2 | from django.db import models 3 | from comments.models import Comment 4 | from users.models import CustomUser 5 | from common.constants.choices import Choices 6 | from rest_framework.permissions import BasePermission, SAFE_METHODS 7 | 8 | 9 | class AbstractPostModel(models.Model): 10 | """ 11 | An abstract model class that represents a basic post. 12 | """ 13 | 14 | id = models.AutoField(primary_key=True) 15 | title = models.CharField(max_length=100) 16 | content = models.CharField(max_length=1000, blank=True) 17 | creator = models.ForeignKey( 18 | to=CustomUser, 19 | related_name="created_%(class)ss", 20 | on_delete=models.CASCADE, 21 | blank=True, 22 | ) 23 | datetime_created = models.DateTimeField(auto_now_add=True) 24 | community = models.PositiveSmallIntegerField(choices=Choices.COMMUNITY_CHOICES) 25 | tagged_users = models.ManyToManyField( 26 | to=CustomUser, related_name="tagged_%(class)ss", blank=True 27 | ) 28 | liked_users = models.ManyToManyField( 29 | to=CustomUser, related_name="liked_%(class)ss", blank=True 30 | ) 31 | saved_users = models.ManyToManyField( 32 | to=CustomUser, related_name="saved_%(class)ss", blank=True 33 | ) 34 | comments = GenericRelation(Comment) 35 | 36 | class Meta: 37 | abstract = True 38 | ordering = ["-datetime_created"] 39 | 40 | def __str__(self): 41 | return f"{self.title}" 42 | 43 | 44 | class IsCreatorOrReadOnly(BasePermission): 45 | """ 46 | A permission class that only allows the creator of a shared item to modify or delete it. 47 | """ 48 | 49 | def has_object_permission(self, request, view, obj: AbstractPostModel): 50 | if request.method in SAFE_METHODS: 51 | return True 52 | 53 | return obj.creator_id == request.user.id 54 | 55 | 56 | class IsCreator(BasePermission): 57 | """ 58 | A permission class that only allows the creator of a shared item to access it. 59 | """ 60 | 61 | def has_object_permission(self, request, view, obj: AbstractPostModel): 62 | return obj.creator_id == request.user.id 63 | -------------------------------------------------------------------------------- /api/src/common/decorators.py: -------------------------------------------------------------------------------- 1 | from django.utils.decorators import method_decorator 2 | from drf_yasg.utils import swagger_auto_schema 3 | 4 | 5 | def model_view_set_swagger_decorator(tags): 6 | """ 7 | A decorator that applies the swagger_auto_schema decorator to all methods in a model view set class. 8 | """ 9 | 10 | def wrapper(obj): 11 | for name in [ 12 | "list", 13 | "create", 14 | "retrieve", 15 | "update", 16 | "partial_update", 17 | "destroy", 18 | ]: 19 | method_decorator(name=name, decorator=swagger_auto_schema(tags=tags))(obj) 20 | 21 | return obj 22 | 23 | return wrapper 24 | 25 | 26 | def api_view_swagger_decorator(tags): 27 | """ 28 | A decorator that applies the swagger_auto_schema decorator to get and post methods in an API view class. 29 | """ 30 | 31 | def wrapper(obj): 32 | for name in [ 33 | "get", 34 | "post", 35 | ]: 36 | method_decorator(name=name, decorator=swagger_auto_schema(tags=tags))(obj) 37 | 38 | return obj 39 | 40 | return wrapper 41 | -------------------------------------------------------------------------------- /api/src/common/model_extensions.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.contenttypes.fields import GenericRelation 3 | from django.db.models import QuerySet 4 | from photos.models import Photo 5 | 6 | 7 | class PhotoCollectionExtension(models.Model): 8 | """ 9 | An abstract model class that adds a collection of photos to a Model class. 10 | """ 11 | 12 | id: int = None 13 | """ 14 | The id field of the model object. Must be overwritten. 15 | """ 16 | 17 | photos = GenericRelation(Photo) 18 | 19 | class Meta: 20 | abstract = True 21 | 22 | def delete(self, using=None, keep_parents=False): 23 | # Delete all photos for this object from the file system 24 | photos: QuerySet[Photo] = self.photos.all() 25 | for photo in photos: 26 | photo.delete() 27 | 28 | super().delete() 29 | -------------------------------------------------------------------------------- /api/src/common/serializer_extensions.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from django.db.models import Model 3 | from rest_framework import serializers 4 | 5 | 6 | class ModelSerializerExtension(serializers.ModelSerializer): 7 | """ 8 | An abstract model serializer class that allows for excluding fields and including related fields. 9 | """ 10 | 11 | class Meta: 12 | model: Model = None 13 | """ 14 | The model class to serialize. Must be overwritten. 15 | """ 16 | 17 | fields: List[str] or str = None 18 | """ 19 | The list of field names to serialize or '__all__'. Must be overwritten. 20 | """ 21 | 22 | extra_fields: List[str] = [] 23 | exclude_fields: List[str] = [] 24 | 25 | def get_field_names(self, declared_fields, info): 26 | fields = super(ModelSerializerExtension, self).get_field_names( 27 | declared_fields, info 28 | ) 29 | 30 | # add the fields listed in extra_fields 31 | if getattr(self.Meta, "extra_fields", []): 32 | fields = fields + self.Meta.extra_fields 33 | 34 | # remove the fields listed in exclude_fields 35 | if getattr(self.Meta, "exclude_fields", []): 36 | fields = [ 37 | field for field in fields if field not in self.Meta.exclude_fields 38 | ] 39 | 40 | return fields 41 | -------------------------------------------------------------------------------- /api/src/common/utils/create_resources.py: -------------------------------------------------------------------------------- 1 | from jobs.models import Job 2 | from messaging.models import Room, Message 3 | from datetime import date, timedelta 4 | 5 | 6 | # Create job instances 7 | def create_jobs(num): 8 | for i in range(num): 9 | job = Job.objects.create( 10 | creator_id=1, 11 | position=f"Job {i}", 12 | company=f"Company {i}", 13 | job_type=0, 14 | start_date=date.today(), 15 | end_date=date.today() + timedelta(days=1), 16 | location="Waterloo", 17 | ) 18 | job.save() 19 | -------------------------------------------------------------------------------- /api/src/common/utils/local_authentication.py: -------------------------------------------------------------------------------- 1 | from rest_framework.authentication import SessionAuthentication 2 | 3 | 4 | # Custom session authentication class that ignores csrf checks 5 | class CsrfExemptSessionAuthentication(SessionAuthentication): 6 | def enforce_csrf(self, request): 7 | return 8 | -------------------------------------------------------------------------------- /api/src/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/events/__init__.py -------------------------------------------------------------------------------- /api/src/events/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Event 3 | 4 | 5 | # Register the Event model with the admin site 6 | admin.site.register(Event) 7 | -------------------------------------------------------------------------------- /api/src/events/api_views.py: -------------------------------------------------------------------------------- 1 | from common.abstract_api_views import ( 2 | AbstractModelViewSet, 3 | AbstractAddRemoveUser, 4 | AbstractLikedUsers, 5 | ) 6 | from .filters import EventFilterSet 7 | from .models import Event 8 | from .serializers import EventSerializer, EventDetailSerializer 9 | from django.utils.decorators import method_decorator 10 | from drf_yasg.utils import swagger_auto_schema 11 | from common.decorators import ( 12 | model_view_set_swagger_decorator, 13 | api_view_swagger_decorator, 14 | ) 15 | 16 | 17 | @model_view_set_swagger_decorator(["Events"]) 18 | class EventViewSet(AbstractModelViewSet): 19 | queryset = Event.objects.all() 20 | serializer_class = EventSerializer 21 | detail_serializer_class = EventDetailSerializer 22 | filterset_class = EventFilterSet 23 | 24 | 25 | @api_view_swagger_decorator(["Events"]) 26 | class LikedEvents(AbstractAddRemoveUser): 27 | query_string = "liked_events" 28 | serializer_class = EventSerializer 29 | model_class = Event 30 | 31 | 32 | @api_view_swagger_decorator(["Events"]) 33 | class SavedEvents(AbstractAddRemoveUser): 34 | query_string = "saved_events" 35 | serializer_class = EventSerializer 36 | model_class = Event 37 | 38 | 39 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["Events"])) 40 | class EventLikes(AbstractLikedUsers): 41 | model_class = Event 42 | 43 | 44 | @api_view_swagger_decorator(["Events"]) 45 | class InterestedEvents(AbstractAddRemoveUser): 46 | query_string = "interested_events" 47 | serializer_class = EventSerializer 48 | model_class = Event 49 | 50 | 51 | @api_view_swagger_decorator(["Events"]) 52 | class AttendingEvents(AbstractAddRemoveUser): 53 | query_string = "attending_events" 54 | serializer_class = EventSerializer 55 | model_class = Event 56 | -------------------------------------------------------------------------------- /api/src/events/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EventsConfig(AppConfig): 5 | name = "events" 6 | -------------------------------------------------------------------------------- /api/src/events/factories.py: -------------------------------------------------------------------------------- 1 | import random 2 | import factory 3 | from django.db.models import QuerySet 4 | from common.abstract_factories import AbstractFactory 5 | from common.constants.choices import Choices, get_length, MockData 6 | from users.factories import UserFactory 7 | from .models import Event 8 | 9 | 10 | class EventFactory(AbstractFactory): 11 | class Meta: 12 | model = Event 13 | 14 | start_datetime = factory.Faker( 15 | "date_time_between", start_date="-30y", end_date="now" 16 | ) 17 | end_datetime = factory.Faker("date_time_between", start_date="now", end_date="+30y") 18 | location = factory.lazy_attribute( 19 | lambda a: MockData.MOCK_ADDRESSES[ 20 | random.randint(0, len(MockData.MOCK_ADDRESSES) - 1) 21 | ] 22 | ) 23 | price = factory.Faker("pydecimal", left_digits=6, right_digits=2) 24 | price_scale = factory.Faker( 25 | "random_int", min=0, max=get_length(Choices.PRICE_CHOICES) - 1 26 | ) 27 | 28 | @factory.post_generation 29 | def interested_users(self, create, extracted, **kwargs): 30 | if create: 31 | if isinstance(extracted, (list, QuerySet)): 32 | for user in extracted: 33 | self.interested_users.add(user) 34 | else: 35 | rand_int = random.randint(0, 2) 36 | for i in range(rand_int): 37 | self.interested_users.add( 38 | UserFactory(connected_users=[], blocked_users=[]) 39 | ) 40 | 41 | @factory.post_generation 42 | def attending_users(self, create, extracted, **kwargs): 43 | if create: 44 | if isinstance(extracted, (list, QuerySet)): 45 | for user in extracted: 46 | self.attending_users.add(user) 47 | else: 48 | rand_int = random.randint(0, 2) 49 | for i in range(rand_int): 50 | self.attending_users.add( 51 | UserFactory(connected_users=[], blocked_users=[]) 52 | ) 53 | -------------------------------------------------------------------------------- /api/src/events/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Event 3 | 4 | 5 | class EventFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the Event model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | min_start_datetime = filters.DateTimeFilter( 17 | field_name="start_datetime", lookup_expr="gte" 18 | ) 19 | max_start_datetime = filters.DateTimeFilter( 20 | field_name="start_datetime", lookup_expr="lte" 21 | ) 22 | min_end_datetime = filters.DateTimeFilter( 23 | field_name="end_datetime", lookup_expr="gte" 24 | ) 25 | max_end_datetime = filters.DateTimeFilter( 26 | field_name="end_datetime", lookup_expr="lte" 27 | ) 28 | min_price = filters.NumberFilter(field_name="price", lookup_expr="gte") 29 | max_price = filters.NumberFilter(field_name="price", lookup_expr="lte") 30 | 31 | class Meta: 32 | model = Event 33 | fields = [ 34 | "creator", 35 | "min_datetime_created", 36 | "max_datetime_created", 37 | "community", 38 | "min_start_datetime", 39 | "max_start_datetime", 40 | "min_end_datetime", 41 | "max_end_datetime", 42 | "min_price", 43 | "max_price", 44 | "price_scale", 45 | ] 46 | -------------------------------------------------------------------------------- /api/src/events/migrations/0002_auto_20201128_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ("events", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="event", 20 | name="attending_users", 21 | field=models.ManyToManyField( 22 | blank=True, related_name="attending_events", to=settings.AUTH_USER_MODEL 23 | ), 24 | ), 25 | migrations.AddField( 26 | model_name="event", 27 | name="creator", 28 | field=models.ForeignKey( 29 | blank=True, 30 | on_delete=django.db.models.deletion.CASCADE, 31 | related_name="created_events", 32 | to=settings.AUTH_USER_MODEL, 33 | ), 34 | ), 35 | migrations.AddField( 36 | model_name="event", 37 | name="interested_users", 38 | field=models.ManyToManyField( 39 | blank=True, 40 | related_name="interested_events", 41 | to=settings.AUTH_USER_MODEL, 42 | ), 43 | ), 44 | migrations.AddField( 45 | model_name="event", 46 | name="liked_users", 47 | field=models.ManyToManyField( 48 | blank=True, related_name="liked_events", to=settings.AUTH_USER_MODEL 49 | ), 50 | ), 51 | migrations.AddField( 52 | model_name="event", 53 | name="saved_users", 54 | field=models.ManyToManyField( 55 | blank=True, related_name="saved_events", to=settings.AUTH_USER_MODEL 56 | ), 57 | ), 58 | migrations.AddField( 59 | model_name="event", 60 | name="tagged_users", 61 | field=models.ManyToManyField( 62 | blank=True, related_name="tagged_events", to=settings.AUTH_USER_MODEL 63 | ), 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /api/src/events/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/events/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/events/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ( 2 | AbstractPostSerializer, 3 | AbstractPostDetailSerializer, 4 | ) 5 | from .models import Event 6 | from rest_framework import serializers 7 | 8 | 9 | class EventSerializer(AbstractPostSerializer): 10 | """ 11 | A serializer class for the Event model. 12 | """ 13 | 14 | is_interested = serializers.SerializerMethodField() 15 | is_attending = serializers.SerializerMethodField() 16 | interested = serializers.SerializerMethodField() 17 | attending = serializers.SerializerMethodField() 18 | 19 | class Meta: 20 | model = Event 21 | fields = "__all__" 22 | exclude_fields = [ 23 | "saved_users", 24 | "liked_users", 25 | "interested_users", 26 | "attending_users", 27 | ] 28 | 29 | def get_is_interested(self, instance): 30 | return instance.interested_users.filter( 31 | id=self.context["request"].user.id 32 | ).exists() 33 | 34 | def get_is_attending(self, instance): 35 | return instance.attending_users.filter( 36 | id=self.context["request"].user.id 37 | ).exists() 38 | 39 | def get_interested(self, instance): 40 | return instance.interested_users.count() 41 | 42 | def get_attending(self, instance): 43 | return instance.attending_users.count() 44 | 45 | 46 | class EventDetailSerializer(EventSerializer, AbstractPostDetailSerializer): 47 | """ 48 | A detailed serializer class for the Event model. 49 | """ 50 | 51 | pass 52 | -------------------------------------------------------------------------------- /api/src/events/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import ( 4 | EventViewSet, 5 | LikedEvents, 6 | SavedEvents, 7 | EventLikes, 8 | InterestedEvents, 9 | AttendingEvents, 10 | ) 11 | 12 | router = DefaultRouter(trailing_slash=False) 13 | router.register(r"", EventViewSet, basename="events") 14 | 15 | urlpatterns = router.urls + [ 16 | path("liked", LikedEvents.as_view()), 17 | path("saved", SavedEvents.as_view()), 18 | path("/likes", EventLikes.as_view()), 19 | path("interested", InterestedEvents.as_view()), 20 | path("attending", AttendingEvents.as_view()), 21 | ] 22 | """ 23 | Events url patterns. 24 | """ 25 | -------------------------------------------------------------------------------- /api/src/jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/jobs/__init__.py -------------------------------------------------------------------------------- /api/src/jobs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Job 3 | 4 | 5 | # Registers the job model with the admin site 6 | admin.site.register(Job) 7 | -------------------------------------------------------------------------------- /api/src/jobs/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class JobsConfig(AppConfig): 5 | name = "jobs" 6 | -------------------------------------------------------------------------------- /api/src/jobs/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from users.factories import UserFactory 3 | from common.constants.choices import Choices, get_length 4 | from .models import Job 5 | 6 | 7 | class JobFactory(factory.django.DjangoModelFactory): 8 | class Meta: 9 | model = Job 10 | 11 | content = factory.Faker("paragraph") 12 | creator = factory.SubFactory(UserFactory) 13 | position = factory.Faker("job") 14 | company = factory.Faker("company") 15 | job_type = factory.Faker( 16 | "random_int", min=0, max=get_length(Choices.JOB_TYPE_CHOICES) - 1 17 | ) 18 | start_date = factory.Faker("date") 19 | end_date = factory.Faker("date") 20 | location = factory.Faker("city") 21 | -------------------------------------------------------------------------------- /api/src/jobs/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | import datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="Job", 16 | fields=[ 17 | ("id", models.AutoField(primary_key=True, serialize=False)), 18 | ("content", models.CharField(blank=True, max_length=1000)), 19 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 20 | ("position", models.CharField(max_length=100)), 21 | ("company", models.CharField(max_length=100)), 22 | ( 23 | "job_type", 24 | models.PositiveSmallIntegerField( 25 | choices=[(0, "Full-time"), (1, "Internship")], default=0 26 | ), 27 | ), 28 | ("start_date", models.DateField(default=datetime.datetime.today)), 29 | ("end_date", models.DateField(blank=True, null=True)), 30 | ("location", models.CharField(max_length=100)), 31 | ], 32 | options={ 33 | "ordering": ["-datetime_created"], 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /api/src/jobs/migrations/0002_job_creator.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ("jobs", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="job", 20 | name="creator", 21 | field=models.ForeignKey( 22 | blank=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | related_name="jobs", 25 | to=settings.AUTH_USER_MODEL, 26 | ), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /api/src/jobs/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/jobs/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/jobs/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from users.models import CustomUser 3 | from common.constants.choices import Choices 4 | from datetime import datetime 5 | 6 | 7 | # Represents a job object 8 | class Job(models.Model): 9 | id = models.AutoField(primary_key=True) 10 | content = models.CharField(max_length=1000, blank=True) 11 | creator = models.ForeignKey( 12 | to=CustomUser, 13 | related_name="jobs", 14 | on_delete=models.CASCADE, 15 | blank=True, 16 | ) 17 | datetime_created = models.DateTimeField(auto_now_add=True) 18 | position = models.CharField(max_length=100) 19 | company = models.CharField(max_length=100) 20 | job_type = models.PositiveSmallIntegerField( 21 | choices=Choices.JOB_TYPE_CHOICES, default=0 22 | ) 23 | start_date = models.DateField(default=datetime.today) 24 | end_date = models.DateField(blank=True, null=True) 25 | location = models.CharField(max_length=100) 26 | 27 | class Meta: 28 | ordering = ["-datetime_created"] 29 | 30 | def __str__(self): 31 | return f"{str(self.creator)}: {self.position} at {self.company}" 32 | -------------------------------------------------------------------------------- /api/src/jobs/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ModelSerializerExtension 2 | from .models import Job 3 | 4 | 5 | # Serializes the job model 6 | class JobSerializer(ModelSerializerExtension): 7 | class Meta: 8 | model = Job 9 | fields = "__all__" 10 | -------------------------------------------------------------------------------- /api/src/jobs/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .api_views import JobListCreate, JobRetrieveUpdateDestroy, SavedJob 3 | 4 | 5 | # Posts url patterns 6 | urlpatterns = [ 7 | path("users/jobs/", JobListCreate.as_view()), 8 | path("users/jobs/", JobRetrieveUpdateDestroy.as_view()), 9 | path("users/jobs/save", SavedJob.as_view()), 10 | ] 11 | -------------------------------------------------------------------------------- /api/src/listings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/listings/__init__.py -------------------------------------------------------------------------------- /api/src/listings/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Listing, RoommatePost 3 | 4 | 5 | # Register the Listing and RoommatePost models with the admin site 6 | admin.site.register(Listing) 7 | admin.site.register(RoommatePost) 8 | -------------------------------------------------------------------------------- /api/src/listings/api_views.py: -------------------------------------------------------------------------------- 1 | from common.abstract_api_views import ( 2 | AbstractModelViewSet, 3 | AbstractAddRemoveUser, 4 | AbstractLikedUsers, 5 | ) 6 | from .filters import ListingFilter 7 | from .models import Listing 8 | from .serializers import ListingSerializer, ListingDetailSerializer 9 | from django.utils.decorators import method_decorator 10 | from drf_yasg.utils import swagger_auto_schema 11 | from common.decorators import ( 12 | model_view_set_swagger_decorator, 13 | api_view_swagger_decorator, 14 | ) 15 | 16 | 17 | @model_view_set_swagger_decorator(["Listings"]) 18 | class ListingViewSet(AbstractModelViewSet): 19 | queryset = Listing.objects.all() 20 | serializer_class = ListingSerializer 21 | detail_serializer_class = ListingDetailSerializer 22 | filterset_class = ListingFilter 23 | 24 | 25 | @api_view_swagger_decorator(["Listings"]) 26 | class LikedListings(AbstractAddRemoveUser): 27 | query_string = "liked_listings" 28 | serializer_class = ListingSerializer 29 | model_class = Listing 30 | 31 | 32 | @api_view_swagger_decorator(["Listings"]) 33 | class SavedListings(AbstractAddRemoveUser): 34 | query_string = "saved_listings" 35 | serializer_class = ListingSerializer 36 | model_class = Listing 37 | 38 | 39 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["Listings"])) 40 | class ListingLikes(AbstractLikedUsers): 41 | model_class = Listing 42 | -------------------------------------------------------------------------------- /api/src/listings/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HousingConfig(AppConfig): 5 | name = "listings" 6 | -------------------------------------------------------------------------------- /api/src/listings/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from common.abstract_factories import AbstractFactory 3 | from .models import Listing 4 | from common.constants.choices import Choices, get_length 5 | from users.factories import UserFactory 6 | import random 7 | from django.db.models.query import QuerySet 8 | 9 | 10 | class ListingFactory(AbstractFactory): 11 | class Meta: 12 | model = Listing 13 | 14 | category = factory.Faker( 15 | "random_int", min=0, max=get_length(Choices.LISTING_CATEGORY_CHOICES) - 1 16 | ) 17 | location = factory.Faker("address") 18 | price = factory.Faker("pydecimal", left_digits=6, right_digits=2) 19 | season = factory.Faker( 20 | "random_int", min=0, max=get_length(Choices.SEASON_CHOICES) - 1 21 | ) 22 | year = factory.Faker("random_int", min=2000, max=2050) 23 | 24 | @factory.post_generation 25 | def contacted_users(self, create, extracted, **kwargs): 26 | if create: 27 | if isinstance(extracted, (list, QuerySet)): 28 | for user in extracted: 29 | self.contacted_users.add(user) 30 | else: 31 | rand_int = random.randint(0, 2) 32 | for i in range(rand_int): 33 | self.contacted_users.add( 34 | UserFactory(connected_users=[], blocked_users=[]) 35 | ) 36 | 37 | @factory.post_generation 38 | def confirmed_users(self, create, extracted, **kwargs): 39 | if create: 40 | if isinstance(extracted, (list, QuerySet)): 41 | for user in extracted: 42 | self.confirmed_users.add(user) 43 | else: 44 | rand_int = random.randint(0, 2) 45 | for i in range(rand_int): 46 | self.confirmed_users.add( 47 | UserFactory(connected_users=[], blocked_users=[]) 48 | ) 49 | -------------------------------------------------------------------------------- /api/src/listings/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Listing 3 | 4 | 5 | class ListingFilter(filters.FilterSet): 6 | """ 7 | A filter set class for the Listing model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | min_price = filters.NumberFilter(field_name="price", lookup_expr="gte") 17 | max_price = filters.NumberFilter(field_name="price", lookup_expr="lte") 18 | 19 | class Meta: 20 | model = Listing 21 | fields = [ 22 | "creator", 23 | "min_datetime_created", 24 | "max_datetime_created", 25 | "community", 26 | "category", 27 | "min_price", 28 | "max_price", 29 | "season", 30 | "year", 31 | "quantity", 32 | ] 33 | -------------------------------------------------------------------------------- /api/src/listings/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/listings/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/listings/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from common.abstract_models import AbstractPostModel 3 | from common.model_extensions import PhotoCollectionExtension 4 | from common.constants.choices import Choices 5 | from users.models import CustomUser 6 | 7 | 8 | class Listing(AbstractPostModel, PhotoCollectionExtension): 9 | """ 10 | A model class that represents a listing. 11 | """ 12 | 13 | category = models.PositiveSmallIntegerField( 14 | choices=Choices.LISTING_CATEGORY_CHOICES, default=0 15 | ) 16 | location = models.CharField(max_length=100) 17 | price = models.DecimalField(max_digits=8, decimal_places=2, default=0.0) 18 | season = models.PositiveSmallIntegerField(choices=Choices.SEASON_CHOICES, default=0) 19 | year = models.PositiveSmallIntegerField(default=2020) 20 | quantity = models.PositiveSmallIntegerField(default=0) 21 | contacted_users = models.ManyToManyField( 22 | to=CustomUser, related_name="contacted_listings", blank=True 23 | ) 24 | confirmed_users = models.ManyToManyField( 25 | to=CustomUser, related_name="confirmed_listings", blank=True 26 | ) 27 | 28 | 29 | class RoommatePost(AbstractPostModel, PhotoCollectionExtension): 30 | """ 31 | A model class that represents a roommate post. 32 | """ 33 | 34 | quantity = models.PositiveSmallIntegerField(default=0) 35 | contacted_users = models.ManyToManyField( 36 | to=CustomUser, related_name="contacted_roommate_posts", blank=True 37 | ) 38 | confirmed_users = models.ManyToManyField( 39 | to=CustomUser, related_name="confirmed_roommate_posts", blank=True 40 | ) 41 | -------------------------------------------------------------------------------- /api/src/listings/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ( 2 | AbstractPostSerializer, 3 | AbstractPostDetailSerializer, 4 | ) 5 | from .models import Listing 6 | 7 | 8 | class ListingSerializer(AbstractPostSerializer): 9 | """ 10 | A serializer class for the Listing model. 11 | """ 12 | 13 | class Meta: 14 | model = Listing 15 | fields = "__all__" 16 | exclude_fields = ["saved_users", "liked_users"] 17 | 18 | 19 | class ListingDetailSerializer(ListingSerializer, AbstractPostDetailSerializer): 20 | """ 21 | A detailed serializer class for the Listing model. 22 | """ 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /api/src/listings/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import ListingViewSet, LikedListings, SavedListings, ListingLikes 4 | 5 | router = DefaultRouter(trailing_slash=False) 6 | router.register(r"", ListingViewSet, basename="listings") 7 | 8 | urlpatterns = router.urls + [ 9 | path("liked", LikedListings.as_view()), 10 | path("saved", SavedListings.as_view()), 11 | path("/likes", ListingLikes.as_view()), 12 | ] 13 | """ 14 | Listings url patterns. 15 | """ 16 | -------------------------------------------------------------------------------- /api/src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umigrate.settings") 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == "__main__": 21 | main() 22 | -------------------------------------------------------------------------------- /api/src/messaging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/messaging/__init__.py -------------------------------------------------------------------------------- /api/src/messaging/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Room, Message 3 | 4 | 5 | # Register the room and message models with the admin site 6 | admin.site.register(Room) 7 | admin.site.register(Message) 8 | -------------------------------------------------------------------------------- /api/src/messaging/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MessagingConfig(AppConfig): 5 | name = "messaging" 6 | -------------------------------------------------------------------------------- /api/src/messaging/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | import random 3 | from django.db.models import QuerySet 4 | from .models import Room, Message 5 | from users.factories import UserFactory 6 | 7 | 8 | class RoomFactory(factory.django.DjangoModelFactory): 9 | class Meta: 10 | model = Room 11 | 12 | title = factory.Faker("text", max_nb_chars=100) 13 | 14 | @factory.post_generation 15 | def members(self, create, extracted, **kwargs): 16 | if create: 17 | if isinstance(extracted, (list, QuerySet)): 18 | for user in extracted: 19 | self.members.add(user) 20 | else: 21 | rand_int = random.randint(0, 2) 22 | for i in range(rand_int): 23 | self.members.add(UserFactory(connected_users=[], blocked_users=[])) 24 | 25 | 26 | class MessageFactory(factory.django.DjangoModelFactory): 27 | class Meta: 28 | model = Message 29 | 30 | content = factory.Faker("paragraph") 31 | creator = factory.SubFactory(UserFactory) 32 | room = factory.SubFactory(RoomFactory) 33 | 34 | @factory.post_generation 35 | def liked_users(self, create, extracted, **kwargs): 36 | if create: 37 | if isinstance(extracted, (list, QuerySet)): 38 | for user in extracted: 39 | self.liked_users.add(user) 40 | else: 41 | rand_int = random.randint(0, 2) 42 | for i in range(rand_int): 43 | self.liked_users.add( 44 | UserFactory(connected_users=[], blocked_users=[]) 45 | ) 46 | 47 | @factory.post_generation 48 | def tagged_users(self, create, extracted, **kwargs): 49 | if create: 50 | if isinstance(extracted, (list, QuerySet)): 51 | for user in extracted: 52 | self.tagged_users.add(user) 53 | else: 54 | rand_int = random.randint(0, 2) 55 | for i in range(rand_int): 56 | self.tagged_users.add( 57 | UserFactory(connected_users=[], blocked_users=[]) 58 | ) 59 | -------------------------------------------------------------------------------- /api/src/messaging/migrations/0002_alter_message_previous_message.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.5 on 2021-07-04 20:49 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("messaging", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="message", 16 | name="previous_message", 17 | field=models.ForeignKey( 18 | blank=True, 19 | null=True, 20 | on_delete=django.db.models.deletion.SET_NULL, 21 | related_name="replies", 22 | to="messaging.message", 23 | ), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /api/src/messaging/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/messaging/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/messaging/routing.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .consumers import ChatConsumer 3 | 4 | websocket_urlpatterns = [ 5 | path("ws/messaging//", ChatConsumer.as_asgi()), 6 | ] 7 | """ 8 | WebSocket url patterns. 9 | """ 10 | -------------------------------------------------------------------------------- /api/src/messaging/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ModelSerializerExtension 2 | from users.serializers import BasicUserSerializer 3 | from .models import Room, Message 4 | 5 | 6 | class RoomSerializer(ModelSerializerExtension): 7 | """ 8 | A serializer class for the Room model. 9 | """ 10 | 11 | class Meta: 12 | model = Room 13 | fields = "__all__" 14 | 15 | def create(self, validated_data): 16 | created_data: Room = ModelSerializerExtension.create(self, validated_data) 17 | 18 | # Set the user as a member of the room 19 | created_data.members.add( 20 | self.context["request"].user 21 | ) # todo: use membership_set instead of members. Figure out what needs to be passed as an argument(s) 22 | 23 | return created_data 24 | 25 | 26 | class RoomDetailSerializer(RoomSerializer): 27 | """ 28 | A detailed serializer class for the Room model. 29 | """ 30 | 31 | members = BasicUserSerializer(read_only=True, many=True) 32 | 33 | 34 | class BasicMessageSerializer(ModelSerializerExtension): 35 | """ 36 | A basic serializer class for the Message model. 37 | """ 38 | 39 | creator = BasicUserSerializer(read_only=True) 40 | 41 | class Meta: 42 | model = Message 43 | fields = [ 44 | "id", 45 | "content", 46 | "creator", 47 | ] 48 | 49 | 50 | class MessageSerializer(ModelSerializerExtension): 51 | """ 52 | A serializer class for the Message model. 53 | """ 54 | 55 | creator = BasicUserSerializer(read_only=True) 56 | previous_message = BasicMessageSerializer(read_only=True) 57 | 58 | class Meta: 59 | model = Message 60 | fields = "__all__" 61 | -------------------------------------------------------------------------------- /api/src/messaging/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chat Rooms 7 | 8 | 9 | What is the ID of the chat room you would like to enter?
10 |
11 | 12 | 13 | 26 | 27 | -------------------------------------------------------------------------------- /api/src/messaging/tests.py: -------------------------------------------------------------------------------- 1 | from unittest import skip 2 | from django.http import HttpRequest 3 | from common.abstract_tests import AbstractAPITestCase 4 | from rest_framework.test import APITestCase 5 | from users.factories import UserFactory 6 | from .factories import RoomFactory 7 | from .models import Room 8 | from .serializers import RoomSerializer, RoomDetailSerializer 9 | 10 | 11 | # Test case for the room API views 12 | @skip("Obsolete") 13 | class RoomTestCase(AbstractAPITestCase, APITestCase): 14 | def setUp(self): 15 | self.api_client = self.client 16 | self.assert_equal = self.assertEqual 17 | self.assert_list_equal = self.assertListEqual 18 | self.endpoint = "/api/rooms/" 19 | self.model_class = Room 20 | self.serializer_class = RoomSerializer 21 | self.detail_serializer_class = RoomDetailSerializer 22 | self.factory_class = RoomFactory 23 | self.pop_keys = ["id", "datetime_created", "members"] 24 | self.maxDiff = self.max_diff 25 | 26 | users = UserFactory.create_batch(5, connected_users=[], blocked_users=[]) 27 | items = self.factory_class.create_batch(5, members=users) 28 | self.api_client.login( 29 | email=users[0].email, password="Top$ecret150", request=HttpRequest() 30 | ) 31 | 32 | def test_list(self): 33 | AbstractAPITestCase.test_list(self) 34 | 35 | def test_create(self): 36 | AbstractAPITestCase.test_create(self) 37 | 38 | def test_retrieve(self): 39 | AbstractAPITestCase.test_retrieve(self) 40 | 41 | def test_update(self): 42 | AbstractAPITestCase.test_update(self) 43 | 44 | def test_update_partial(self): 45 | AbstractAPITestCase.test_update_partial(self) 46 | 47 | def test_destroy(self): 48 | AbstractAPITestCase.test_destroy(self) 49 | -------------------------------------------------------------------------------- /api/src/messaging/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import ( 4 | RoomViewSet, 5 | AddRemoveMembers, 6 | MessageList, 7 | ) 8 | 9 | # TODO: Remove this import later 10 | from .views import index, room 11 | 12 | router = DefaultRouter(trailing_slash=False) 13 | router.register(r"", RoomViewSet, basename="rooms") 14 | 15 | urlpatterns = router.urls + [ 16 | path("/messages/", MessageList.as_view()), 17 | path("/membership/", AddRemoveMembers.as_view()), 18 | # TODO: Remove these 2 paths later 19 | path("messaging/", index, name="index"), 20 | path("messaging//", room, name="room"), 21 | ] 22 | """ 23 | Messaging url patterns. 24 | """ 25 | -------------------------------------------------------------------------------- /api/src/messaging/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | 4 | # Todo: Remove this file later 5 | def index(request): 6 | return render(request, "index.html") 7 | 8 | 9 | def room(request, room_id): 10 | return render(request, "room.html", {"room_id": room_id}) 11 | -------------------------------------------------------------------------------- /api/src/notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/notifications/__init__.py -------------------------------------------------------------------------------- /api/src/notifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Notification, Device 3 | 4 | # Registers the Notification and Device models with the admin site 5 | admin.site.register(Notification) 6 | admin.site.register(Device) 7 | -------------------------------------------------------------------------------- /api/src/notifications/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NotificationsConfig(AppConfig): 5 | name = "notifications" 6 | -------------------------------------------------------------------------------- /api/src/notifications/factories.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/notifications/factories.py -------------------------------------------------------------------------------- /api/src/notifications/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:31 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("contenttypes", "0002_remove_content_type_name"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Device", 18 | fields=[ 19 | ("id", models.AutoField(primary_key=True, serialize=False)), 20 | ("name", models.CharField(max_length=50)), 21 | ("expo_push_token", models.CharField(max_length=50)), 22 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 23 | ], 24 | options={ 25 | "ordering": ["-datetime_created"], 26 | }, 27 | ), 28 | migrations.CreateModel( 29 | name="Notification", 30 | fields=[ 31 | ("id", models.AutoField(primary_key=True, serialize=False)), 32 | ("content", models.CharField(max_length=100)), 33 | ("object_id", models.PositiveIntegerField()), 34 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 35 | ( 36 | "content_type", 37 | models.ForeignKey( 38 | on_delete=django.db.models.deletion.CASCADE, 39 | to="contenttypes.contenttype", 40 | ), 41 | ), 42 | ], 43 | options={ 44 | "ordering": ["-datetime_created"], 45 | }, 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /api/src/notifications/migrations/0002_auto_20201128_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:32 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ("notifications", "0001_initial"), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="notification", 20 | name="creator", 21 | field=models.ForeignKey( 22 | blank=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | related_name="created_notifications", 25 | to=settings.AUTH_USER_MODEL, 26 | ), 27 | ), 28 | migrations.AddField( 29 | model_name="notification", 30 | name="receivers", 31 | field=models.ManyToManyField( 32 | blank=True, 33 | related_name="received_notifications", 34 | to=settings.AUTH_USER_MODEL, 35 | ), 36 | ), 37 | migrations.AddField( 38 | model_name="notification", 39 | name="viewers", 40 | field=models.ManyToManyField( 41 | blank=True, 42 | related_name="viewed_notifications", 43 | to=settings.AUTH_USER_MODEL, 44 | ), 45 | ), 46 | migrations.AddField( 47 | model_name="device", 48 | name="creator", 49 | field=models.ForeignKey( 50 | blank=True, 51 | on_delete=django.db.models.deletion.CASCADE, 52 | related_name="devices", 53 | to=settings.AUTH_USER_MODEL, 54 | ), 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /api/src/notifications/migrations/0003_notification_notification_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-07-05 16:02 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("notifications", "0002_auto_20201128_2031"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="notification", 15 | name="notification_type", 16 | field=models.PositiveSmallIntegerField( 17 | choices=[ 18 | (1, "Shared_Item_Likes"), 19 | (2, "Messages_Received"), 20 | (3, "Shared_Item_Comments"), 21 | (4, "Shared_Item_Tag"), 22 | (5, "Connection_Request"), 23 | ], 24 | default=1, 25 | ), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /api/src/notifications/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/notifications/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/notifications/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from users.models import CustomUser 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from django.contrib.contenttypes.models import ContentType 5 | from common.constants.choices import Choices 6 | 7 | 8 | class Notification(models.Model): 9 | """ 10 | A model class that represents a notification. 11 | """ 12 | 13 | id = models.AutoField(primary_key=True) 14 | content = models.CharField(max_length=100) 15 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 16 | object_id = models.PositiveIntegerField() 17 | content_object = GenericForeignKey("content_type", "object_id") 18 | creator = models.ForeignKey( 19 | to=CustomUser, 20 | related_name="created_notifications", 21 | on_delete=models.CASCADE, 22 | blank=True, 23 | ) 24 | datetime_created = models.DateTimeField(auto_now_add=True) 25 | receivers = models.ManyToManyField( 26 | to=CustomUser, related_name="received_notifications", blank=True 27 | ) 28 | viewers = models.ManyToManyField( 29 | to=CustomUser, related_name="viewed_notifications", blank=True 30 | ) 31 | notification_type = models.PositiveSmallIntegerField( 32 | choices=Choices.NOTIFICATION_CHOICES, default=Choices.LIKES_FIELD 33 | ) 34 | 35 | class Meta: 36 | ordering = ["-datetime_created"] 37 | 38 | def __str__(self): 39 | return f"{self.content}" 40 | 41 | 42 | class Device(models.Model): 43 | """ 44 | A model class that represents a device. 45 | """ 46 | 47 | id = models.AutoField(primary_key=True) 48 | name = models.CharField(max_length=50) 49 | expo_push_token = models.CharField(max_length=50) 50 | creator = models.ForeignKey( 51 | to=CustomUser, related_name="devices", on_delete=models.CASCADE, blank=True 52 | ) 53 | datetime_created = models.DateTimeField(auto_now_add=True) 54 | 55 | class Meta: 56 | ordering = ["-datetime_created"] 57 | 58 | def __str__(self): 59 | return f"{self.name}" 60 | -------------------------------------------------------------------------------- /api/src/notifications/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from common.serializer_extensions import ModelSerializerExtension 3 | from notifications.models import Device, Notification 4 | from users.models import CustomUser 5 | from users.serializers import BasicUserSerializer 6 | 7 | 8 | class NotificationSerializer(ModelSerializerExtension): 9 | """ 10 | A serializer class for the Notification model. 11 | """ 12 | 13 | creator = BasicUserSerializer(read_only=True) 14 | is_viewed = serializers.SerializerMethodField() 15 | 16 | class Meta: 17 | model = Notification 18 | fields = "__all__" 19 | exclude_fields = ["receivers", "viewers"] 20 | 21 | def get_is_viewed(self, instance: Notification) -> bool: 22 | return instance.viewers.filter(id=self.context["request"].user.id).exists() 23 | 24 | 25 | class DeviceSerializer(ModelSerializerExtension): 26 | """ 27 | A serializer class for the Device model. 28 | """ 29 | 30 | class Meta: 31 | model = Device 32 | fields = "__all__" 33 | 34 | creator = BasicUserSerializer(read_only=True) 35 | 36 | def create(self, validated_data): 37 | # Set the user as the creator of the shared item 38 | validated_data["creator"] = self.context["request"].user 39 | 40 | return ModelSerializerExtension.create(self, validated_data) 41 | 42 | 43 | class ReceivedNotificationsSerializer(ModelSerializerExtension): 44 | """ 45 | A serializer class for received notifications. 46 | """ 47 | 48 | class Meta: 49 | model = CustomUser 50 | fields = ["received_notifications"] 51 | -------------------------------------------------------------------------------- /api/src/notifications/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/src/notifications/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework.routers import DefaultRouter 2 | from django.urls import path, include 3 | from .api_views import ViewedReceivedNotifications, DeviceViewSet 4 | 5 | router = DefaultRouter(trailing_slash=False) 6 | router.register(r"", DeviceViewSet, basename="devices") 7 | 8 | urlpatterns = [ 9 | path("notifications/", ViewedReceivedNotifications.as_view()), 10 | path("devices/", include(router.urls)), 11 | ] 12 | """ 13 | Notifications url patterns. 14 | """ 15 | -------------------------------------------------------------------------------- /api/src/photos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/photos/__init__.py -------------------------------------------------------------------------------- /api/src/photos/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Photo 3 | 4 | 5 | # Register the Photo model with the admin site 6 | admin.site.register(Photo) 7 | -------------------------------------------------------------------------------- /api/src/photos/api_views.py: -------------------------------------------------------------------------------- 1 | from django.utils.decorators import method_decorator 2 | from drf_yasg.utils import swagger_auto_schema 3 | from .models import Photo 4 | from .serializers import PhotoSerializer 5 | from rest_framework.generics import CreateAPIView, RetrieveUpdateDestroyAPIView 6 | from rest_framework.permissions import IsAuthenticated 7 | 8 | 9 | @method_decorator(name="post", decorator=swagger_auto_schema(tags=["uploads"])) 10 | class PhotoCreate(CreateAPIView): 11 | queryset = Photo.objects.all() 12 | serializer_class = PhotoSerializer 13 | permission_classes = [ 14 | IsAuthenticated, 15 | ] 16 | 17 | 18 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["uploads"])) 19 | @method_decorator(name="put", decorator=swagger_auto_schema(tags=["uploads"])) 20 | @method_decorator(name="patch", decorator=swagger_auto_schema(tags=["uploads"])) 21 | @method_decorator(name="delete", decorator=swagger_auto_schema(tags=["uploads"])) 22 | class PhotoRetrieveUpdateDestroy(RetrieveUpdateDestroyAPIView): 23 | queryset = Photo.objects.all() 24 | serializer_class = PhotoSerializer 25 | lookup_field = "id" 26 | permission_classes = [ 27 | IsAuthenticated, 28 | ] 29 | -------------------------------------------------------------------------------- /api/src/photos/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PhotosConfig(AppConfig): 5 | name = "photos" 6 | -------------------------------------------------------------------------------- /api/src/photos/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:32 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("contenttypes", "0002_remove_content_type_name"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Photo", 18 | fields=[ 19 | ("id", models.AutoField(primary_key=True, serialize=False)), 20 | ("image", models.ImageField(blank=True, upload_to="images/photos")), 21 | ("object_id", models.PositiveIntegerField()), 22 | ( 23 | "content_type", 24 | models.ForeignKey( 25 | on_delete=django.db.models.deletion.CASCADE, 26 | to="contenttypes.contenttype", 27 | ), 28 | ), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /api/src/photos/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/photos/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/photos/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.contenttypes.fields import GenericForeignKey 3 | from django.contrib.contenttypes.models import ContentType 4 | 5 | 6 | class CustomManager(models.Manager): 7 | """ 8 | A custom manager for multiple deletions at once. 9 | """ 10 | 11 | def delete(self): 12 | for obj in self.get_queryset(): 13 | obj.delete() 14 | 15 | 16 | class Photo(models.Model): 17 | """ 18 | A model class that represents a photo. 19 | """ 20 | 21 | id = models.AutoField(primary_key=True) 22 | image = models.ImageField(upload_to="images/photos", blank=True) 23 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 24 | object_id = models.PositiveIntegerField() 25 | content_object = GenericForeignKey("content_type", "object_id") 26 | 27 | objects = CustomManager() 28 | 29 | def delete(self, using=None, keep_parents=False): 30 | self.image.delete() 31 | super().delete() 32 | 33 | def save(self, *args, **kwargs): 34 | if self.id is not None: 35 | db_instance: Photo = self.__class__.objects.get(id=self.id) 36 | 37 | if self.image != db_instance.image: 38 | db_instance.image.delete(save=False) 39 | 40 | super().save(*args, **kwargs) 41 | -------------------------------------------------------------------------------- /api/src/photos/serializers.py: -------------------------------------------------------------------------------- 1 | from common.serializer_extensions import ModelSerializerExtension 2 | from .models import Photo 3 | 4 | 5 | class PhotoSerializer(ModelSerializerExtension): 6 | """ 7 | A serializer class for the Photo model. 8 | """ 9 | 10 | class Meta: 11 | model = Photo 12 | fields = "__all__" 13 | 14 | 15 | class PhotoRetrieveSerializer(PhotoSerializer): 16 | """ 17 | A serializer class for the Photo model with only the image field. 18 | """ 19 | 20 | class Meta: 21 | model = Photo 22 | fields = "__all__" 23 | exclude_fields = ["id", "object_id", "content_type"] 24 | -------------------------------------------------------------------------------- /api/src/photos/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/src/photos/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .api_views import PhotoCreate, PhotoRetrieveUpdateDestroy 3 | 4 | urlpatterns = [ 5 | path("", PhotoCreate.as_view()), 6 | path("", PhotoRetrieveUpdateDestroy.as_view()), 7 | ] 8 | """ 9 | Photo url patterns. 10 | """ 11 | -------------------------------------------------------------------------------- /api/src/polls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/polls/__init__.py -------------------------------------------------------------------------------- /api/src/polls/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Poll, Option, Vote 3 | 4 | 5 | # Register the pol, option, and vote models with the admin site 6 | admin.site.register(Poll) 7 | admin.site.register(Option) 8 | admin.site.register(Vote) 9 | -------------------------------------------------------------------------------- /api/src/polls/api_views.py: -------------------------------------------------------------------------------- 1 | from common.generics.generic_post_api_views import GenericPostListCreate 2 | from common.abstract_api_views import ( 3 | AbstractModelViewSet, 4 | AbstractAddRemoveUser, 5 | AbstractLikedUsers, 6 | ) 7 | from .filters import PollFilterSet, OptionFilterSet, VoteFilterSet 8 | from .models import Poll, Option, Vote 9 | from .serializers import ( 10 | PollSerializer, 11 | PollDetailSerializer, 12 | OptionSerializer, 13 | VoteSerializer, 14 | ) 15 | from django.utils.decorators import method_decorator 16 | from drf_yasg.utils import swagger_auto_schema 17 | from common.decorators import ( 18 | model_view_set_swagger_decorator, 19 | api_view_swagger_decorator, 20 | ) 21 | 22 | 23 | @model_view_set_swagger_decorator(["Polls"]) 24 | class PollViewSet(AbstractModelViewSet): 25 | queryset = Poll.objects.all() 26 | serializer_class = PollSerializer 27 | detail_serializer_class = PollDetailSerializer 28 | filterset_class = PollFilterSet 29 | 30 | 31 | @api_view_swagger_decorator(["Polls"]) 32 | class LikedPolls(AbstractAddRemoveUser): 33 | query_string = "liked_polls" 34 | serializer_class = PollSerializer 35 | model_class = Poll 36 | 37 | 38 | @api_view_swagger_decorator(["Polls"]) 39 | class SavedPolls(AbstractAddRemoveUser): 40 | query_string = "saved_polls" 41 | serializer_class = PollSerializer 42 | model_class = Poll 43 | 44 | 45 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["Polls"])) 46 | class PollLikes(AbstractLikedUsers): 47 | model_class = Poll 48 | 49 | 50 | @api_view_swagger_decorator(["Polls"]) 51 | class OptionListCreate(GenericPostListCreate): 52 | queryset = Option.objects.all() 53 | serializer_class = OptionSerializer 54 | filterset_class = OptionFilterSet 55 | detail_serializer_class = OptionSerializer 56 | 57 | 58 | @api_view_swagger_decorator(["Polls"]) 59 | class VoteListCreate(GenericPostListCreate): 60 | queryset = Vote.objects.all() 61 | serializer_class = VoteSerializer 62 | filterset_class = VoteFilterSet 63 | detail_serializer_class = VoteSerializer 64 | -------------------------------------------------------------------------------- /api/src/polls/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PollsConfig(AppConfig): 5 | name = "polls" 6 | -------------------------------------------------------------------------------- /api/src/polls/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from common.abstract_factories import AbstractFactory 3 | from .models import Poll, Option, Vote 4 | from users.factories import UserFactory 5 | 6 | 7 | class PollFactory(AbstractFactory): 8 | class Meta: 9 | model = Poll 10 | 11 | 12 | class OptionFactory(factory.django.DjangoModelFactory): 13 | class Meta: 14 | model = Option 15 | 16 | content = factory.Faker("sentence", nb_words=3) 17 | creator = factory.SubFactory(UserFactory) 18 | poll = factory.SubFactory(PollFactory) 19 | 20 | 21 | class VoteFactory(factory.django.DjangoModelFactory): 22 | class Meta: 23 | model = Vote 24 | 25 | creator = factory.SubFactory(UserFactory) 26 | option = factory.SubFactory(OptionFactory) 27 | -------------------------------------------------------------------------------- /api/src/polls/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Poll, Option, Vote 3 | 4 | 5 | class PollFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the Poll model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | 17 | class Meta: 18 | model = Poll 19 | fields = [ 20 | "creator", 21 | "min_datetime_created", 22 | "max_datetime_created", 23 | "community", 24 | "is_multiselect", 25 | "is_public", 26 | ] 27 | 28 | 29 | class OptionFilterSet(filters.FilterSet): 30 | """ 31 | A filter set class for the Option model. 32 | """ 33 | 34 | class Meta: 35 | model = Option 36 | fields = [ 37 | "poll", 38 | ] 39 | 40 | 41 | class VoteFilterSet(filters.FilterSet): 42 | """ 43 | A filter set class for the Vote model. 44 | """ 45 | 46 | class Meta: 47 | model = Vote 48 | fields = [ 49 | "option", 50 | ] 51 | -------------------------------------------------------------------------------- /api/src/polls/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Option", 15 | fields=[ 16 | ("id", models.AutoField(primary_key=True, serialize=False)), 17 | ("content", models.CharField(max_length=100)), 18 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 19 | ], 20 | options={ 21 | "ordering": ["-datetime_created"], 22 | }, 23 | ), 24 | migrations.CreateModel( 25 | name="Poll", 26 | fields=[ 27 | ("id", models.AutoField(primary_key=True, serialize=False)), 28 | ("title", models.CharField(max_length=100)), 29 | ("content", models.CharField(blank=True, max_length=1000)), 30 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 31 | ( 32 | "community", 33 | models.PositiveSmallIntegerField( 34 | choices=[ 35 | (0, "Waterloo"), 36 | (1, "Toronto"), 37 | (2, "Brampton"), 38 | (3, "Ottawa"), 39 | ] 40 | ), 41 | ), 42 | ("is_multiselect", models.BooleanField(default=False)), 43 | ("is_public", models.BooleanField(default=False)), 44 | ], 45 | options={ 46 | "ordering": ["-datetime_created"], 47 | "abstract": False, 48 | }, 49 | ), 50 | migrations.CreateModel( 51 | name="Vote", 52 | fields=[ 53 | ("id", models.AutoField(primary_key=True, serialize=False)), 54 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 55 | ], 56 | options={ 57 | "ordering": ["-datetime_created"], 58 | }, 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /api/src/polls/migrations/0003_poll_is_anonymous.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-05-17 18:11 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("polls", "0002_auto_20201128_2031"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="poll", 15 | name="is_anonymous", 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /api/src/polls/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/polls/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/polls/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from common.abstract_models import AbstractPostModel 3 | from common.model_extensions import PhotoCollectionExtension 4 | from users.models import CustomUser 5 | 6 | 7 | class Poll(AbstractPostModel, PhotoCollectionExtension): 8 | """ 9 | A model class that represents a poll. 10 | """ 11 | 12 | is_multiselect = models.BooleanField(default=False) 13 | is_public = models.BooleanField(default=False) 14 | is_anonymous = models.BooleanField(default=False) 15 | 16 | 17 | class Option(models.Model): 18 | """ 19 | A model class that represents a poll option. 20 | """ 21 | 22 | id = models.AutoField(primary_key=True) 23 | content = models.CharField(max_length=100) 24 | creator = models.ForeignKey( 25 | to=CustomUser, 26 | related_name="created_options", 27 | on_delete=models.CASCADE, 28 | blank=True, 29 | ) 30 | datetime_created = models.DateTimeField(auto_now_add=True) 31 | poll = models.ForeignKey(to=Poll, related_name="options", on_delete=models.CASCADE) 32 | 33 | class Meta: 34 | ordering = ["-datetime_created"] 35 | 36 | def __str__(self): 37 | return f"{self.content}" 38 | 39 | 40 | class Vote(models.Model): 41 | """ 42 | A model class that represents an option vote. 43 | """ 44 | 45 | id = models.AutoField(primary_key=True) 46 | creator = models.ForeignKey( 47 | to=CustomUser, 48 | related_name="created_votes", 49 | on_delete=models.CASCADE, 50 | blank=True, 51 | ) 52 | datetime_created = models.DateTimeField(auto_now_add=True) 53 | option = models.ForeignKey( 54 | to=Option, related_name="votes", on_delete=models.CASCADE 55 | ) 56 | 57 | class Meta: 58 | ordering = ["-datetime_created"] 59 | 60 | def __str__(self): 61 | return f"{str(self.creator)} voted for {str(self.option)} in {str(self.option.poll)}" 62 | -------------------------------------------------------------------------------- /api/src/polls/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ( 2 | AbstractPostSerializer, 3 | AbstractPostDetailSerializer, 4 | AbstractCreatorSerializer, 5 | ) 6 | from .models import Poll, Option, Vote 7 | 8 | 9 | class VoteSerializer(AbstractCreatorSerializer): 10 | """ 11 | A serializer class for the Vote model. 12 | """ 13 | 14 | class Meta: 15 | model = Vote 16 | fields = "__all__" 17 | 18 | 19 | class OptionSerializer(AbstractCreatorSerializer): 20 | """ 21 | A serializer class for the option model. 22 | """ 23 | 24 | vote_set = VoteSerializer(read_only=True, many=True) 25 | 26 | class Meta: 27 | model = Option 28 | fields = "__all__" 29 | extra_fields = [ 30 | "votes", 31 | ] 32 | 33 | 34 | class PollSerializer(AbstractPostSerializer): 35 | """ 36 | A serializer class for the Poll model. 37 | """ 38 | 39 | option_set = OptionSerializer(read_only=True, many=True) 40 | 41 | class Meta: 42 | model = Poll 43 | fields = "__all__" 44 | extra_fields = [ 45 | "options", 46 | ] 47 | exclude_fields = ["saved_users", "liked_users"] 48 | 49 | 50 | class PollDetailSerializer(PollSerializer, AbstractPostDetailSerializer): 51 | """ 52 | A detailed serializer class for the Poll model. 53 | """ 54 | 55 | pass 56 | -------------------------------------------------------------------------------- /api/src/polls/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import ( 4 | PollViewSet, 5 | LikedPolls, 6 | SavedPolls, 7 | PollLikes, 8 | OptionListCreate, 9 | VoteListCreate, 10 | ) 11 | 12 | router = DefaultRouter(trailing_slash=False) 13 | router.register(r"", PollViewSet, basename="polls") 14 | 15 | urlpatterns = router.urls + [ 16 | path("liked", LikedPolls.as_view()), 17 | path("saved", SavedPolls.as_view()), 18 | path("/likes", PollLikes.as_view()), 19 | path("options/", OptionListCreate.as_view()), 20 | path("options/votes/", VoteListCreate.as_view()), 21 | ] 22 | """ 23 | Polls url patterns. 24 | """ 25 | -------------------------------------------------------------------------------- /api/src/posts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/posts/__init__.py -------------------------------------------------------------------------------- /api/src/posts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post 3 | 4 | 5 | # Register the Post model with the admin site 6 | admin.site.register(Post) 7 | -------------------------------------------------------------------------------- /api/src/posts/api_views.py: -------------------------------------------------------------------------------- 1 | from common.abstract_api_views import ( 2 | AbstractModelViewSet, 3 | AbstractAddRemoveUser, 4 | AbstractLikedUsers, 5 | ) 6 | from .filters import PostFilterSet 7 | from .models import Post 8 | from .serializers import PostSerializer, PostDetailSerializer 9 | from django.utils.decorators import method_decorator 10 | from drf_yasg.utils import swagger_auto_schema 11 | from common.decorators import ( 12 | model_view_set_swagger_decorator, 13 | api_view_swagger_decorator, 14 | ) 15 | 16 | 17 | @model_view_set_swagger_decorator(["Posts"]) 18 | class PostViewSet(AbstractModelViewSet): 19 | queryset = Post.objects.all() 20 | serializer_class = PostSerializer 21 | detail_serializer_class = PostDetailSerializer 22 | filterset_class = PostFilterSet 23 | 24 | 25 | @api_view_swagger_decorator(["Posts"]) 26 | class LikedPosts(AbstractAddRemoveUser): 27 | query_string = "liked_posts" 28 | serializer_class = PostSerializer 29 | model_class = Post 30 | 31 | 32 | @api_view_swagger_decorator(["Posts"]) 33 | class SavedPosts(AbstractAddRemoveUser): 34 | query_string = "saved_posts" 35 | serializer_class = PostSerializer 36 | model_class = Post 37 | 38 | 39 | @method_decorator(name="get", decorator=swagger_auto_schema(tags=["Posts"])) 40 | class PostLikes(AbstractLikedUsers): 41 | model_class = Post 42 | -------------------------------------------------------------------------------- /api/src/posts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PostsConfig(AppConfig): 5 | name = "posts" 6 | -------------------------------------------------------------------------------- /api/src/posts/factories.py: -------------------------------------------------------------------------------- 1 | from common.abstract_factories import AbstractFactory 2 | from .models import Post 3 | 4 | 5 | class PostFactory(AbstractFactory): 6 | class Meta: 7 | model = Post 8 | -------------------------------------------------------------------------------- /api/src/posts/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Post 3 | 4 | 5 | class PostFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the Post model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | 17 | class Meta: 18 | model = Post 19 | fields = [ 20 | "creator", 21 | "min_datetime_created", 22 | "max_datetime_created", 23 | "community", 24 | ] 25 | -------------------------------------------------------------------------------- /api/src/posts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Post", 15 | fields=[ 16 | ("id", models.AutoField(primary_key=True, serialize=False)), 17 | ("title", models.CharField(max_length=100)), 18 | ("content", models.CharField(blank=True, max_length=1000)), 19 | ("datetime_created", models.DateTimeField(auto_now_add=True)), 20 | ( 21 | "community", 22 | models.PositiveSmallIntegerField( 23 | choices=[ 24 | (0, "Waterloo"), 25 | (1, "Toronto"), 26 | (2, "Brampton"), 27 | (3, "Ottawa"), 28 | ] 29 | ), 30 | ), 31 | ], 32 | options={ 33 | "ordering": ["-datetime_created"], 34 | "abstract": False, 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /api/src/posts/migrations/0002_auto_20201128_2031.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.3 on 2020-11-29 01:32 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ("posts", "0001_initial"), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="post", 20 | name="creator", 21 | field=models.ForeignKey( 22 | blank=True, 23 | on_delete=django.db.models.deletion.CASCADE, 24 | related_name="created_posts", 25 | to=settings.AUTH_USER_MODEL, 26 | ), 27 | ), 28 | migrations.AddField( 29 | model_name="post", 30 | name="liked_users", 31 | field=models.ManyToManyField( 32 | blank=True, related_name="liked_posts", to=settings.AUTH_USER_MODEL 33 | ), 34 | ), 35 | migrations.AddField( 36 | model_name="post", 37 | name="saved_users", 38 | field=models.ManyToManyField( 39 | blank=True, related_name="saved_posts", to=settings.AUTH_USER_MODEL 40 | ), 41 | ), 42 | migrations.AddField( 43 | model_name="post", 44 | name="tagged_users", 45 | field=models.ManyToManyField( 46 | blank=True, related_name="tagged_posts", to=settings.AUTH_USER_MODEL 47 | ), 48 | ), 49 | ] 50 | -------------------------------------------------------------------------------- /api/src/posts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/posts/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/posts/models.py: -------------------------------------------------------------------------------- 1 | from common.abstract_models import AbstractPostModel 2 | from common.model_extensions import PhotoCollectionExtension 3 | 4 | 5 | class Post(AbstractPostModel, PhotoCollectionExtension): 6 | """ 7 | A model class that represents a post. 8 | """ 9 | 10 | pass 11 | -------------------------------------------------------------------------------- /api/src/posts/serializers.py: -------------------------------------------------------------------------------- 1 | from common.abstract_serializers import ( 2 | AbstractPostSerializer, 3 | AbstractPostDetailSerializer, 4 | ) 5 | from .models import Post 6 | 7 | 8 | class PostSerializer(AbstractPostSerializer): 9 | """ 10 | A serializer class for the Post model. 11 | """ 12 | 13 | class Meta: 14 | model = Post 15 | fields = "__all__" 16 | exclude_fields = ["saved_users", "liked_users"] 17 | 18 | 19 | class PostDetailSerializer(PostSerializer, AbstractPostDetailSerializer): 20 | """ 21 | A detailed serializer class for the Post model. 22 | """ 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /api/src/posts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.routers import DefaultRouter 3 | from .api_views import PostViewSet, LikedPosts, SavedPosts, PostLikes 4 | 5 | router = DefaultRouter(trailing_slash=False) 6 | router.register(r"", PostViewSet, basename="posts") 7 | 8 | urlpatterns = router.urls + [ 9 | path("liked", LikedPosts.as_view()), 10 | path("saved", SavedPosts.as_view()), 11 | path("/likes", PostLikes.as_view()), 12 | ] 13 | """ 14 | Posts url patterns. 15 | """ 16 | -------------------------------------------------------------------------------- /api/src/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = umigrate.settings 3 | python_files = tests.py tests_*.py 4 | -------------------------------------------------------------------------------- /api/src/registration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/registration/__init__.py -------------------------------------------------------------------------------- /api/src/registration/urls.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | from django.conf.urls import url 3 | from .views import RegistrationView 4 | from rest_auth.registration.views import VerifyEmailView 5 | 6 | urlpatterns = [ 7 | url(r"^$", RegistrationView.as_view(), name="rest_register"), 8 | url(r"^verify-email/$", VerifyEmailView.as_view(), name="rest_verify_email"), 9 | url( 10 | r"^account-confirm-email/(?P[-:\w]+)/$", 11 | TemplateView.as_view(), 12 | name="account_confirm_email", 13 | ), 14 | ] 15 | """ 16 | Registration url patterns 17 | """ 18 | -------------------------------------------------------------------------------- /api/src/requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | djangorestframework 3 | django-rest-auth 4 | django-allauth 5 | django-axes 6 | django-filter 7 | requests 8 | Pillow 9 | django-cors-headers 10 | channels 11 | channels-redis 12 | exponent_server_sdk 13 | sendgrid 14 | drf_yasg 15 | model_bakery 16 | factory_boy 17 | pytest-django 18 | uwaterloodriver 19 | psycopg2-binary 20 | gunicorn 21 | daphne 22 | sentry-sdk 23 | black 24 | pylint-django 25 | django-rq 26 | django-multiselectfield -------------------------------------------------------------------------------- /api/src/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/__init__.py -------------------------------------------------------------------------------- /api/src/tests/api_views_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/api_views_tests/__init__.py -------------------------------------------------------------------------------- /api/src/tests/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TestsConfig(AppConfig): 5 | name = "tests" 6 | -------------------------------------------------------------------------------- /api/src/tests/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/tests/models_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/models_tests/__init__.py -------------------------------------------------------------------------------- /api/src/tests/serializers_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/serializers_tests/__init__.py -------------------------------------------------------------------------------- /api/src/tests/utils_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/tests/utils_tests/__init__.py -------------------------------------------------------------------------------- /api/src/umigrate/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/umigrate/__init__.py -------------------------------------------------------------------------------- /api/src/umigrate/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for umigrate project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | import django 12 | from channels.routing import get_default_application 13 | from sentry_sdk.integrations.asgi import SentryAsgiMiddleware 14 | from sentry_sdk.integrations.django import DjangoIntegration 15 | import sentry_sdk 16 | 17 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umigrate.settings") 18 | 19 | django.setup() 20 | 21 | # Sentry Initialization 22 | sentry_sdk.init( 23 | dsn="https://a4946255ae774d7e9c0dd8b5adfa9526@o442315.ingest.sentry.io/5413903", 24 | # integrations=[DjangoIntegration()], 25 | # traces_sample_rate=1.0, 26 | # If you wish to associate users to errors (assuming you are using 27 | # django.contrib.auth) you may enable sending PII data. 28 | # send_default_pii=True, 29 | ) 30 | 31 | application = get_default_application() 32 | application = SentryAsgiMiddleware(application) 33 | -------------------------------------------------------------------------------- /api/src/umigrate/routing.py: -------------------------------------------------------------------------------- 1 | from channels.routing import ProtocolTypeRouter, URLRouter 2 | import messaging.routing 3 | from channels.auth import AuthMiddlewareStack 4 | 5 | application = ProtocolTypeRouter( 6 | { 7 | "websocket": AuthMiddlewareStack( 8 | URLRouter(messaging.routing.websocket_urlpatterns) 9 | ) 10 | } 11 | ) 12 | """ 13 | Routing for asynchronous connections. 14 | """ 15 | -------------------------------------------------------------------------------- /api/src/umigrate/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for umigrate project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | from sentry_sdk.integrations.django import DjangoIntegration 14 | import sentry_sdk 15 | 16 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umigrate.settings") 17 | 18 | # Sentry Initialization 19 | sentry_sdk.init( 20 | dsn="https://a4946255ae774d7e9c0dd8b5adfa9526@o442315.ingest.sentry.io/5413903", 21 | integrations=[DjangoIntegration()], 22 | # traces_sample_rate=1.0, 23 | # If you wish to associate users to errors (assuming you are using 24 | # django.contrib.auth) you may enable sending PII data. 25 | # send_default_pii=True, 26 | ) 27 | 28 | application = get_wsgi_application() 29 | -------------------------------------------------------------------------------- /api/src/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/users/__init__.py -------------------------------------------------------------------------------- /api/src/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import CustomUser 3 | 4 | 5 | # Register the user model with the admin site 6 | admin.site.register(CustomUser) 7 | -------------------------------------------------------------------------------- /api/src/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = "users" 6 | -------------------------------------------------------------------------------- /api/src/users/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from users.models import CustomUser 3 | 4 | 5 | class UserFilterSet(filters.FilterSet): 6 | """ 7 | A filter set class for the User model. 8 | """ 9 | 10 | min_datetime_created = filters.DateTimeFilter( 11 | field_name="datetime_created", lookup_expr="gte" 12 | ) 13 | max_datetime_created = filters.DateTimeFilter( 14 | field_name="datetime_created", lookup_expr="lte" 15 | ) 16 | 17 | class Meta: 18 | model = CustomUser 19 | fields = [ 20 | "min_datetime_created", 21 | "max_datetime_created", 22 | "community", 23 | "pronouns", 24 | "current_term", 25 | "enrolled_program", 26 | ] 27 | -------------------------------------------------------------------------------- /api/src/users/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/users/management/__init__.py -------------------------------------------------------------------------------- /api/src/users/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/users/management/commands/__init__.py -------------------------------------------------------------------------------- /api/src/users/migrations/0002_auto_20210703_1814.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-07-03 22:14 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("users", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="customuser", 16 | name="blocked_users", 17 | field=models.ManyToManyField( 18 | blank=True, 19 | related_name="blocked_users_set", 20 | to=settings.AUTH_USER_MODEL, 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="customuser", 25 | name="connected_users", 26 | field=models.ManyToManyField( 27 | blank=True, 28 | related_name="connected_users_set", 29 | to=settings.AUTH_USER_MODEL, 30 | ), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /api/src/users/migrations/0003_customuser_notification_preferences.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2 on 2021-07-05 16:02 2 | 3 | from django.db import migrations 4 | import multiselectfield.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("users", "0002_auto_20210703_1814"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="customuser", 16 | name="notification_preferences", 17 | field=multiselectfield.db.fields.MultiSelectField( 18 | blank=True, 19 | choices=[ 20 | (1, "Shared_Item_Likes"), 21 | (2, "Messages_Received"), 22 | (3, "Shared_Item_Comments"), 23 | (4, "Shared_Item_Tag"), 24 | (5, "Connection_Request"), 25 | ], 26 | default="1,2,3,4,5", 27 | max_length=9, 28 | null=True, 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /api/src/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/api/src/users/migrations/__init__.py -------------------------------------------------------------------------------- /api/src/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .api_views import ( 3 | UserList, 4 | UserRetrieve, 5 | ConnectUser, 6 | BlockUser, 7 | NotificationPreferences, 8 | ) 9 | 10 | # Users url patterns 11 | urlpatterns = [ 12 | path("", UserList.as_view()), 13 | path("notification_preferences", NotificationPreferences.as_view()), 14 | path("", UserRetrieve.as_view()), 15 | path("connect", ConnectUser.as_view()), 16 | path("block", BlockUser.as_view()), 17 | ] 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | debuggerWorker.js 5 | 6 | # Coverage directory used by tools like istanbul 7 | coverage/ 8 | 9 | *.jks 10 | *.p8 11 | *.p12 12 | *.key 13 | *.mobileprovision 14 | *.orig.* 15 | web-build/ 16 | 17 | # macOS 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # uMigrate 2 | 3 | Source code for the uMigrate app 4 | -------------------------------------------------------------------------------- /app/deployment/scripts/InstallExpo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm install -g expo-cli 3 | 4 | -------------------------------------------------------------------------------- /app/deployment/scripts/Publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=$1 3 | SUPERUSER_PASSWORD=$2 4 | 5 | cd $DIR/_umigrate-app/drop 6 | expo login -u teamumigrate -p $SUPERUSER_PASSWORD 7 | expo publish 8 | 9 | -------------------------------------------------------------------------------- /app/src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | // 'plugin:react/recommended', 8 | // 'airbnb', 9 | 'prettier', 10 | ], 11 | parser: 'babel-eslint', 12 | parserOptions: { 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | ecmaVersion: 12, 17 | sourceType: 'module', 18 | }, 19 | plugins: ['react'], 20 | rules: { 21 | 'react/jsx-filename-extension': [ 22 | 1, 23 | { 24 | extensions: ['.js', '.jsx'], 25 | }, 26 | ], 27 | 'linebreak-style': ['error', 'windows'], 28 | 'max-classes-per-file': ['error', 16], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /app/src/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "c89faa98648b428fd6fb1634bcc7384fb9f5f7bfaeceb290209a9558f18bc915": true, 3 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true, 4 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true 5 | } 6 | -------------------------------------------------------------------------------- /app/src/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'es5', 4 | endOfLine: 'crlf', 5 | }; 6 | -------------------------------------------------------------------------------- /app/src/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "dbaeumer.vscode-eslint", 5 | "msjsdiag.vscode-react-native", 6 | "visualstudioexptteam.vscodeintellicode", 7 | "xabikos.javascriptsnippets", 8 | "eg2.vscode-npm-script", 9 | "christian-kohler.npm-intellisense", 10 | "christian-kohler.path-intellisense" 11 | ] 12 | } -------------------------------------------------------------------------------- /app/src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug in Exponent", 5 | "cwd": "${workspaceFolder}", 6 | "type": "reactnative", 7 | "request": "launch", 8 | "platform": "exponent" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /app/src/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "eslint.alwaysShowStatus": true, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "[javascript]": { 7 | "editor.formatOnSave": false 8 | }, 9 | "editor.codeActionsOnSave": { 10 | "source.fixAll.eslint": true 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /app/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StatusBar } from 'react-native'; 3 | import { AuthContextProvider } from './src/contexts/AuthContext'; 4 | import { Provider as PaperProvider } from 'react-native-paper'; 5 | import AuthNavigator from './src/navigators/AuthNavigator'; 6 | import { ErrorContextProvider } from './src/contexts/ErrorContext'; 7 | 8 | /** 9 | * Renders the app. 10 | * @return {JSX.Element} 11 | */ 12 | const App = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /app/src/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "umigrate", 4 | "description": "The uMigrate App!", 5 | "slug": "umigrate", 6 | "version": "1.0.0", 7 | "orientation": "portrait", 8 | "icon": "./assets/icon.png", 9 | "splash": { 10 | "image": "./assets/favicon.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "updates": { 15 | "fallbackToCacheTimeout": 0 16 | }, 17 | "assetBundlePatterns": [ 18 | "**/*" 19 | ], 20 | "ios": { 21 | "supportsTablet": true 22 | }, 23 | "android": { 24 | "adaptiveIcon": { 25 | "foregroundImage": "./assets/adaptive-icon.png", 26 | "backgroundColor": "#FFFFFF" 27 | } 28 | }, 29 | "web": { 30 | "favicon": "./assets/favicon.png" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/adaptive-icon.png -------------------------------------------------------------------------------- /app/src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/favicon.png -------------------------------------------------------------------------------- /app/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/icon.png -------------------------------------------------------------------------------- /app/src/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/splash.png -------------------------------------------------------------------------------- /app/src/assets/templatedLogin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/templatedLogin.png -------------------------------------------------------------------------------- /app/src/assets/templatedRegister.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/assets/templatedRegister.png -------------------------------------------------------------------------------- /app/src/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['@babel/plugin-transform-flow-strip-types'], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /app/src/src/components/buttons/MenuLogout.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Platform } from 'react-native'; 3 | import { IconButton } from 'react-native-paper'; 4 | import { AuthEndpoint } from '../../utils/endpoints'; 5 | import AuthContext from '../../contexts/AuthContext'; 6 | 7 | const MenuLogout = () => { 8 | const auth = useContext(AuthContext); 9 | 10 | const handleSignOut = async () => { 11 | await AuthEndpoint.logout(); 12 | auth.setIsAuthenticated(false); 13 | }; 14 | 15 | return ( 16 | 22 | ); 23 | }; 24 | 25 | export default MenuLogout; 26 | -------------------------------------------------------------------------------- /app/src/src/components/buttons/PostTypeOptionsButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | import { Button } from 'react-native-paper'; 4 | 5 | const PostTypeOptionsButton = ({ 6 | title, 7 | selectedPostType, 8 | setSelectedPostType, 9 | }) => { 10 | return ( 11 | 22 | ); 23 | }; 24 | 25 | export default PostTypeOptionsButton; 26 | 27 | const styles = StyleSheet.create({ 28 | postTypeOptionsButton: { 29 | flex: 1, 30 | elevation: 10, 31 | borderRadius: 15, 32 | marginHorizontal: 3, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /app/src/src/components/buttons/ShowRepliesButton.jsx: -------------------------------------------------------------------------------- 1 | import { Text, TouchableHighlight, StyleSheet } from 'react-native'; 2 | import React from 'react'; 3 | 4 | // My current plan is for this component to accept 2 callbacks: 5 | // one to fetch more replies and another to collapse the list of replies in the CommentView component 6 | const ShowRepliesButton = ({ 7 | buttonVisible, 8 | fetchReplies, 9 | collapseReplies, //TODO add functionality for this 10 | }) => { 11 | if (buttonVisible) { 12 | return ( 13 | 14 | Show more replies 15 | 16 | ); 17 | } else return null; 18 | }; 19 | 20 | export default ShowRepliesButton; 21 | 22 | const styles = StyleSheet.create({ 23 | buttonText: { 24 | color: 'blue', 25 | textDecorationLine: 'underline', 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /app/src/src/components/common/BasicModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Modal, View } from 'react-native'; 3 | import PickADayModal from '../modals/PickADayModal'; 4 | 5 | // Basic outline for a sliding-up modal 6 | const BasicModal = ({ visible, setVisible, title, type, setType, setDate }) => { 7 | return ( 8 | 9 | setVisible(false)} 15 | > 16 | 25 | 26 | 27 | ); 28 | }; 29 | export default BasicModal; 30 | 31 | const styles = StyleSheet.create({ 32 | centeredView: { 33 | justifyContent: 'center', 34 | alignItems: 'center', 35 | marginTop: '90%', 36 | marginBottom: '2%', 37 | }, 38 | modalView: { 39 | height: '90%', 40 | width: '90%', 41 | top: '15%', 42 | backgroundColor: 'white', 43 | borderRadius: 20, 44 | padding: 35, 45 | alignItems: 'center', 46 | shadowColor: '#000', 47 | shadowOffset: { 48 | width: 0, 49 | height: 2, 50 | }, 51 | shadowOpacity: 0.25, 52 | shadowRadius: 3.84, 53 | elevation: 5, 54 | }, 55 | modalText: { 56 | marginBottom: 15, 57 | textAlign: 'center', 58 | }, 59 | modalButton: { 60 | justifyContent: 'space-evenly', 61 | flexWrap: 'wrap', 62 | flexDirection: 'row', 63 | }, 64 | modalPicker: { 65 | height: '50%', 66 | width: '100%', 67 | }, 68 | modalOptions: { 69 | marginBottom: 15, 70 | textAlign: 'center', 71 | }, 72 | openButton: { 73 | backgroundColor: '#007CFF', 74 | borderRadius: 20, 75 | padding: 10, 76 | elevation: 2, 77 | }, 78 | textStyle: { 79 | color: 'white', 80 | fontWeight: 'bold', 81 | textAlign: 'center', 82 | }, 83 | }); 84 | -------------------------------------------------------------------------------- /app/src/src/components/common/ButtonWithDownArrow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, TouchableWithoutFeedback, View, StyleSheet } from 'react-native'; 3 | import { Card } from 'react-native-paper'; 4 | import { Entypo } from '@expo/vector-icons'; 5 | 6 | // A button styled to look like the button with 7 | // the down arrow shown in the figma. 8 | const ButtonWithDownArrow = ({ onPress, text }) => { 9 | return ( 10 | 11 | 12 | 13 | {/* Todo montserrat here */} 14 | {text} 15 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default ButtonWithDownArrow; 28 | 29 | const styles = StyleSheet.create({ 30 | cardStyle: { 31 | padding: 5, 32 | paddingLeft: 10, 33 | elevation: 5, 34 | borderRadius: 10, 35 | }, 36 | viewStyle: { 37 | flexDirection: 'row', 38 | justifyContent: 'space-between', 39 | }, 40 | textStyle: { fontSize: 14, color: '#8781D0' }, 41 | }); 42 | -------------------------------------------------------------------------------- /app/src/src/components/common/CreatePageTextInput.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TextInput, StyleSheet } from 'react-native'; 3 | import { IconButton } from 'react-native-paper'; 4 | 5 | // Customized text input component for the create pages 6 | // Made to reduce copy-pasted code in the create pages by a bit 7 | const CreatePageTextInput = ({ 8 | textValue, 9 | setText, 10 | placeholder, 11 | multiline = false, 12 | numberOfLines = 1, 13 | style, 14 | edit, 15 | profileEdit, 16 | maxlength, 17 | textDefault, 18 | }) => { 19 | return ( 20 | 36 | ); 37 | }; 38 | 39 | export default CreatePageTextInput; 40 | 41 | const styles = StyleSheet.create({ 42 | basicTextInput: { 43 | flex: 1, 44 | marginTop: 10, 45 | borderRadius: 10, 46 | padding: 3, 47 | paddingLeft: '5%', 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /app/src/src/components/common/FeedHeader.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | import { IconButton } from 'react-native-paper'; 4 | import StackNavContext from '../../contexts/StackNavContext'; 5 | import { routes } from '../../utils/routes'; 6 | 7 | const FeedHeader = ({ feedName }) => { 8 | const nav = useContext(StackNavContext); 9 | 10 | return ( 11 | 12 | {feedName} 13 | nav.navigation.push(routes.search)} 19 | /> 20 | {/* TODO: Filter Bar */} 21 | 22 | ); 23 | }; 24 | 25 | export default FeedHeader; 26 | 27 | const styles = StyleSheet.create({ 28 | viewStyle: { 29 | flex: 1, 30 | flexDirection: 'row', 31 | justifyContent: 'flex-start', 32 | paddingTop: 0, 33 | backgroundColor: 'white', 34 | }, 35 | textStyle: { 36 | paddingLeft: '5%', 37 | fontSize: 25, 38 | color: 'black', 39 | alignSelf: 'center', 40 | textAlign: 'center', 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /app/src/src/components/common/PickImage.jsx: -------------------------------------------------------------------------------- 1 | import * as ImagePicker from 'expo-image-picker'; 2 | import * as ImageManipulator from 'expo-image-manipulator'; 3 | import { Platform } from 'react-native'; 4 | /* Select an image from the camera roll. Returns the local uri to the image. 5 | The local uri can also be used on the page without saving it, so if the "undo changes" 6 | or "back" buttons are pressed, then the image isn't saved (unless you press the "save" button) 7 | Quality and compress are both 0 for ios so high quality images can be saved without error (not really noticable 8 | difference in the lower quality on ios). 9 | On android, the quality/compress of the images uploaded don't match the quality/compress on ios, but can 10 | be saved with higher quality and compress values (0.5 max - pretty much same quality as ios) without error. 11 | */ 12 | 13 | const pickImage = async ({ set }) => { 14 | const result = await ImagePicker.launchImageLibraryAsync({ 15 | mediaTypes: ImagePicker.MediaTypeOptions.Images, 16 | allowsEditing: true, 17 | aspect: [1, 1], 18 | quality: Platform.OS === 'android' ? 0.5 : 0, 19 | }); 20 | 21 | if (!result.cancelled) { 22 | const manipulatedImage = await ImageManipulator.manipulateAsync( 23 | result.uri, 24 | [], 25 | { 26 | compress: Platform.OS === 'android' ? 0.5 : 0, 27 | format: ImageManipulator.SaveFormat.JPEG, 28 | } 29 | ); 30 | set(manipulatedImage.uri); 31 | return; 32 | } 33 | }; 34 | 35 | export default pickImage; 36 | -------------------------------------------------------------------------------- /app/src/src/components/containers/FeedContainer.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-native'; 3 | import { waitFor } from 'react-native-testing-library'; 4 | import { shallow } from 'enzyme'; 5 | import FeedContainer from './FeedContainer'; 6 | import { MockEndpoint1, MockEndpoint2 } from '../../utils/mockEndpoints'; 7 | 8 | const endpoints = [ 9 | async (page, filters) => await MockEndpoint1.list(page, filters), 10 | async (page, filters) => await MockEndpoint2.list(page, filters), 11 | ]; 12 | const itemViews = [ 13 | (item) => {item.id}, 14 | (item) => {item.id}, 15 | ]; 16 | const filtersList = [{}, {}]; 17 | 18 | const setup = (initialState) => { 19 | const feedContainerWrapper = shallow( 20 | , 26 | { disableLifecycleMethods: true } 27 | ); 28 | feedContainerWrapper.setState(initialState); 29 | return feedContainerWrapper; 30 | }; 31 | 32 | describe('', () => { 33 | let feedContainerWrapper = null; 34 | let feedContainer = null; 35 | 36 | beforeEach(() => { 37 | feedContainerWrapper = setup({}); 38 | feedContainer = feedContainerWrapper.instance(); 39 | }); 40 | 41 | it('should fetch items', async () => { 42 | await feedContainer.fetchItems(); 43 | await waitFor(() => { 44 | expect(feedContainer.state.items).toHaveLength(9); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /app/src/src/components/modals/CreateItemModal.test.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Todo: Tests 3 | * 1. Should be visible only when visible state is true 4 | * 2. Buttons should route to correct place 5 | */ 6 | -------------------------------------------------------------------------------- /app/src/src/components/modals/CreateWorkEduModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'react-native'; 3 | 4 | /* Will be used for education/jobs section of edit profile page */ 5 | const CreateWorkEduModal = ({ type, title, setPreSave, setClose, edit }) => { 6 | return ; 7 | }; 8 | 9 | export default CreateWorkEduModal; 10 | -------------------------------------------------------------------------------- /app/src/src/components/modals/ErrorModal.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/src/components/modals/ErrorModal.jsx -------------------------------------------------------------------------------- /app/src/src/components/modals/ErrorModal.test.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Todo: Tests 3 | * 1. Should only show when error messages are present 4 | */ 5 | -------------------------------------------------------------------------------- /app/src/src/components/modals/PickADayModal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View, TouchableHighlight, Platform } from 'react-native'; 3 | import DateTimePicker from '@react-native-community/datetimepicker'; 4 | import toYearMonthDayInNumbers from '../../utils/FormatDate/toYearMonthDayInNumbers'; 5 | 6 | // Modal to select a day 7 | const PickADayModal = ({ 8 | visible, 9 | setVisible, 10 | title, 11 | setType, 12 | type, 13 | setDate, 14 | styles, 15 | }) => { 16 | const onDateChange = (_event, selectedValue) => { 17 | setDate(selectedValue); 18 | const yyyymmdd = toYearMonthDayInNumbers({ selectedValue: selectedValue }); 19 | setType(yyyymmdd); 20 | }; 21 | return ( 22 | 23 | 24 | {title} 25 | 26 | = 14 29 | ? { 30 | width: '100%', 31 | flex: 1, 32 | left: '35%', 33 | } 34 | : { width: '100%', flex: 1 } 35 | } 36 | value={type} 37 | display="default" 38 | mode="date" 39 | minimumDate={new Date(1960, 0, 1)} 40 | maximumDate={new Date()} 41 | onChange={(event, selectedValue) => 42 | onDateChange(event, selectedValue) 43 | } 44 | /> 45 | 46 | setVisible(!visible)} 49 | > 50 | Close 51 | 52 | 53 | 54 | ); 55 | }; 56 | export default PickADayModal; 57 | -------------------------------------------------------------------------------- /app/src/src/components/views/CommentView.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import { View, StyleSheet } from 'react-native'; 3 | import ReplyView from './ReplyView'; 4 | import { RepliesEndpoint } from '../../utils/endpoints'; 5 | import FeedContainer from '../containers/FeedContainer'; 6 | import { useScrollToTop } from '@react-navigation/native'; 7 | 8 | const fetchItemsList = [ 9 | async (page, filters) => await RepliesEndpoint.list(page, filters), 10 | ]; 11 | const itemViews = [ 12 | (item, updateItem) => , 13 | ]; 14 | 15 | /** @type {function(number): object} */ 16 | const getInitialState = (id) => ({ 17 | repliesFilters: { 18 | /** @type {number} */ 19 | comment: id, 20 | }, 21 | }); 22 | 23 | /** 24 | * Renders a comment. 25 | * @param {object} item 26 | * @param {function(object): void} updateItem 27 | * @return {JSX.Element} 28 | */ 29 | const CommentView = ({ item, updateItem }) => { 30 | const [repliesFilters, setRepliesFilters] = useState( 31 | getInitialState(item.id).repliesFilters 32 | ); 33 | const ref = useRef(null); 34 | 35 | useScrollToTop(ref); 36 | 37 | return ( 38 | 39 | 40 | 41 | 47 | 48 | 49 | ); 50 | }; 51 | 52 | export default CommentView; 53 | 54 | const styles = StyleSheet.create({ 55 | commentView: { 56 | flex: 1, 57 | margin: 10, 58 | marginTop: 0, 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /app/src/src/components/views/CommentView.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, waitFor } from 'react-native-testing-library'; 3 | import { shallow } from 'enzyme'; 4 | import { mockComment } from '../../utils/mockData'; 5 | import CommentView from './CommentView'; 6 | 7 | describe('', () => { 8 | let wrapper = null; 9 | let commentView = null; 10 | beforeEach(() => { 11 | screen = render(); 12 | }); 13 | 14 | it('should contain the creator', () => { 15 | expect(screen.getByText(mockComment.creator.preferred_name)); 16 | }); 17 | 18 | it('should contain the datetime created', () => { 19 | expect( 20 | screen.getByText( 21 | mockComment.datetime_created.substring(0, 'YYYY-MM-DD'.length) + 22 | '\n' + 23 | mockComment.datetime_created.substring( 24 | 'YYYY-MM-DD'.length + 1, 25 | 'YYYY-MM-DDTHH:MM'.length 26 | ) 27 | ) 28 | ); 29 | }); 30 | 31 | it('should contain content', () => { 32 | expect(screen.getByText(mockComment.content)); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /app/src/src/components/views/ImageCollectionView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Swiper from 'react-native-swiper'; 3 | import { Image } from 'react-native'; 4 | 5 | const ImageCollectionView = ({ photos }) => { 6 | if (photos.length > 0) { 7 | return ( 8 | 9 | {photos 10 | .filter((photo) => photo.image !== null) 11 | .map((photo, i) => { 12 | return ( 13 | 21 | ); 22 | })} 23 | 24 | ); 25 | } 26 | 27 | return <>; 28 | }; 29 | 30 | export default ImageCollectionView; 31 | -------------------------------------------------------------------------------- /app/src/src/components/views/PollView.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Team-uMigrate/umigrate/b1125add628378695053204c60b32d041d327ea3/app/src/src/components/views/PollView.jsx -------------------------------------------------------------------------------- /app/src/src/components/views/PostView.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-native-testing-library'; 3 | import PostView from './PostView'; 4 | import { mockPost } from '../../utils/mockData'; 5 | import { communities } from '../../utils/choices'; 6 | 7 | describe('', () => { 8 | let screen = null; 9 | beforeEach(() => (screen = render())); 10 | 11 | it('should contain the title', () => { 12 | expect(screen.getByText(mockPost.title)); 13 | }); 14 | 15 | it('should contain the creator', () => { 16 | expect(screen.getByText(mockPost.creator.preferred_name)); 17 | }); 18 | 19 | it('should contain the datetime created', () => { 20 | expect( 21 | screen.getByText( 22 | mockPost.datetime_created.substring(0, 'YYYY-MM-DD'.length) 23 | ) 24 | ); 25 | }); 26 | 27 | it('should contain content', () => { 28 | expect(screen.getByText(mockPost.content)); 29 | }); 30 | 31 | it('should contain the community', () => { 32 | expect(screen.getByText(`Community: ${communities[mockPost.community]}`)); 33 | }); 34 | 35 | it('should contain the number of likes', () => { 36 | expect(screen.getByText(`Likes: ${mockPost.likes}`)); 37 | }); 38 | 39 | it('should contain the number of comments', () => { 40 | expect(screen.getByText(`Comments: ${mockPost.comments}`)); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /app/src/src/components/views/ProfilePhotoView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar } from 'react-native-paper'; 3 | 4 | const ProfilePhotoView = ({ photo, styles, size = 45 }) => { 5 | if (photo === null) { 6 | return ; 7 | } else { 8 | return ; 9 | } 10 | }; 11 | 12 | export default ProfilePhotoView; 13 | -------------------------------------------------------------------------------- /app/src/src/components/views/ProfileView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | 4 | const ProfileView = ({ label, val, row }) => { 5 | if (row) { 6 | if (val == '' || val === null) { 7 | return ( 8 | 9 | {label} 10 | Edit to add your {label} 11 | 12 | ); 13 | } else { 14 | return ( 15 | 16 | {label} 17 | {val} 18 | 19 | ); 20 | } 21 | } else { 22 | if (val == '' || val === null) { 23 | return ( 24 | 25 | {label} 26 | Edit to add your {label} 27 | 28 | ); 29 | } else { 30 | return ( 31 | 32 | {label} 33 | {val} 34 | 35 | ); 36 | } 37 | } 38 | }; 39 | 40 | export default ProfileView; 41 | 42 | const styles = StyleSheet.create({ 43 | textLabel: { 44 | fontSize: 12, 45 | textAlign: 'left', 46 | marginLeft: '5%', 47 | marginBottom: '1%', 48 | color: '#6C6A6A', 49 | }, 50 | textVal: { 51 | fontSize: 14, 52 | textAlign: 'left', 53 | fontWeight: 'bold', 54 | marginLeft: '5%', 55 | marginBottom: '3%', 56 | }, 57 | textValNull: { 58 | fontSize: 14, 59 | textAlign: 'left', 60 | fontStyle: 'italic', 61 | marginLeft: '5%', 62 | marginBottom: '3%', 63 | }, 64 | textLabelRow: { 65 | fontSize: 12, 66 | textAlign: 'left', 67 | marginLeft: '45%', 68 | marginBottom: '1%', 69 | color: '#6C6A6A', 70 | }, 71 | textValRow: { 72 | fontSize: 14, 73 | textAlign: 'left', 74 | fontWeight: 'bold', 75 | marginLeft: '45%', 76 | marginBottom: '3%', 77 | }, 78 | textValRowValNull: { 79 | fontSize: 14, 80 | textAlign: 'left', 81 | fontStyle: 'italic', 82 | marginLeft: '50%', 83 | marginBottom: '3%', 84 | }, 85 | }); 86 | -------------------------------------------------------------------------------- /app/src/src/components/views/ReplyView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, View, Text } from 'react-native'; 3 | import ProfilePhotoView from './ProfilePhotoView'; 4 | 5 | /** 6 | * Renders a reply. 7 | * @param {object} item 8 | * @param {function(object): void} updateItem 9 | * @return {JSX.Element} 10 | */ 11 | const ReplyView = ({ item, updateItem }) => { 12 | const { datetime_created, creator, content } = item; 13 | // The dateTime string looks like this: 2020-11-02T23:49:23.846475Z 14 | const date = datetime_created.substring(0, 10); 15 | const time = datetime_created.substring(11, 16); 16 | 17 | return ( 18 | 19 | {/* The user's name */} 20 | 21 | {/* Pushes the user's name forward so it lines up with the content */} 22 | 23 | 24 | {creator.preferred_name} 25 | 26 | 27 | {/* Profile photo, text content, and date/time of post creation */} 28 | 29 | 30 | 31 | 32 | 33 | {content} 34 | 35 | 36 | {date + '\n' + time} 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export default ReplyView; 44 | 45 | const styles = StyleSheet.create({ 46 | profilePhotoView: { 47 | marginRight: 5, 48 | flex: 1, 49 | }, 50 | replyView: { 51 | marginTop: 10, 52 | }, 53 | contentContainer: { 54 | flex: 5.5, 55 | backgroundColor: '#EBEBEB', 56 | borderRadius: 15, 57 | padding: 5, 58 | paddingLeft: 12, 59 | paddingRight: 5, 60 | }, 61 | timestampView: { 62 | marginLeft: 2, 63 | alignSelf: 'flex-end', 64 | flex: 1.5, 65 | }, 66 | timestamp: { 67 | color: 'gray', 68 | fontSize: 10, 69 | paddingBottom: 5, 70 | }, 71 | }); 72 | -------------------------------------------------------------------------------- /app/src/src/components/views/ReplyView.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-native-testing-library'; 3 | import ReplyView from './ReplyView'; 4 | import { mockReply } from '../../utils/mockData'; 5 | 6 | describe('', () => { 7 | let screen = null; 8 | beforeEach(() => (screen = render())); 9 | 10 | it('should contain the creator', () => { 11 | expect(screen.getByText(mockReply.creator.preferred_name)); 12 | }); 13 | 14 | it('should contain the datetime created', () => { 15 | expect( 16 | screen.getByText( 17 | mockReply.datetime_created.substring(0, 'YYYY-MM-DD'.length) + 18 | '\n' + 19 | mockReply.datetime_created.substring( 20 | 'YYYY-MM-DD'.length + 1, 21 | 'YYYY-MM-DDTHH:MM'.length 22 | ) 23 | ) 24 | ); 25 | }); 26 | 27 | it('should contain content', () => { 28 | expect(screen.getByText(mockReply.content)); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /app/src/src/components/views/UserView.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View, StyleSheet, Image } from 'react-native'; 3 | import ProfilePhotoView from './ProfilePhotoView'; 4 | import { IconButton } from 'react-native-paper'; 5 | 6 | /** 7 | * Renders a User. 8 | * @param {object} item 9 | * @param {function(object): void} updateItem 10 | * @return {JSX.Element} 11 | */ 12 | const UserView = ({ item, updateItem }) => { 13 | const { preferred_name, profile_photo } = item; 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | {preferred_name} 23 | 24 | 25 | console.log('Pressed')} 30 | /> 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default UserView; 38 | 39 | const styles = StyleSheet.create({ 40 | listView: { 41 | marginTop: 10, 42 | }, 43 | 44 | profilePhotoView: { 45 | marginRight: 5, 46 | marginLeft: 20, 47 | flex: 1, 48 | padding: 10, 49 | }, 50 | 51 | contentContainer: { 52 | flex: 5.5, 53 | padding: 10, 54 | }, 55 | 56 | iconButton: { 57 | alignSelf: 'center', 58 | marginBottom: '-15%', 59 | paddingTop: 10, 60 | marginRight: 20, 61 | }, 62 | 63 | text: { 64 | paddingTop: 10, 65 | fontSize: 15, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /app/src/src/contexts/AuthContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext, ReactNode } from 'react'; 2 | 3 | const AuthContext = createContext({ 4 | /** @type {boolean | null} */ 5 | isAuthenticated: null, 6 | /** @type {function(boolean | null): void} */ 7 | setIsAuthenticated: function (isAuthenticated) {}, 8 | }); 9 | 10 | /** 11 | * Provides access to the authentication state. 12 | * @param {ReactNode} children 13 | * @return {JSX.Element} 14 | * */ 15 | const AuthContextProvider = ({ children }) => { 16 | const [isAuthenticated, setIsAuthenticated] = useState(null); 17 | 18 | return ( 19 | 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | export { AuthContextProvider }; 31 | export default AuthContext; 32 | -------------------------------------------------------------------------------- /app/src/src/contexts/CreateItemContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from 'react'; 2 | 3 | const CreateItemContext = createContext({ 4 | /** @type {boolean} */ 5 | isModalVisible: false, 6 | /** @type {function(boolean): void} */ 7 | setIsModalVisible: function (isModalVisible) {}, 8 | }); 9 | 10 | /** 11 | * Provides access to the create item modal visibility state. 12 | * @param {ReactNode} children 13 | * @return {JSX.Element} 14 | * */ 15 | const CreateItemContextProvider = ({ children }) => { 16 | const [isModalVisible, setIsModalVisible] = useState(false); 17 | 18 | return ( 19 | 25 | {children} 26 | 27 | ); 28 | }; 29 | 30 | export { CreateItemContextProvider }; 31 | export default CreateItemContext; 32 | -------------------------------------------------------------------------------- /app/src/src/contexts/ErrorContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from 'react'; 2 | 3 | const ErrorContext = createContext({ 4 | /** @type {string | null} */ 5 | message: null, 6 | /** @type {function(string | null): void} */ 7 | setMessage: function (message) {}, 8 | }); 9 | 10 | /** 11 | * Provides access to the error modal message state. 12 | * @param {ReactNode} children 13 | * @return {JSX.Element} 14 | * */ 15 | const ErrorContextProvider = ({ children }) => { 16 | const [message, setMessage] = useState(null); 17 | 18 | return ( 19 | 20 | {children} 21 | 22 | ); 23 | }; 24 | 25 | export { ErrorContextProvider }; 26 | export default ErrorContext; 27 | -------------------------------------------------------------------------------- /app/src/src/contexts/StackNavContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from 'react'; 2 | import { StackNavigationProp } from '@react-navigation/stack'; 3 | 4 | const initialState = { 5 | /** @type {StackNavigationProp | null} */ 6 | navigation: null, 7 | /** @type {function(StackNavigationProp | null): void} */ 8 | setNavigation: function (navigation) {}, 9 | }; 10 | const StackNavContext = createContext(initialState); 11 | 12 | /** 13 | * Provides access to the stack navigator navigation state. 14 | * @param {ReactNode} children 15 | * @return {JSX.Element} 16 | * */ 17 | const StackNavContextProvider = ({ children }) => { 18 | const [navigation, setNavigation] = useState(initialState.navigation); 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export { StackNavContextProvider }; 28 | export default StackNavContext; 29 | -------------------------------------------------------------------------------- /app/src/src/contexts/UserViewContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from 'react'; 2 | 3 | const UserViewContext = createContext(); 4 | /** 5 | * Stores the user whose profile is to be shown 6 | * @param {ReactNode} children 7 | * @return {JSX.Element} 8 | * */ 9 | const UserViewContextProvider = ({ children }) => { 10 | const [user, setUser] = useState(null); 11 | 12 | return ( 13 | 19 | {children} 20 | 21 | ); 22 | }; 23 | 24 | export { UserViewContextProvider }; 25 | export default UserViewContext; 26 | -------------------------------------------------------------------------------- /app/src/src/navigators/MenuNavigator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | import SettingsScreen from '../screens/menu/SettingsScreen'; 4 | import SavedHomeScreen from '../screens/saved/SavedHomeScreen'; 5 | import SavedItemsScreen from '../screens/saved/SavedItemsScreen'; 6 | import ProfileScreen from '../screens/menu/ProfileScreen'; 7 | import EditProfileScreen from '../screens/menu/EditProfileScreen'; 8 | import MenuHomeScreen from '../screens/menu/MenuHomeScreen'; 9 | import CalendarScreen from '../screens/menu/CalendarScreen'; 10 | import { routes } from '../utils/routes'; 11 | 12 | const Stack = createStackNavigator(); 13 | 14 | /** 15 | * Renders screens based on the current menu navigation route. 16 | * @return {JSX.Element} 17 | */ 18 | const MenuNavigator = () => { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default MenuNavigator; 33 | -------------------------------------------------------------------------------- /app/src/src/screens/authentication/LoadingScreen.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect } from 'react'; 2 | import { ProfileEndpoint } from '../../utils/endpoints'; 3 | import AuthContext from '../../contexts/AuthContext'; 4 | import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; 5 | import { 6 | getAuthToken, 7 | removeAuthToken, 8 | setAuthToken, 9 | } from '../../utils/storageAccess'; 10 | 11 | /** 12 | * Renders the loading screen. 13 | * @return {JSX.Element} 14 | * */ 15 | const LoadingScreen = () => { 16 | const auth = useContext(AuthContext); 17 | 18 | useEffect(() => { 19 | (async () => { 20 | const token = await getAuthToken(); 21 | 22 | // Make request to Profile endpoint and set isAuthenticated to true if successful or false otherwise 23 | if (token != null) { 24 | await setAuthToken(token); 25 | try { 26 | await ProfileEndpoint.get(); 27 | auth.setIsAuthenticated(true); 28 | } catch (error) { 29 | await removeAuthToken(); 30 | auth.setIsAuthenticated(false); 31 | } 32 | } else auth.setIsAuthenticated(false); 33 | })(); 34 | }, [auth.isAuthenticated]); 35 | 36 | return ( 37 | 38 | Please Wait 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default LoadingScreen; 45 | 46 | const styles = StyleSheet.create({ 47 | waitContainer: { 48 | flex: 1, 49 | backgroundColor: '#eeeeee', 50 | alignItems: 'center', 51 | justifyContent: 'center', 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /app/src/src/screens/comments/CommentsScreen.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import { StyleSheet, View } from 'react-native'; 3 | import Header from '../../components/views/Header'; 4 | import FeedContainer from '../../components/containers/FeedContainer'; 5 | import CreateItemModal from '../../components/modals/CreateItemModal'; 6 | import { CommentsEndpoint } from '../../utils/endpoints'; 7 | import CommentView from '../../components/views/CommentView'; 8 | import { sharedLikesCommentsstyles } from '../../stylesheets/likesAndComments/likesAndComments.jsx'; 9 | import { StackNavigationProp } from '@react-navigation/stack'; 10 | import { RouteProp, useScrollToTop } from '@react-navigation/native'; 11 | 12 | const fetchItemsList = [ 13 | async (page, filters) => await CommentsEndpoint.list(page, filters), 14 | ]; 15 | const itemViews = [ 16 | (item, updateItem) => , 17 | ]; 18 | 19 | /** 20 | * Gets the initial state. 21 | * @param {RouteProp} route 22 | * @return {object} 23 | * */ 24 | const getInitialState = (route) => ({ 25 | commentsFilters: { 26 | /** @type {number} */ 27 | content_type: route.params['contentType'], 28 | /** @type {number} */ 29 | object_id: route.params['postId'], 30 | }, 31 | }); 32 | 33 | /** 34 | * Renders the comments screen. 35 | * @param {StackNavigationProp} navigation 36 | * @param {RouteProp} route 37 | * @return {JSX.Element} 38 | * */ 39 | const CommentsScreen = ({ navigation, route }) => { 40 | const [commentsFilters, setCommentsFilters] = useState( 41 | getInitialState(route).commentsFilters 42 | ); 43 | const ref = useRef(null); 44 | 45 | useScrollToTop(ref); 46 | 47 | return ( 48 | 49 |
50 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default CommentsScreen; 62 | -------------------------------------------------------------------------------- /app/src/src/screens/createItem/CreateCommunityScreen.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-native-testing-library'; 3 | import { Text } from 'react-native'; 4 | import { waitFor } from 'react-native-testing-library'; 5 | import { shallow } from 'enzyme'; 6 | import CreateCommunityScreen from './CreateCommunityScreen'; 7 | 8 | describe('', () => { 9 | let screen = null; 10 | beforeEach(() => (screen = render(shallow()))); 11 | 12 | it('should contain post, poll, and event options', () => { 13 | expect(screen.getByText('Post')); 14 | expect(screen.getByText('Poll')); 15 | expect(screen.getByText('Event')); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/src/src/screens/createItem/CreateHousingScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text } from 'react-native'; 3 | import { sharedItemStyles } from '../../stylesheets/createItem/createItem.jsx'; 4 | 5 | const CreateHousingScreen = () => { 6 | return ( 7 | 8 | Create a post in housing! 9 | 10 | ); 11 | }; 12 | 13 | export default CreateHousingScreen; 14 | -------------------------------------------------------------------------------- /app/src/src/screens/createItem/CreateMarketScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text } from 'react-native'; 3 | import { sharedItemStyles } from '../../stylesheets/createItem/createItem.jsx'; 4 | 5 | const CreateMarketScreen = () => { 6 | return ( 7 | 8 | Create a post in market! 9 | 10 | ); 11 | }; 12 | 13 | export default CreateMarketScreen; 14 | -------------------------------------------------------------------------------- /app/src/src/screens/likes/LikesScreen.jsx: -------------------------------------------------------------------------------- 1 | import { ListingsEndpoint, PostsEndpoint } from '../../utils/endpoints'; 2 | import ListingView from '../../components/views/ListingView'; 3 | import React, { useRef, useState } from 'react'; 4 | import { useScrollToTop } from '@react-navigation/native'; 5 | import { View } from 'react-native'; 6 | import Header from '../../components/views/Header'; 7 | import FeedContainer from '../../components/containers/FeedContainer'; 8 | import CreateItemModal from '../../components/modals/CreateItemModal'; 9 | import { sharedLikesCommentsstyles } from '../../stylesheets/likesAndComments/likesAndComments.jsx'; 10 | import { StackNavigationProp } from '@react-navigation/stack'; 11 | import { RouteProp } from '@react-navigation/native'; 12 | import UserView from '../../components/views/UserView'; 13 | 14 | const itemViews = [ 15 | (item, updateItem) => , 16 | ]; 17 | 18 | /** 19 | * Renders the likes screen. 20 | * @param {StackNavigationProp} navigation 21 | * @param {RouteProp} route 22 | * @return {JSX.Element} 23 | * */ 24 | 25 | const LikesScreen = ({ navigation, route }) => { 26 | const fetchItemsList = [ 27 | async (page, filters) => 28 | await PostsEndpoint.likes(route.params['postId'], page), 29 | ]; 30 | 31 | const ref = useRef(null); 32 | 33 | useScrollToTop(ref); 34 | 35 | return ( 36 | 37 |
38 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default LikesScreen; 50 | -------------------------------------------------------------------------------- /app/src/src/screens/menu/CalendarScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View } from 'react-native'; 3 | import Header from '../../components/views/Header'; 4 | import { Button } from 'react-native-paper'; 5 | import { routes } from '../../utils/routes'; 6 | import { sharedMenustyles } from '../../stylesheets/menu/menu.jsx'; 7 | 8 | const CalendarScreen = ({ navigation }) => { 9 | const menuRedirect = () => { 10 | navigation.navigate(routes.menuHome); 11 | }; 12 | 13 | return ( 14 | 15 |
16 | Calendar Page! 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default CalendarScreen; 23 | -------------------------------------------------------------------------------- /app/src/src/screens/menu/EditProfileScreen.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-native-testing-library'; 3 | import { mockUser } from '../../utils/mockData'; 4 | import EditProfileView from '../../components/views/EditProfileView'; 5 | import { communities, pronouns } from '../../utils/choices'; 6 | 7 | describe(' UI Tests', () => { 8 | beforeEach(() => { 9 | screen = render(); 10 | }); 11 | 12 | it('should contain the preferred name as a text input', () => { 13 | expect(screen.getByDisplayValue(mockUser.preferred_name)); 14 | }); 15 | 16 | it('should contain the first name', () => { 17 | expect(screen.getByText(mockUser.first_name)); 18 | }); 19 | 20 | it('should contain the last name', () => { 21 | expect(screen.getByText(mockUser.last_name)); 22 | }); 23 | 24 | it('should contain the email', () => { 25 | expect(screen.getByText(mockUser.email)); 26 | }); 27 | 28 | it('should contain the phone number as a text input', () => { 29 | expect(screen.getByDisplayValue(mockUser.phone_number)); 30 | }); 31 | 32 | it('should contain the birthday', () => { 33 | expect( 34 | screen.getByText(mockUser.birthday.substring(0, 'YYYY-MM-DD'.length)) 35 | ); 36 | }); 37 | 38 | it('should contain the pronoun', () => { 39 | expect(screen.getByText(pronouns[mockUser.pronouns])); 40 | }); 41 | 42 | it('should contain the community', () => { 43 | expect(screen.getByText(communities[mockUser.community])); 44 | }); 45 | 46 | it('should contain the bio', () => { 47 | expect(screen.getByText(mockUser.bio)); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /app/src/src/screens/menu/SettingsScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import Header from '../../components/views/Header'; 4 | import { Button } from 'react-native-paper'; 5 | import { routes } from '../../utils/routes'; 6 | import { sharedMenustyles } from '../../stylesheets/menu/menu.jsx'; 7 | 8 | const SettingsScreen = ({ navigation }) => { 9 | const menuRedirect = () => { 10 | navigation.navigate(routes.menuHome); 11 | }; 12 | 13 | return ( 14 | 15 |
16 | Settings Page! 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default SettingsScreen; 23 | -------------------------------------------------------------------------------- /app/src/src/screens/messaging/MessagingScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, Text, View } from 'react-native'; 3 | import Header from '../../components/views/Header'; 4 | import { sharedMessagingNotificationStyles } from '../../stylesheets/messaging/messaging.jsx'; 5 | import { StackNavigationProp } from '@react-navigation/stack'; 6 | 7 | /** 8 | * Renders the messaging screen. 9 | * @param {StackNavigationProp} navigation 10 | * @return {JSX.Element} 11 | * */ 12 | const MessagingScreen = ({ navigation }) => { 13 | return ( 14 | 15 |
16 | 17 | Messaging Page! 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default MessagingScreen; 24 | -------------------------------------------------------------------------------- /app/src/src/screens/notifications/NotificationsScreen.jsx: -------------------------------------------------------------------------------- 1 | import { Text, View } from 'react-native'; 2 | import Header from '../../components/views/Header'; 3 | import React from 'react'; 4 | import { sharedMessagingNotificationStyles } from '../../stylesheets/messaging/messaging.jsx'; 5 | import { StackNavigationProp } from '@react-navigation/stack'; 6 | 7 | /** 8 | * Renders the notification screen. 9 | * @param {StackNavigationProp} navigation 10 | * @return {JSX.Element} 11 | * */ 12 | const NotificationScreen = ({ navigation }) => { 13 | return ( 14 | 15 |
16 | 17 | Notification Page! 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default NotificationScreen; 24 | -------------------------------------------------------------------------------- /app/src/src/screens/search/SearchScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View } from 'react-native'; 3 | import SearchContainer from '../../components/containers/SearchContainer'; 4 | import { StackNavigationProp } from '@react-navigation/stack'; 5 | 6 | /** 7 | * Renders the search screen. 8 | * @param {StackNavigationProp} navigation 9 | * @return {JSX.Element} 10 | * */ 11 | const SearchScreen = ({ navigation }) => { 12 | return ( 13 | 14 | navigation.pop()} /> 15 | 16 | ); 17 | }; 18 | 19 | export default SearchScreen; 20 | -------------------------------------------------------------------------------- /app/src/src/screens/tabs/CommunityScreen.jsx: -------------------------------------------------------------------------------- 1 | import { EventsEndpoint, PostsEndpoint } from '../../utils/endpoints'; 2 | import PostView from '../../components/views/PostView'; 3 | import EventView from '../../components/views/EventView'; 4 | import React, { useRef, useState } from 'react'; 5 | import { useScrollToTop } from '@react-navigation/native'; 6 | import { View } from 'react-native'; 7 | import Header from '../../components/views/Header'; 8 | import FeedContainer from '../../components/containers/FeedContainer'; 9 | import CreateItemModal from '../../components/modals/CreateItemModal'; 10 | import UserViewModal from '../../components/modals/UserViewModal'; 11 | import { sharedItemTabsStyles } from '../../stylesheets/tabs/tabs.jsx'; 12 | import { StackNavigationProp } from '@react-navigation/stack'; 13 | import { RouteProp } from '@react-navigation/native'; 14 | 15 | const fetchItemsList = [ 16 | async (page, filters) => await PostsEndpoint.list(page, filters), 17 | async (page, filters) => await EventsEndpoint.list(page, filters), 18 | ]; 19 | const itemViews = [ 20 | (item, updateItem) => , 21 | (item, updateItem) => , 22 | ]; 23 | 24 | /** 25 | * Renders the community screen. 26 | * @param {StackNavigationProp} navigation 27 | * @param {RouteProp} route 28 | * @return {JSX.Element} 29 | * */ 30 | const CommunityScreen = ({ navigation, route }) => { 31 | const [postFilters, setPostFilters] = useState({}); 32 | const [eventFilters, setEventFilters] = useState({}); 33 | const ref = useRef(null); 34 | 35 | useScrollToTop(ref); 36 | 37 | return ( 38 | 39 |
40 | 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default CommunityScreen; 54 | -------------------------------------------------------------------------------- /app/src/src/screens/tabs/CreateItemScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CreateCommunityScreen from '../createItem/CreateCommunityScreen'; 3 | import CreateMarketScreen from '../createItem/CreateMarketScreen'; 4 | import CreateHousingScreen from '../createItem/CreateHousingScreen'; 5 | import { RouteProp } from '@react-navigation/native'; 6 | 7 | /** 8 | * Renders the create item screen. 9 | * @param {RouteProp} route 10 | * @return {JSX.Element} 11 | * */ 12 | const CreateItemScreen = ({ route }) => { 13 | switch (route.params.page) { 14 | case 'Community': 15 | return ; 16 | 17 | case 'Market': 18 | return ; 19 | 20 | case 'Housing': 21 | return ; 22 | 23 | default: 24 | return <>; 25 | } 26 | }; 27 | 28 | export default CreateItemScreen; 29 | -------------------------------------------------------------------------------- /app/src/src/screens/tabs/HousingScreen.jsx: -------------------------------------------------------------------------------- 1 | import { ListingsEndpoint } from '../../utils/endpoints'; 2 | import ListingView from '../../components/views/ListingView'; 3 | import React, { useRef, useState } from 'react'; 4 | import { useScrollToTop } from '@react-navigation/native'; 5 | import { View } from 'react-native'; 6 | import Header from '../../components/views/Header'; 7 | import FeedContainer from '../../components/containers/FeedContainer'; 8 | import CreateItemModal from '../../components/modals/CreateItemModal'; 9 | import UserViewModal from '../../components/modals/UserViewModal'; 10 | import { sharedItemTabsStyles } from '../../stylesheets/tabs/tabs.jsx'; 11 | import { StackNavigationProp } from '@react-navigation/stack'; 12 | import { RouteProp } from '@react-navigation/native'; 13 | 14 | const fetchItemsList = [ 15 | async (page, filters) => await ListingsEndpoint.list(page, filters), 16 | ]; 17 | const itemViews = [ 18 | (item, updateItem) => , 19 | ]; 20 | 21 | /** 22 | * Renders the housing screen. 23 | * @param {StackNavigationProp} navigation 24 | * @param {RouteProp} route 25 | * @return {JSX.Element} 26 | * */ 27 | const HousingScreen = ({ navigation, route }) => { 28 | const [listingFilters, setListingFilters] = useState({}); 29 | const ref = useRef(null); 30 | 31 | useScrollToTop(ref); 32 | 33 | return ( 34 | 35 |
36 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default HousingScreen; 50 | -------------------------------------------------------------------------------- /app/src/src/screens/tabs/MarketScreen.jsx: -------------------------------------------------------------------------------- 1 | import { AdsEndpoint } from '../../utils/endpoints'; 2 | import AdView from '../../components/views/AdView'; 3 | import React, { useRef, useState } from 'react'; 4 | import { useScrollToTop } from '@react-navigation/native'; 5 | import { View } from 'react-native'; 6 | import Header from '../../components/views/Header'; 7 | import FeedContainer from '../../components/containers/FeedContainer'; 8 | import CreateItemModal from '../../components/modals/CreateItemModal'; 9 | import UserViewModal from '../../components/modals/UserViewModal'; 10 | import { sharedItemTabsStyles } from '../../stylesheets/tabs/tabs.jsx'; 11 | import { StackNavigationProp } from '@react-navigation/stack'; 12 | import { RouteProp } from '@react-navigation/native'; 13 | 14 | const fetchItemsList = [ 15 | async (page, filters) => await AdsEndpoint.list(page, filters), 16 | ]; 17 | const itemViews = [ 18 | (item, updateItem) => , 19 | ]; 20 | 21 | /** 22 | * Renders the market screen. 23 | * @param {StackNavigationProp} navigation 24 | * @param {RouteProp} route 25 | * @return {JSX.Element} 26 | * */ 27 | const MarketScreen = ({ navigation, route }) => { 28 | const [adFilters, setAdFilters] = useState({}); 29 | const ref = useRef(null); 30 | 31 | useScrollToTop(ref); 32 | 33 | return ( 34 | 35 |
36 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default MarketScreen; 50 | -------------------------------------------------------------------------------- /app/src/src/screens/tabs/MenuScreen.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MenuNavigator from '../../navigators/MenuNavigator'; 3 | import { StackNavigationProp } from '@react-navigation/stack'; 4 | 5 | /** 6 | * Renders the menu screen. 7 | * @param {StackNavigationProp} navigation 8 | * @return {JSX.Element} 9 | * */ 10 | const MenuScreen = ({ navigation }) => { 11 | return ; 12 | }; 13 | 14 | export default MenuScreen; 15 | -------------------------------------------------------------------------------- /app/src/src/setupTests.js: -------------------------------------------------------------------------------- 1 | import { setupMockEndpoint } from './utils/mockEndpoints'; 2 | import { configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import React from 'react'; 5 | 6 | setupMockEndpoint(); 7 | configure({ adapter: new Adapter() }); 8 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/createItem/createItem.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Shared Item Screens 4 | export const sharedItemStyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: '#eeeeee', 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/likesAndComments/likesAndComments.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Likes and Comments Screens 4 | export const sharedLikesCommentsstyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: 'white', 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/menu/menu.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Shared Menu Screens 4 | export const sharedMenustyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: '#eeeeee', 8 | }, 9 | title: { 10 | alignSelf: 'center', 11 | marginTop: '80%', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/messaging/messaging.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Shared Messaging and Notification Screens 4 | export const sharedMessagingNotificationStyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: '#eeeeee', 8 | }, 9 | title: { 10 | alignSelf: 'center', 11 | marginTop: '80%', 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/tabs/tabs.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Shared Item Tabs 4 | export const sharedItemTabsStyles = StyleSheet.create({ 5 | container: { 6 | flex: 1, 7 | backgroundColor: '#eeeeee', 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /app/src/src/stylesheets/views/views.jsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | // Styles for Shared Item Views 4 | export const sharedItemViewStyles = StyleSheet.create({ 5 | container: { 6 | display: 'flex', 7 | marginTop: '2.5%', 8 | padding: 3, 9 | flexDirection: 'column', 10 | backgroundColor: '#ffffff', 11 | }, 12 | cardContent: { 13 | paddingTop: '1.5%', 14 | paddingBottom: '2.5%', 15 | }, 16 | row: { 17 | flexDirection: 'row', 18 | marginBottom: '2.5%', 19 | }, 20 | column: { 21 | flex: 5, 22 | marginLeft: '4%', 23 | flexDirection: 'column', 24 | alignSelf: 'center', 25 | }, 26 | title: { 27 | alignSelf: 'flex-start', 28 | letterSpacing: 0.5, 29 | }, 30 | bodyText: { 31 | marginBottom: 0, 32 | letterSpacing: 0.5, 33 | fontSize: 15, 34 | }, 35 | name: { 36 | fontWeight: '500', 37 | fontSize: 16, 38 | }, 39 | bold: { 40 | fontWeight: 'bold', 41 | }, 42 | date: { 43 | color: 'grey', 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /app/src/src/utils/FormatDate/toMonthDayYearInWords.jsx: -------------------------------------------------------------------------------- 1 | import toYearMonthDayInNumbers from './toYearMonthDayInNumbers'; 2 | // takes in Date and returns the date in Month Day Year format (ex: January 1 1960) 3 | 4 | const toMonthDayYearInWords = ({ date }) => { 5 | let yyyymmdd = date; 6 | // check if date is in Date format or is already in YYYY-MM-DD format (birthday saves in YYYY-MM-DD format) 7 | const check = /^\d{4}-\d{2}-\d{2}$/; 8 | if (!date.match(check)) 9 | yyyymmdd = toYearMonthDayInNumbers({ selectedValue: date }); 10 | const months = [ 11 | 'January', 12 | 'February', 13 | 'March', 14 | 'April', 15 | 'May', 16 | 'June', 17 | 'July', 18 | 'August', 19 | 'September', 20 | 'October', 21 | 'November', 22 | 'December', 23 | ]; 24 | const sectioned = yyyymmdd.split(/\D/); 25 | const MonthDayYear = 26 | sectioned[2] + ' ' + months[sectioned[1] - 1] + ' ' + sectioned[0]; 27 | return MonthDayYear; 28 | }; 29 | export default toMonthDayYearInWords; 30 | -------------------------------------------------------------------------------- /app/src/src/utils/FormatDate/toMonthYear.jsx: -------------------------------------------------------------------------------- 1 | import toMonthDayYearInWords from './toMonthDayYearInWords'; 2 | 3 | // takes in a date value in the form YYYY-MM-DD and returns the month and year in words 4 | const toMonthYear = ({ date }) => { 5 | const result = toMonthDayYearInWords({ date: date }).split(' '); 6 | return result[1] + ' ' + result[2]; 7 | }; 8 | export default toMonthYear; 9 | -------------------------------------------------------------------------------- /app/src/src/utils/FormatDate/toYearMonthDayInNumbers.jsx: -------------------------------------------------------------------------------- 1 | // takes in Date and returns the date in YYYY-MM-DD format (ex: 1960-01-01) 2 | const toYearMonthDayInNumbers = ({ selectedValue }) => { 3 | const YYYYMMDD = selectedValue.toLocaleDateString('en-CA', { 4 | timezone: 'EST', 5 | year: 'numeric', 6 | month: 'numeric', 7 | day: 'numeric', 8 | }); 9 | const check = YYYYMMDD.indexOf('/'); 10 | // on android, it returns with / instead of - in format of MM/DD/YY instead of YYYY-MM-DD 11 | if (check == -1) return YYYYMMDD; 12 | const dateArr = YYYYMMDD.split('/'); 13 | const androidYYYYMMDD = 14 | '20' + dateArr[2] + '-' + dateArr[0] + '-' + dateArr[1]; 15 | return androidYYYYMMDD; 16 | }; 17 | export default toYearMonthDayInNumbers; 18 | -------------------------------------------------------------------------------- /app/src/src/utils/FormatDate/toYearMonthDayTimeInNumbers.jsx: -------------------------------------------------------------------------------- 1 | import toYearMonthDayInNumbers from './toYearMonthDayInNumbers'; 2 | 3 | /* Takes in a Date object and returns an EST date string to send to the API 4 | Format in YYYY-MM-DDThh:mm:ss.uuuuuuZ (ex: 1960-01-01T12:00:00.000000Z) */ 5 | const toYearMonthDayTimeInNumbers = ({ date }) => { 6 | let dateString = toYearMonthDayInNumbers({ selectedValue: date }); 7 | let timeString = date.toLocaleTimeString('en-CA', { 8 | timezone: 'EST', 9 | hour12: false, 10 | hour: '2-digit', 11 | minute: '2-digit', 12 | }); 13 | 14 | let fullDate = dateString + 'T' + timeString + 'Z'; 15 | return fullDate; 16 | }; 17 | export default toYearMonthDayTimeInNumbers; 18 | -------------------------------------------------------------------------------- /app/src/src/utils/choices.js: -------------------------------------------------------------------------------- 1 | export const pronouns = ['None', 'He/Him', 'She/Her', 'They/Them', 'Other']; 2 | export const seasons = ['Winter', 'Spring', 'Fall']; 3 | export const prices = ['Free', '$', '$$', '$$$', '$$$$', '$$$$$']; 4 | export const communities = ['Waterloo', 'Toronto', 'Brampton', 'Ottawa']; 5 | export const programs = [ 6 | 'Unknown', 7 | 'Engineering', 8 | 'Arts', 9 | 'Mathematics', 10 | 'Science', 11 | 'Applied Health Sciences', 12 | 'Environment', 13 | 'Theology', 14 | 'Graduate Studies', 15 | 'Independent Studies', 16 | 'Interdisciplinary', 17 | 'Conrad Grebel', 18 | 'Renison', 19 | 'St. Pauls', 20 | 'St. Jeromes', 21 | ]; 22 | export const terms = [ 23 | '1A', 24 | '1B', 25 | 'W1', 26 | '2A', 27 | 'W2', 28 | '2B', 29 | 'W3', 30 | '3A', 31 | 'W4', 32 | '3B', 33 | 'W5', 34 | 'W6', 35 | '4A', 36 | '4B', 37 | ]; 38 | export const contentTypes = { 39 | ad: 16, 40 | event: 17, 41 | listing: 18, 42 | roommate: 19, 43 | job: 20, 44 | membership: 21, 45 | messagingRoom: 22, 46 | message: 23, 47 | pollOption: 24, 48 | poll: 25, 49 | pollVote: 26, 50 | post: 27, 51 | user: 28, 52 | photo: 29, 53 | comment: 30, 54 | reply: 31, 55 | device: 32, 56 | notification: 33, 57 | }; 58 | export const adCategories = ['Electronics', 'Books', 'Food', 'Other']; 59 | export const listingCategories = ['Condominium', 'Townhouse', 'Apartment']; 60 | export const jobTypes = ['Full-time', 'Internship']; 61 | -------------------------------------------------------------------------------- /app/src/src/utils/endpoints.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Todo: Tests 3 | * 1. toFormData function 4 | * 2. toQueryString function 5 | */ 6 | -------------------------------------------------------------------------------- /app/src/src/utils/mockEndpoints.js: -------------------------------------------------------------------------------- 1 | import MockAdapter from 'axios-mock-adapter'; 2 | import Axios from 'axios'; 3 | import { 4 | mockAd, 5 | mockComment, 6 | mockEvent, 7 | mockListing, 8 | mockPost, 9 | mockReply, 10 | mockUser, 11 | } from './mockData'; 12 | 13 | function createMockResults(page, hour) { 14 | let mockResults = []; 15 | for (let i = 0; i < 5; i++) { 16 | mockResults = mockResults.concat({ 17 | id: 10 - 5 * (page - 1) - i, 18 | datetime_created: new Date(2020, 0, 5 * page - i + 1, hour).toISOString(), 19 | }); 20 | } 21 | return mockResults; 22 | } 23 | 24 | export function setupMockEndpoint() { 25 | const mockAxios = new MockAdapter(Axios); 26 | mockAxios.onGet(/^https:\/\/umigrate.ca\/api\/mock1\/\?page=1$/).reply(200, { 27 | results: createMockResults(1, 1), 28 | next: 'https://umigrate.ca/api/mock1/?page=2', 29 | }); 30 | mockAxios.onGet(/^https:\/\/umigrate.ca\/api\/mock1\/\?page=2$/).reply(200, { 31 | results: createMockResults(2, 2), 32 | next: null, 33 | }); 34 | mockAxios.onGet(/^https:\/\/umigrate.ca\/api\/mock2\/\?page=1$/).reply(200, { 35 | results: createMockResults(1, 2), 36 | next: 'https://umigrate.ca/api/mock2/?page=2', 37 | }); 38 | mockAxios.onGet(/^https:\/\/umigrate.ca\/api\/mock2\/\?page=2$/).reply(200, { 39 | results: createMockResults(2, 1), 40 | next: null, 41 | }); 42 | } 43 | 44 | export class MockEndpoint1 { 45 | static async list(page, filters = {}) { 46 | let queryString = '?page=' + page; 47 | for (let key in filters) { 48 | queryString += '&' + key + '=' + filters[key]; 49 | } 50 | return await Axios.get('https://umigrate.ca/api/mock1/' + queryString); 51 | } 52 | } 53 | 54 | export class MockEndpoint2 { 55 | static async list(page, filters = {}) { 56 | let queryString = '?page=' + page; 57 | for (let key in filters) { 58 | queryString += '&' + key + '=' + filters[key]; 59 | } 60 | return await Axios.get('https://umigrate.ca/api/mock2/' + queryString); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/src/utils/pushNotificationHelpers.js: -------------------------------------------------------------------------------- 1 | import Constants from 'expo-constants'; 2 | import { Platform } from 'react-native'; 3 | import * as Notifications from 'expo-notifications'; 4 | import { DevicesEndpoint } from './endpoints'; 5 | import { setPushToken } from './storageAccess'; 6 | 7 | export async function registerForPushNotificationsAsync(error) { 8 | let token; 9 | // Check the platform that the app is running on 10 | if ( 11 | Constants.isDevice && 12 | (Platform.OS === 'ios' || Platform.OS === 'android') 13 | ) { 14 | // Retrieve notification permissions 15 | let permission = await Notifications.getPermissionsAsync(); 16 | // Request notification permissions if not already granted 17 | if (!permission.granted) { 18 | permission = await Notifications.requestPermissionsAsync(); 19 | } 20 | // Set error message if notification permissions is not granted 21 | if (!permission.granted) { 22 | error.setMessage('Failed to get push token for push notification!'); 23 | return; 24 | } 25 | // Retrieve token and set it to async storage 26 | token = (await Notifications.getExpoPushTokenAsync()).data; 27 | await setPushToken(token); 28 | 29 | // Retrieve devices from Devices endpoint and register current device if not in the devices list 30 | const devices = (await DevicesEndpoint.list()).data; 31 | if (!devices.find((d) => d.expo_push_token === token)) { 32 | await DevicesEndpoint.post(`Device ${devices.length + 1}`, token); 33 | } 34 | } else { 35 | error.setMessage('Must use physical device for Push Notifications'); 36 | } 37 | 38 | if (Platform.OS === 'android') { 39 | await Notifications.setNotificationChannelAsync('default', { 40 | name: 'default', 41 | importance: Notifications.AndroidImportance.MAX, 42 | vibrationPattern: [0, 250, 250, 250], 43 | lightColor: '#FF231F7C', 44 | }); 45 | } 46 | 47 | return token; 48 | } 49 | -------------------------------------------------------------------------------- /app/src/src/utils/routes.js: -------------------------------------------------------------------------------- 1 | // Navigation routes 2 | export const routes = Object.freeze({ 3 | login: 'Login', 4 | registration: 'Registration', 5 | resetPassword: 'PasswordResetScreen', 6 | emailSent: 'EmailSentScreen', 7 | tabs: 'Tabs', 8 | messaging: 'Messaging', 9 | notifications: 'Notifications', 10 | comments: 'SharedItem', 11 | likes: 'Likes', 12 | community: 'Community', 13 | market: 'Market', 14 | createItem: 'Create', 15 | housing: 'Housing', 16 | menu: 'Menu', 17 | menuHome: 'MenuHome', 18 | profile: 'Profile', 19 | editProfile: 'EditProfile', 20 | savedHome: 'SavedHome', 21 | savedItems: 'SavedItems', 22 | calendar: 'Calendar', 23 | settings: 'Settings', 24 | search: 'Search', 25 | }); 26 | -------------------------------------------------------------------------------- /infrastructure/README.md: -------------------------------------------------------------------------------- 1 | Test 2 | -------------------------------------------------------------------------------- /infrastructure/deployment/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | name: $(MajorVersion).$(MinorVersion) 7 | 8 | trigger: 9 | branches: 10 | include: 11 | - main 12 | paths: 13 | include: 14 | - infrastructure/* 15 | exclude: 16 | - infrastructure/deployment/azure-pipelines.yml 17 | 18 | pr: 19 | branches: 20 | include: 21 | - main 22 | paths: 23 | include: 24 | - infrastructure/* 25 | exclude: 26 | - infrastructure/deployment/azure-pipelines.yml 27 | 28 | variables: 29 | - name: MajorVersion 30 | value: 0.0 31 | - name: MinorVersion 32 | value: $[counter(format('{0}-{1}', variables['Build.SourceBranch'], variables['MajorVersion']), 0)] 33 | - name: IsMain 34 | value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')] 35 | - name: IsPr 36 | value: $[contains(variables['Build.SourceBranch'], 'refs/pull/')] 37 | - name: IsBranch 38 | value: $[and(eq(variables['IsPr'], 'False'), eq(variables['IsMain'], 'False'))] 39 | 40 | jobs: 41 | - job: 'Job' 42 | displayName: 'Build & Publish' 43 | pool: 44 | vmImage: 'ubuntu-18.04' 45 | steps: 46 | - task: CopyFiles@2 47 | displayName: 'Copy Infrastructure Source Files' 48 | inputs: 49 | SourceFolder: 'infrastructure/src' 50 | Contents: '**' 51 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 52 | 53 | - task: CopyFiles@2 54 | displayName: 'Copy Infrastructure Deployment Scripts' 55 | inputs: 56 | SourceFolder: 'infrastructure/deployment/scripts' 57 | Contents: '**' 58 | TargetFolder: '$(Build.ArtifactStagingDirectory)/scripts' 59 | 60 | - task: CopyFiles@2 61 | displayName: 'Copy Infrastructure Test Files' 62 | inputs: 63 | SourceFolder: 'infrastructure/tests' 64 | Contents: '**' 65 | TargetFolder: '$(Build.ArtifactStagingDirectory)/tests' 66 | 67 | - task: PublishBuildArtifacts@1 68 | displayName: 'Publish Build Artifact' 69 | inputs: 70 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 71 | ArtifactName: 'drop' 72 | publishLocation: 'Container' 73 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Certbot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables passed through using pipelines 4 | DOMAIN_NAME=$1 5 | 6 | # Install SSL certificates 7 | # We can always run this script, no need to first time run 8 | certbot --nginx -d $DOMAIN_NAME --email 'teamumigrate@gmail.com' --agree-tos -n 9 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Crontab.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install the cron job 4 | crontab /home/umigrate/crontab/umigratecron 5 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/DBBackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create backups folder 4 | DIR="/home/umigrate/backups" 5 | if [ ! -d "$DIR" ] 6 | then 7 | mkdir /home/umigrate/backups 8 | fi 9 | 10 | # Clean backups folder 11 | rm -rf /home/umigrate/backups/*.tar 12 | 13 | # Intake environment variable 14 | export $(egrep -v '^#' /home/umigrate/venv/.env | xargs) 15 | 16 | # Differentiating between weekly and version backup 17 | fileName="" 18 | if [ -z "$1" ] 19 | then 20 | fileName=$STAGE_ENVIRONMENT-umigratedb-$(date --iso-8601).tar 21 | else 22 | fileName=$STAGE_ENVIRONMENT-umigratedb-$1.tar 23 | fi 24 | 25 | # Backing up the DB 26 | sudo -u postgres pg_dump -U postgres -w -F t umigratedb > /home/umigrate/backups/$fileName 27 | 28 | # Sending backup to Azure 29 | /home/umigrate/maintenance/azcopy/azcopy copy "/home/umigrate/backups/${fileName}" "https://umigratefilestorage.blob.core.windows.net/databasebackups/${SAS_TOKEN}" 30 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/DBRestore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create backups folder 4 | DIR="/home/umigrate/backups" 5 | if [ ! -d "$DIR" ] 6 | then 7 | mkdir /home/umigrate/backups 8 | fi 9 | 10 | # Clean backups folder 11 | rm -rf /home/umigrate/backups/*.tar 12 | 13 | # Intake file name 14 | fileName="" 15 | if [ -z "$1" ] 16 | then 17 | echo NO FILE NAME SPECIFIED, EXITING... 18 | exit -1 19 | else 20 | fileName=$1 21 | 22 | #Download the file from Azure 23 | /home/umigrate/maintenance/azcopy/azcopy copy --recursive "https://umigratefilestorage.blob.core.windows.net/databasebackups/${fileName}${SAS_TOKEN}" /home/umigrate/backups/ 24 | 25 | #Delete the current DB 26 | sudo -u postgres psql -c "DROP DATABASE umigratedb;" 27 | 28 | #Create a new DB 29 | sudo -u postgres psql -c "CREATE DATABASE umigratedb;" 30 | 31 | #Restoring the DB 32 | sudo -u postgres pg_restore -d umigratedb /home/umigrate/backups/$fileName 33 | fi 34 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/DownloadAzCopy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create azcopy folder 4 | DIR="/home/umigrate/maintenance/azcopy" 5 | if [ ! -d "$DIR" ] 6 | then 7 | mkdir /home/umigrate/maintenance/azcopy 8 | fi 9 | cd /home/umigrate/maintenance/azcopy 10 | 11 | # Download AzCopy tool 12 | wget -O azcopy_v10.tar.gz https://aka.ms/downloadazcopy-v10-linux && tar -xf azcopy_v10.tar.gz --strip-components=1 13 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/EnableServices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Enable daphne, gunicorn, nginx 4 | # These can always be run, won't harm anything 5 | systemctl daemon-reload 6 | systemctl enable daphne@{0..2} 7 | systemctl enable gunicorn.socket 8 | systemctl enable redis-server 9 | systemctl enable nginx 10 | systemctl enable fail2ban 11 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/InstallApps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Update and install required applications 4 | add-apt-repository ppa:certbot/certbot -y 5 | add-apt-repository ppa:chris-lea/redis-server -y 6 | add-apt-repository ppa:deadsnakes/ppa -y 7 | wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 8 | echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" |sudo tee /etc/apt/sources.list.d/pgdg.list 9 | apt update 10 | apt-get install python3.9 python3-pip python3-dev python3.9-distutils libpq-dev postgresql-13 postgresql-contrib-13 nginx tree virtualenv redis-server python-certbot-nginx fail2ban -y 11 | 12 | # Symlink python version 13 | ln -sf /usr/bin/python3.9 /usr/bin/py 14 | 15 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DOMAIN_NAME=$1 3 | 4 | # Symlinking 5 | # To prevent extra junk in pipelines, we can do ln -sf 6 | # For the future, hardcode dev.umigrate.ca and umigrate.ca 7 | ln -sf /etc/nginx/sites-available/$DOMAIN_NAME /etc/nginx/sites-enabled 8 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables passed through using pipelines 4 | DATABASE_PASSWORD=$1 5 | 6 | # Only create database if database doesn't exist 7 | if [ -z $(sudo -u postgres psql -lqt | cut -d\| -f 1 | grep umigratedb) ]; then 8 | sudo -u postgres psql \ 9 | -c "CREATE DATABASE umigratedb;" \ 10 | -c "CREATE USER umigrate WITH PASSWORD '$DATABASE_PASSWORD';" \ 11 | -c "ALTER ROLE umigrate SET client_encoding TO 'utf8';" \ 12 | -c "ALTER ROLE umigrate SET default_transaction_isolation TO 'read committed';" \ 13 | -c "ALTER ROLE umigrate SET timezone TO 'UTC';" \ 14 | -c "GRANT ALL PRIVILEGES ON DATABASE umigratedb TO umigrate;" 15 | fi 16 | 17 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Redis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables passed through using pipelines 4 | REDIS_PASSWORD=$1 5 | 6 | # Set Redis password and configure Redis to be supervised by systemd 7 | # TODO: fix supervised no to change to supervised systemd 8 | sed -i "s/# requirepass foobared/requirepass $REDIS_PASSWORD/g" /etc/redis/redis.conf 9 | sed -i "s/supervised no/supervised systemd/g" /etc/redis/redis.conf 10 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Security.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Edit /etc/sysctl.conf 4 | sed -i "s/#net.ipv4.conf.default.rp_filter=1/net.ipv4.conf.default.rp_filter=1/g" /etc/sysctl.conf 5 | sed -i "s/#net.ipv4.conf.all.rp_filter=1/net.ipv4.conf.all.rp_filter=1/g" /etc/sysctl.conf 6 | sed -i "s/#net.ipv4.conf.all.accept_redirects = 0/net.ipv4.conf.all.accept_redirects = 0/g" /etc/sysctl.conf 7 | sed -i "s/#net.ipv6.conf.all.accept_redirects = 0/net.ipv6.conf.all.accept_redirects = 0/g" /etc/sysctl.conf 8 | sed -i "s/#net.ipv4.conf.all.send_redirects = 0/net.ipv4.conf.all.send_redirects = 0/g" /etc/sysctl.conf 9 | sed -i "s/#net.ipv4.conf.all.accept_source_route = 0/net.ipv4.conf.all.accept_source_route = 0/g" /etc/sysctl.conf 10 | sed -i "s/#net.ipv6.conf.all.accept_source_route = 0/net.ipv6.conf.all.accept_source_route = 0/g" /etc/sysctl.conf 11 | sed -i "s/#net.ipv4.conf.all.log_martians = 1/net.ipv4.conf.all.log_martians = 1/g" /etc/sysctl.conf 12 | 13 | # Edit /etc/host.conf 14 | sed -i "s/order hosts,bind/order bind,hosts/g" /etc/host.conf 15 | 16 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/StartServices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Starting gunicorn, daphne, redis-server, and nginx 4 | # This is always run 5 | systemctl daemon-reload 6 | systemctl start gunicorn.socket 7 | systemctl start gunicorn 8 | systemctl start daphne@{0..2} 9 | systemctl start redis-server 10 | systemctl start nginx 11 | systemctl start fail2ban 12 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/StopServices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Stopping nginx, redis-server, daphne, gunicorn 4 | # Stop only if services exist. 5 | STATUS_NGINX="$(systemctl is-active nginx)" 6 | STATUS_REDIS="$(systemctl is-active redis-server)" 7 | STATUS_DAPHNE0="$(systemctl is-active daphne@0)" 8 | STATUS_DAPHNE1="$(systemctl is-active daphne@1)" 9 | STATUS_DAPHNE2="$(systemctl is-active daphne@2)" 10 | STATUS_GUNICORNSOCK="$(systemctl is-active gunicorn.socket)" 11 | STATUS_GUNICORN="$(systemctl is-active gunicorn)" 12 | 13 | if [ "$STATUS_NGINX" = "active" ]; then 14 | systemctl stop nginx 15 | fi 16 | 17 | if [ "$STATUS_REDIS" = "active" ]; then 18 | systemctl stop redis-server 19 | fi 20 | 21 | if [ "$STATUS_DAPHNE0" = "active" ] && [ "$STATUS_DAPHNE1" = "active" ] && [ "$STATUS_DAPHNE2" = "active" ]; then 22 | systemctl stop daphne@{0..2} 23 | fi 24 | 25 | if [ "$STATUS_GUNICORNSOCK" = "active" ]; then 26 | systemctl stop gunicorn.socket 27 | fi 28 | 29 | if [ "$STATUS_GUNICORN" = "active" ]; then 30 | systemctl stop gunicorn 31 | fi 32 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/SystemConfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set timezone to Eastern 4 | timedatectl set-timezone America/Toronto 5 | 6 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/UFW.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Configure firewall 4 | # This can always be run 5 | ufw allow 'OpenSSH' 6 | ufw allow 'Nginx Full' 7 | ufw default deny incoming 8 | ufw default allow outgoing 9 | echo y | ufw enable 10 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Update the server packages 4 | apt-get update 5 | apt-get upgrade -y 6 | apt-get autoremove -y 7 | -------------------------------------------------------------------------------- /infrastructure/deployment/scripts/Virtualenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Variables passed through using pipelines 4 | SECRET_KEY=$1 5 | DATABASE_PASSWORD=$2 6 | REDIS_PASSWORD=$3 7 | SENDGRID_API_KEY=$4 8 | DOMAIN_NAME=$5 9 | STAGE_ENVIRONMENT=$6 10 | UW_API_KEY=$7 11 | SAS_TOKEN=$8 12 | 13 | # Create virtualenv 14 | py -m pip install --upgrade pip 15 | py -m pip install virtualenv 16 | 17 | # Make dir for venv 18 | DIR="/home/umigrate/venv/" 19 | if [ ! -d "$DIR" ]; then 20 | mkdir /home/umigrate/venv 21 | fi 22 | 23 | virtualenv --python=/usr/bin/py /home/umigrate/venv 24 | 25 | # Send pipeline variables to environment variables file 26 | echo SECRET_KEY=$SECRET_KEY$'\n'\ 27 | DATABASE_PASSWORD=$DATABASE_PASSWORD$'\n'\ 28 | REDIS_PASSWORD=$REDIS_PASSWORD$'\n'\ 29 | SENDGRID_API_KEY=$SENDGRID_API_KEY$'\n'\ 30 | DOMAIN_NAME=$DOMAIN_NAME$'\n'\ 31 | STAGE_ENVIRONMENT=$STAGE_ENVIRONMENT$'\n'\ 32 | UW_API_KEY=$UW_API_KEY$'\n'\ 33 | SAS_TOKEN=$SAS_TOKEN$'\n' > /home/umigrate/venv/.env 34 | -------------------------------------------------------------------------------- /infrastructure/src/crontab/umigratecron: -------------------------------------------------------------------------------- 1 | 0 0 * * * export $(egrep -v '^#' /home/umigrate/venv/.env | xargs) && python3 /home/umigrate/api/manage.py clearsessions 2 | 0 0 * * 0 /home/umigrate/maintenance/scripts/DBBackup.sh 3 | 0 0 1 * * /home/umigrate/maintenance/scripts/Certbot.sh 4 | -------------------------------------------------------------------------------- /infrastructure/src/services/daphne@.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=daphne server script #%i 4 | After=network.target 5 | 6 | [Service] 7 | WorkingDirectory=/home/umigrate/api 8 | Environment=DJANGO_SETTINGS_MODULE=umigrate.settings 9 | EnvironmentFile=/home/umigrate/venv/.env 10 | ExecStart=/home/umigrate/venv/bin/daphne \ 11 | --bind 0.0.0.0 \ 12 | --port 8001 \ 13 | umigrate.asgi:application 14 | Restart=always 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /infrastructure/src/services/gunicorn.service: -------------------------------------------------------------------------------- 1 | 2 | [Unit] 3 | Description=gunicorn daemon 4 | Requires=gunicorn.socket 5 | After=network.target 6 | 7 | [Service] 8 | User=root 9 | Group=www-data 10 | WorkingDirectory=/home/umigrate/api 11 | EnvironmentFile=/home/umigrate/venv/.env 12 | ExecStart=/home/umigrate/venv/bin/gunicorn \ 13 | --access-logfile - \ 14 | --workers 3 \ 15 | --bind unix:/run/gunicorn.sock \ 16 | umigrate.wsgi:application 17 | 18 | [Install] 19 | WantedBy=multi-user.target 20 | -------------------------------------------------------------------------------- /infrastructure/src/sites/dev.umigrate.ca: -------------------------------------------------------------------------------- 1 | upstream websocket { 2 | server 127.0.0.0:8001; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_name dev.umigrate.ca; 8 | root /home/umigrate/website; 9 | index index.html index.htm; 10 | 11 | location = /favicon.ico { 12 | access_log off; 13 | log_not_found off; 14 | } 15 | 16 | location ~ ^/(static|media)/ { 17 | root /home/umigrate; 18 | } 19 | 20 | location /ws/ { 21 | include proxy_params; 22 | proxy_pass http://0.0.0.0:8001; 23 | proxy_http_version 1.1; 24 | proxy_set_header Upgrade $http_upgrade; 25 | proxy_set_header Connection "upgrade"; 26 | } 27 | 28 | location ~ ^/(admin|api|swagger)/ { 29 | include proxy_params; 30 | proxy_pass http://unix:/run/gunicorn.sock; 31 | } 32 | 33 | location / { 34 | try_files $uri /index.html =404; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /infrastructure/src/sites/umigrate.ca: -------------------------------------------------------------------------------- 1 | upstream websocket { 2 | server 127.0.0.0:8001; 3 | } 4 | 5 | server { 6 | listen 80; 7 | server_name umigrate.ca; 8 | root /home/umigrate/website; 9 | index index.html index.htm; 10 | 11 | location = /favicon.ico { 12 | access_log off; 13 | log_not_found off; 14 | } 15 | 16 | location ~ ^/(static|media)/ { 17 | root /home/umigrate; 18 | } 19 | 20 | location /ws/ { 21 | include proxy_params; 22 | proxy_pass http://0.0.0.0:8001; 23 | proxy_http_version 1.1; 24 | proxy_set_header Upgrade $http_upgrade; 25 | proxy_set_header Connection "upgrade"; 26 | } 27 | 28 | location ~ ^/(admin|api|swagger)/ { 29 | include proxy_params; 30 | proxy_pass http://unix:/run/gunicorn.sock; 31 | } 32 | 33 | location / { 34 | try_files $uri /index.html =404; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /infrastructure/src/sockets/gunicorn.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=gunicorn socket 3 | 4 | [Socket] 5 | ListenStream=/run/gunicorn.sock 6 | 7 | [Install] 8 | WantedBy=sockets.target 9 | -------------------------------------------------------------------------------- /infrastructure/tests/SystemTests/SystemTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /infrastructure/tests/SystemTests/SystemTests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30517.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemTests", "SystemTests.csproj", "{F8ED7726-E347-4F23-A1DF-1EB0ED3FCE4E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {F8ED7726-E347-4F23-A1DF-1EB0ED3FCE4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {F8ED7726-E347-4F23-A1DF-1EB0ED3FCE4E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {F8ED7726-E347-4F23-A1DF-1EB0ED3FCE4E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {F8ED7726-E347-4F23-A1DF-1EB0ED3FCE4E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {190C8E91-2877-4155-9E7B-E638EE373E29} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /infrastructure/tests/SystemTests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using OpenQA.Selenium; 3 | using OpenQA.Selenium.Chrome; 4 | using System; 5 | 6 | namespace SystemTests 7 | { 8 | public class Tests 9 | { 10 | String test_url = "https://www.google.com"; 11 | 12 | IWebDriver driver; 13 | 14 | [SetUp] 15 | public void start_Browser() 16 | { 17 | // Local Selenium WebDriver 18 | driver = new ChromeDriver(); 19 | driver.Manage().Window.Maximize(); 20 | } 21 | 22 | [Test] 23 | public void test_search() 24 | { 25 | driver.Url = test_url; 26 | 27 | System.Threading.Thread.Sleep(2000); 28 | 29 | IWebElement searchText = driver.FindElement(By.CssSelector("[name = 'q']")); 30 | 31 | searchText.SendKeys("LambdaTest\n"); 32 | 33 | System.Threading.Thread.Sleep(6000); 34 | 35 | Console.WriteLine("Test Passed"); 36 | } 37 | 38 | [TearDown] 39 | public void close_Browser() 40 | { 41 | driver.Quit(); 42 | } 43 | } 44 | } --------------------------------------------------------------------------------