├── .circleci └── config.yml ├── .github ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── cypress-tests.yml │ ├── dev-deploy.yml │ ├── emojify-pr.yml │ ├── prod-deploy.yml │ └── pytest-tests.yml ├── .gitignore ├── .slugignore ├── LICENSE ├── Procfile ├── README.md ├── backend ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api │ ├── __init__.py │ ├── core.py │ ├── models │ │ ├── Admin.py │ │ ├── Announcement.py │ │ ├── AppointmentRequest.py │ │ ├── Availability.py │ │ ├── CommunityLibrary.py │ │ ├── Countries.py │ │ ├── DirectMessage.py │ │ ├── Education.py │ │ ├── Event.py │ │ ├── GroupMessage.py │ │ ├── Guest.py │ │ ├── Hub.py │ │ ├── Image.py │ │ ├── Languages.py │ │ ├── MenteeApplication.py │ │ ├── MenteeProfile.py │ │ ├── MentorApplication.py │ │ ├── MentorProfile.py │ │ ├── Message.py │ │ ├── Moderator.py │ │ ├── NewMentorApplication.py │ │ ├── Notifications.py │ │ ├── PartnerApplication.py │ │ ├── PartnerGroupMessage.py │ │ ├── PartnerProfile.py │ │ ├── SignOrigin.py │ │ ├── SignedDocs.py │ │ ├── Specializations.py │ │ ├── Support.py │ │ ├── Training.py │ │ ├── Translations.py │ │ ├── Users.py │ │ ├── VerifiedEmail.py │ │ ├── Video.py │ │ ├── __init__.py │ │ ├── base.py │ │ └── model_class_possibilities │ │ │ └── Model_id_recursion.py │ ├── utils │ │ ├── constants.py │ │ ├── firebase.py │ │ ├── flask_imgur.py │ │ ├── google_storage.py │ │ ├── jaas_jwt_builder.py │ │ ├── profile_parse.py │ │ ├── request_utils.py │ │ ├── require_auth.py │ │ └── translate.py │ └── views │ │ ├── admin.py │ │ ├── admin_notifications.py │ │ ├── announcement.py │ │ ├── app_blueprint.py │ │ ├── apply.py │ │ ├── appointment.py │ │ ├── auth.py │ │ ├── availability.py │ │ ├── download.py │ │ ├── events.py │ │ ├── main.py │ │ ├── masters.py │ │ ├── meeting.py │ │ ├── mentee.py │ │ ├── messages.py │ │ ├── notifications.py │ │ ├── training.py │ │ ├── translation.py │ │ └── verify.py ├── manage.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt ├── runtime.txt ├── scripts.py ├── scripts │ └── emails.py ├── tests │ ├── .env.example │ ├── __init__.py │ ├── conftest.py │ ├── test_admin.py │ ├── test_apply.py │ ├── test_appointments.py │ ├── test_auth.py │ ├── test_basic.py │ ├── test_events.py │ ├── test_explore.py │ ├── test_messages.py │ ├── test_private.py │ ├── test_profile.py │ ├── test_training.py │ ├── test_verify_user.py │ ├── test_videos.py │ └── utils │ │ ├── consts.json │ │ ├── login_utils.py │ │ └── profile_utils.py └── user_options.json ├── docker-compose.yml ├── docs ├── .nojekyll ├── AnotherReadMe.md ├── README.md ├── _coverpage.md ├── _sidebar.md ├── backend │ ├── file_structure.md │ └── setup.md ├── frontend │ └── setup.md ├── index.html └── media │ └── mentee.png ├── frontend ├── .gitignore ├── Dockerfile ├── README.md ├── jsconfig.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── locales │ │ ├── ar │ │ │ └── translation.json │ │ ├── en-US │ │ │ └── translation.json │ │ ├── es-US │ │ │ └── translation.json │ │ ├── fa-AF │ │ │ └── translation.json │ │ ├── pa-AR │ │ │ └── translation.json │ │ └── pt-BR │ │ │ └── translation.json │ ├── manifest.json │ ├── mentee-logo.ico │ ├── pdf.worker.min.js │ ├── pdf.worker.min.mjs │ └── robots.txt ├── src │ ├── app │ │ ├── App.js │ │ └── store.js │ ├── components │ │ ├── AddAppointmentModal.js │ │ ├── AddEventModal.js │ │ ├── AddGuestModal.js │ │ ├── AdminAppointmentCard.js │ │ ├── AdminAppointmentModal.js │ │ ├── AdminDataTable.js │ │ ├── AdminDownloadDropdown.js │ │ ├── AdminDropdowns.js │ │ ├── AdminPartnerData.js │ │ ├── AppointmentInfo.js │ │ ├── AvailabilityCalendar.js │ │ ├── BookmarkSidebar.js │ │ ├── CreateMeetingLink.js │ │ ├── DigitalSignModal.js │ │ ├── EditProfileModal.js │ │ ├── EventCard.js │ │ ├── GroupMessageChatArea.js │ │ ├── HubCard.js │ │ ├── HubFooter.js │ │ ├── HubForm.js │ │ ├── Hubs.js │ │ ├── Initiator.js │ │ ├── LanguageDropdown.js │ │ ├── Languages.js │ │ ├── LoginForm.js │ │ ├── LoginVerificationModal.js │ │ ├── MeetingPanel.js │ │ ├── MenteeAppInfo.js │ │ ├── MenteeAppointmentModal.js │ │ ├── MenteeButton.js │ │ ├── MenteeCard.js │ │ ├── MenteeInterestModal.js │ │ ├── MenteeMessageTab.js │ │ ├── MenteeSidebar.js │ │ ├── MenteeVerificationModal.js │ │ ├── MenteeVideo.js │ │ ├── MentorAppInfo.js │ │ ├── MentorAppProgress.js │ │ ├── MentorApplicationView.js │ │ ├── MentorCard.js │ │ ├── MentorContactModal.js │ │ ├── MentorProfileModal.js │ │ ├── MentorSidebar.js │ │ ├── MentorVideo.js │ │ ├── MessageCard.js │ │ ├── MessagesChatArea.js │ │ ├── MessagesSidebar.js │ │ ├── ModalInput.js │ │ ├── Navigation.js │ │ ├── NavigationHeader.js │ │ ├── NavigationSider.js │ │ ├── NewMentorAppInfo.js │ │ ├── NotificationBell.js │ │ ├── OverlaySelect.js │ │ ├── PartnerCard.js │ │ ├── PartnerProfileForm.js │ │ ├── PartnerProfileModal.js │ │ ├── PartnerSidebar.js │ │ ├── PrivateRoute.js │ │ ├── ProfileContent.js │ │ ├── ProfileVideos.js │ │ ├── PublicMessageModal.js │ │ ├── PublicRoute.js │ │ ├── SearchMessageCard.js │ │ ├── SelectCard.js │ │ ├── SocketComponent.js │ │ ├── Specializations.js │ │ ├── TrainingList.js │ │ ├── TrainingTranslationModal.js │ │ ├── UpdateAnnouncementModal.js │ │ ├── UpdateCommunityLibraryModal.js │ │ ├── UpdateTrainingModal.js │ │ ├── UploadEmails.js │ │ ├── VideoSubmit.js │ │ ├── css │ │ │ ├── AdminAccountData.scss │ │ │ ├── AdminAppointments.scss │ │ │ ├── AdminMessages.scss │ │ │ ├── Apply.scss │ │ │ ├── Appointments.scss │ │ │ ├── AvailabilityCalendar.scss │ │ │ ├── Gallery.scss │ │ │ ├── Home.scss │ │ │ ├── Login.scss │ │ │ ├── MenteeAppointments.scss │ │ │ ├── MenteeButton.scss │ │ │ ├── MenteeModal.scss │ │ │ ├── MenteeVideo.scss │ │ │ ├── MentorApplicationPage.scss │ │ │ ├── MentorApplicationView.scss │ │ │ ├── MentorModal.scss │ │ │ ├── MentorVideo.scss │ │ │ ├── Messages.scss │ │ │ ├── Modal.scss │ │ │ ├── Navigation.scss │ │ │ ├── NotFound.scss │ │ │ ├── Profile.scss │ │ │ ├── PublicProfile.scss │ │ │ ├── Register.scss │ │ │ ├── RegisterForm.scss │ │ │ ├── SelectLogin.scss │ │ │ ├── Training.scss │ │ │ ├── TrainingList.scss │ │ │ ├── UploadEmails.scss │ │ │ ├── VerificationModal.scss │ │ │ ├── Videos.scss │ │ │ └── _app.scss │ │ └── pages │ │ │ ├── AdminAccountData.js │ │ │ ├── AdminAnnouncement.js │ │ │ ├── AdminAppointmentData.js │ │ │ ├── AdminLogin.js │ │ │ ├── AdminSeeMessages.js │ │ │ ├── AdminSign.js │ │ │ ├── AdminTraining.js │ │ │ ├── AnnouncementDetail.js │ │ │ ├── Announcements.js │ │ │ ├── ApplicationForm.js │ │ │ ├── ApplicationOrganizer.js │ │ │ ├── Apply.js │ │ │ ├── Appointments.js │ │ │ ├── BuildProfile.js │ │ │ ├── CommunityLibrary.js │ │ │ ├── EventDetail.js │ │ │ ├── Events.js │ │ │ ├── ForgotPassword.js │ │ │ ├── Gallery.js │ │ │ ├── GroupMessages.js │ │ │ ├── Home.js │ │ │ ├── HomeLayout.js │ │ │ ├── HubGallery.js │ │ │ ├── HubInviteLink.js │ │ │ ├── Login.js │ │ │ ├── MenteeApplication.js │ │ │ ├── MenteeAppointments.js │ │ │ ├── MenteeGallery.js │ │ │ ├── MenteeProfileForm.js │ │ │ ├── MentorApplication.js │ │ │ ├── MentorProfileForm.js │ │ │ ├── Messages.js │ │ │ ├── NewTrainingConfirm.js │ │ │ ├── PartnerApplication.js │ │ │ ├── PartnerGallery.js │ │ │ ├── Profile.js │ │ │ ├── PublicProfile.js │ │ │ ├── SelectLogin.js │ │ │ ├── SupportLogin.js │ │ │ ├── Training.js │ │ │ ├── TrainingData.js │ │ │ ├── Verify.js │ │ │ └── Videos.js │ ├── features │ │ ├── meetingPanelSlice.js │ │ ├── messagesSlice.js │ │ ├── notificationsSlice.js │ │ ├── optionsSlice.js │ │ └── userSlice.js │ ├── index.js │ ├── index.scss │ ├── resources │ │ ├── AddBookmarkMentor.svg │ │ ├── AdminLogin.svg │ │ ├── MenteeLogin.svg │ │ ├── Mentee_logo_letter.png │ │ ├── Mentee_logo_letter.svg │ │ ├── Mentee_logo_small.png │ │ ├── MentorLogin.svg │ │ ├── N50 Logo_small.png │ │ ├── N50_logo.png │ │ ├── adinkra.png │ │ ├── admin-login-logo.png │ │ ├── apply.png │ │ ├── applystep.png │ │ ├── applystep2.png │ │ ├── focus-for-health.svg │ │ ├── login.png │ │ ├── logo.png │ │ ├── mentee-login-logo.png │ │ ├── mentee-login-logo.webp │ │ ├── mentee.png │ │ ├── mentee.svg │ │ ├── menteeSmall.png │ │ ├── menteeSmall.svg │ │ ├── mentor-login-logo.png │ │ ├── mentor-login-logo.webp │ │ ├── mentorimage.png │ │ ├── partner-login-logo.webp │ │ ├── partner.png │ │ ├── profilestep.png │ │ ├── profilestep2.png │ │ ├── puppy.png │ │ ├── thankYou.png │ │ ├── trainstep.png │ │ └── trainstep2.png │ ├── static.json │ ├── tests │ │ ├── App.test.js │ │ └── setupTests.js │ └── utils │ │ ├── api.js │ │ ├── auth.service.js │ │ ├── consts.js │ │ ├── dateFormatting.js │ │ ├── fireauth.js │ │ ├── hooks │ │ ├── useAuth.js │ │ ├── useInterval.js │ │ ├── usePersistedState.js │ │ ├── useQuery.js │ │ └── useSidebars.js │ │ ├── i18n.js │ │ ├── inputs.js │ │ ├── misc.js │ │ ├── serviceWorker.js │ │ ├── socket.js │ │ ├── translations.js │ │ └── verifyMentee.js └── static.json ├── package.json ├── requirements.txt ├── runtime.txt ├── start.sh ├── static.json └── tests ├── README.md ├── cypress.config.js ├── cypress.env.json └── cypress ├── e2e ├── pages │ ├── AdminAccountDataPage.js │ ├── AdminFindMenteePage.js │ ├── AdminFindMentorPage.js │ ├── AdminFindPartnerPage.js │ ├── AdminLoginPage.js │ ├── ApplyPage.js │ ├── ExplorePage.js │ ├── GuestLogin.js │ ├── HomePage.js │ ├── LoginPage.js │ ├── MentorDashboard.js │ ├── MentorEvents.js │ └── ProfilePage.js └── test │ ├── AdminAccountDataTest.cy.js │ ├── AdminFindMenteeTest.cy.js │ ├── AdminFindMentorTest.cy.js │ ├── AdminFindPartnerTest.cy.js │ ├── AdminLoginPageTest.cy.js │ ├── ApplicationMenteePageTest.cy.js │ ├── ApplicationMentorPageTest.cy.js │ ├── ApplyFormMentorVisibility.cy.js │ ├── ApplyRegisterMenteecheck.cy.js │ ├── ApplyRegisterMentorcheck.cy.js │ ├── ApplyformMenteeVisibility.cy.js │ ├── ApplytestChatsMenteeToMentor.cy.js │ ├── ApplytestChatsMentorToMentee.cy.js │ ├── ExistanceApplyPageTest.cy.js │ ├── ExplorePageMenteeTest.cy.js │ ├── ExplorePageMentorTest.cy.js │ ├── GuestLoggedInPage.cy.js │ ├── HomePageTest.cy.js │ ├── LoginPageTest.cy.js │ ├── MentorDashboardTest.cy.js │ ├── MentorEventsTest.cy.js │ ├── MentorVideopageTest.cy.js │ ├── PartnerProfilePage.cy.js │ └── ProfileTest.cy.js ├── fixtures └── example.json └── support ├── commands.js ├── commands.ts └── e2e.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | python: circleci/python@0.2.1 5 | 6 | jobs: 7 | backend-format: 8 | docker: 9 | - image: circleci/python:3.9 10 | steps: 11 | - checkout 12 | - run: 13 | command: | 14 | cd backend 15 | pip install -r requirements-dev.txt --user 16 | /home/circleci/.local/bin/black . --check 17 | frontend-format: 18 | docker: 19 | - image: cimg/node:16.19.0 20 | steps: 21 | - checkout 22 | - run: 23 | command: | 24 | cd frontend 25 | yarn 26 | yarn format:check 27 | 28 | workflows: 29 | main: 30 | jobs: 31 | - backend-format 32 | - frontend-format 33 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 4 | #### Description 5 | A clear and concise description of what the issue is about. 6 | 7 | #### Screenshots 8 | ![Bunny and carrot](https://media.giphy.com/media/l0HlCc9qfYJ6lZrzO/giphy.gif) 9 | 10 | #### Files 11 | A list of relevant files for this issue. This will help people navigate the project and offer some clues of where to start. 12 | 13 | #### To Reproduce 14 | If this issue is describing a bug, include some steps to reproduce the behavior. 15 | 16 | #### Tasks 17 | Include specific tasks in the order they need to be done in. Include links to specific lines of code where the task should happen at. 18 | - [ ] Task 1 19 | - [ ] Task 2 20 | - [ ] Task 3 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 5 | ## Status: 6 | 11 | 12 | ## Description :star2: 13 | 17 | Fixes #number 18 | 19 | ## TODOs :star: 20 | 24 | -------------------------------------------------------------------------------- /.github/workflows/dev-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Dev 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: 8 | - dev 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | defaults: 18 | run: 19 | working-directory: frontend 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v3.3.0 25 | 26 | # Run yarn build 27 | - name: run yarn build 28 | uses: actions/setup-node@v3.6.0 29 | with: 30 | node-version: "16" 31 | check-latest: true 32 | 33 | - name: Install and Build 34 | env: 35 | # set CI to false because warnings are treated as errors if CI=true 36 | # manually set react env here because NODE_ENV=production on build 37 | CI: false 38 | REACT_APP_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} 39 | REACT_APP_ENV: development 40 | run: yarn install && yarn run build 41 | 42 | - name: save build artifacts 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: frontend-build 46 | path: ${{ github.workspace }}/frontend/build 47 | retention-days: 1 48 | if-no-files-found: error 49 | 50 | deploy: 51 | runs-on: ubuntu-latest 52 | needs: build 53 | steps: 54 | - uses: actions/checkout@v3.3.0 55 | 56 | - name: Downloading build artifacts 57 | uses: actions/download-artifact@v4 58 | with: 59 | name: frontend-build 60 | # Use the path artifacts because frontend/build is in gitignore 61 | path: ${{ github.workspace }}/frontend/artifacts 62 | 63 | - name: Install Heroku CLI 64 | run: | 65 | curl https://cli-assets.heroku.com/install.sh | sh 66 | 67 | - name: Deploy to Heroku 68 | uses: AkhileshNS/heroku-deploy@v3.12.13 69 | with: 70 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 71 | heroku_app_name: "mentee-dev" 72 | heroku_email: "h4i.mentee.auth@gmail.com" 73 | branch: dev 74 | dontuseforce: false # This allows force pushing to Heroku -------------------------------------------------------------------------------- /.github/workflows/emojify-pr.yml: -------------------------------------------------------------------------------- 1 | name: Emojify PR Title 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | emojify-pr-title: 7 | runs-on: ubuntu-latest 8 | name: Emojify PR Title 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Emojify PR Title 13 | uses: pineapplelol/emojify-pr-title@v1.4.1 14 | -------------------------------------------------------------------------------- /.github/workflows/prod-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Prod 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the master branch 6 | push: 7 | branches: 8 | - main 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | defaults: 18 | run: 19 | working-directory: frontend 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v3.3.0 25 | 26 | # Run yarn build 27 | - name: run yarn build 28 | uses: actions/setup-node@v3.6.0 29 | with: 30 | node-version: "16" 31 | check-latest: true 32 | 33 | - name: Install and Build 34 | env: 35 | # set CI to false because warnings are treated as errors if CI=true 36 | # manually set react env here because NODE_ENV=production on build 37 | CI: false 38 | REACT_APP_FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} 39 | REACT_APP_ENV: production 40 | run: yarn install && yarn run build 41 | 42 | - name: save build artifacts 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: frontend-build 46 | path: ${{ github.workspace }}/frontend/build 47 | retention-days: 1 48 | if-no-files-found: error 49 | 50 | deploy: 51 | runs-on: ubuntu-latest 52 | needs: build 53 | steps: 54 | - uses: actions/checkout@v3.3.0 55 | 56 | - name: Downloading build artifacts 57 | uses: actions/download-artifact@v4 58 | with: 59 | name: frontend-build 60 | path: ${{ github.workspace }}/frontend/artifacts 61 | 62 | - name: Install Heroku CLI 63 | run: | 64 | curl https://cli-assets.heroku.com/install.sh | sh 65 | 66 | - name: Deploy to Heroku 67 | uses: AkhileshNS/heroku-deploy@v3.12.13 68 | with: 69 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 70 | heroku_app_name: "mentee-h4i" 71 | heroku_email: "h4i.mentee.auth@gmail.com" 72 | branch: main 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | venv 3 | node_modules 4 | backend/firebase_service_key.json 5 | package-lock.json 6 | yarn.lock 7 | heroku-logs-prod.txt 8 | heroku-logs.txt 9 | poetry.lock 10 | pyproject.toml000 11 | 12 | # macos 13 | .DS_Store 14 | app.log 15 | backend/app.log 16 | tests/.env 17 | tests/cypress/screenshots/ -------------------------------------------------------------------------------- /.slugignore: -------------------------------------------------------------------------------- 1 | package.json 2 | frontend/src/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hack4Impact UIUC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: cd backend && gunicorn --workers 1 --threads 256 --worker-class eventlet manage:app 2 | worker: cd backend && python manage.py runworker 3 | clock: python backend/scripts/emails.py 4 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | !api 2 | !manage.py 3 | !requirements.txt 4 | !creds.ini 5 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Backend .gitignore 2 | myenv/ 3 | venv/ 4 | env/ 5 | .env 6 | __pycache__/ 7 | *.pyc 8 | .vscode/ 9 | .DS_Store/ 10 | migrations/ 11 | postgres-data/ 12 | .mypy_cache 13 | .pytest_cache 14 | api.log 15 | test.db 16 | creds.ini 17 | .env 18 | firebase_service_key.json 19 | app.log 20 | translate-dev.json -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # this dockerfile is used for product deployments 2 | FROM python:3.9 3 | LABEL maintainer "Kelley Chau " 4 | 5 | COPY requirements.txt requirements.txt 6 | RUN apt-get update -y && \ 7 | apt-get install -y python3-dev python3-pkg-resources python3-setuptools python3-wheel python3-pip 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | COPY . /app 12 | WORKDIR /app 13 | 14 | ENTRYPOINT [ "python" ] 15 | 16 | CMD [ "manage.py", "runserver" ] 17 | # CMD [ "manage.py", "runprod" ] -------------------------------------------------------------------------------- /backend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Timothy Ko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /backend/Makefile: -------------------------------------------------------------------------------- 1 | 2 | mac_setup: 3 | ./mac_setup 4 | 5 | setup: 6 | pip3 install virtualenv 7 | virtualenv venv 8 | venv/bin/pip install -r requirements.txt -r requirements-dev.txt 9 | 10 | run_server: 11 | venv/bin/python manage.py runserver 12 | 13 | start_dev_db: 14 | echo "creating docker postgres DB" 15 | docker run -e POSTGRES_USER=testusr -e POSTGRES_PASSWORD=password -e POSTGRES_DB=testdb -p 5432:5432 -v flask-app-db:/var/lib/postgresql/data -d postgres:10 16 | 17 | recreate_db: 18 | ./scripts/docker_destroy.sh 19 | start_dev_db 20 | sleep 2 21 | venv/bin/python manage.py recreate_db 22 | 23 | 24 | destroy: 25 | ./scripts/docker_destroy.sh 26 | 27 | up: 28 | docker-compose up 29 | 30 | compose_destroy: 31 | docker-compose stop 32 | docker-compose rm -f 33 | docker volume rm flask-app-db 34 | 35 | make compose_start: 36 | docker-compose start 37 | 38 | heroku_setup: 39 | python manage.py recreate_db 40 | 41 | format: 42 | ( \ 43 | . venv/bin/activate; \ 44 | black api/; \ 45 | black tests/; \ 46 | black manage.py; \ 47 | ) -------------------------------------------------------------------------------- /backend/api/models/Admin.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from flask_mongoengine import Document 3 | from mongoengine import * 4 | from api.models import Image 5 | 6 | 7 | class Admin(Document, Mixin): 8 | """Admin Collection.""" 9 | 10 | firebase_uid = StringField(required=True) 11 | email = StringField(required=True) 12 | name = StringField(required=True) 13 | image = EmbeddedDocumentField(Image) 14 | roomName = StringField(required=False) 15 | 16 | def __repr__(self): 17 | return f"""""" 21 | -------------------------------------------------------------------------------- /backend/api/models/Announcement.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Translations, Image 6 | 7 | 8 | class Announcement(Document, Mixin): 9 | """Model for Announcement.""" 10 | 11 | name = StringField(required=True) 12 | description = StringField(required=True) 13 | nameTranslated = DictField(required=False) 14 | descriptionTranslated = DictField(required=False) 15 | date_submitted = DateTimeField(required=True) 16 | role = StringField(required=True) 17 | filee = FileField() 18 | translations = EmbeddedDocumentField(Translations, required=False) 19 | file_name = StringField() 20 | image = EmbeddedDocumentField(Image, required=False) 21 | hub_id = StringField(required=False) 22 | hub_user = DictField(required=False) 23 | partner_id = StringField(required=False) 24 | mentor_id = ListField(StringField(), required=False) 25 | mentee_id = ListField(StringField(), required=False) 26 | 27 | def __repr__(self): 28 | return f"""""" 30 | -------------------------------------------------------------------------------- /backend/api/models/AppointmentRequest.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from mongoengine import * 4 | from flask_mongoengine import Document 5 | from api.models import Availability 6 | 7 | 8 | class AppointmentRequest(Document, Mixin): 9 | """Appointment Request Collection.""" 10 | 11 | mentor_id = ObjectIdField(required=True) 12 | mentee_id = ObjectIdField() 13 | name = StringField(required=True) 14 | mentor_name = StringField() 15 | timeslot = EmbeddedDocumentField(Availability, required=True) 16 | topic = StringField() 17 | message = StringField() 18 | status = StringField() 19 | allow_texts = BooleanField() 20 | allow_calls = BooleanField() 21 | 22 | # Legacy Fields 23 | organization = StringField() 24 | email = StringField() 25 | phone_number = StringField() 26 | languages = ListField(StringField()) 27 | age = StringField() 28 | gender = StringField() 29 | location = StringField() 30 | accepted = BooleanField() 31 | specialist_categories = ListField(StringField()) 32 | 33 | def __repr__(self): 34 | return f"""""" 50 | -------------------------------------------------------------------------------- /backend/api/models/Availability.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from mongoengine import * 4 | 5 | 6 | class Availability(EmbeddedDocument, Mixin): 7 | """Availability embedded within Mentor.""" 8 | 9 | start_time = DateTimeField(required=True) 10 | end_time = DateTimeField(required=True) 11 | 12 | def __repr__(self): 13 | return f"""""" 15 | -------------------------------------------------------------------------------- /backend/api/models/CommunityLibrary.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Translations 6 | 7 | 8 | class CommunityLibrary(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | name = StringField(required=True) 12 | description = StringField(required=True) 13 | nameTranslated = DictField(required=False) 14 | descriptionTranslated = DictField(required=False) 15 | date_submitted = DateTimeField(required=True) 16 | filee = FileField() 17 | translations = EmbeddedDocumentField(Translations, required=False) 18 | file_name = StringField() 19 | hub_id = StringField(required=True) 20 | user_id = StringField(required=True) 21 | user_name = StringField(required=True) 22 | 23 | def __repr__(self): 24 | return f"""""" 28 | -------------------------------------------------------------------------------- /backend/api/models/Countries.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class Countries(Document, Mixin): 8 | """Model for Countries Options.""" 9 | 10 | country_code = StringField(required=True) 11 | country_name = StringField(required=True) 12 | 13 | def __repr__(self): 14 | return f"""""" 16 | -------------------------------------------------------------------------------- /backend/api/models/DirectMessage.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | from api.models import Availability 6 | 7 | 8 | class DirectMessage(Document, Mixin): 9 | body = StringField(required=True) 10 | message_read = BooleanField(required=True) 11 | sender_id = ObjectIdField(required=True) 12 | recipient_id = ObjectIdField(required=True) 13 | created_at = DateTimeField(required=True) 14 | availabes_in_future = ListField(EmbeddedDocumentField(Availability), required=False) 15 | 16 | def __repr__(self): 17 | return f"" 18 | -------------------------------------------------------------------------------- /backend/api/models/Education.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from mongoengine import * 4 | 5 | 6 | class Education(EmbeddedDocument, Mixin): 7 | """Education embedded within Mentor.""" 8 | 9 | education_level = StringField(required=True) 10 | majors = ListField(StringField(), required=True) 11 | school = StringField(required=True) 12 | graduation_year = IntField(required=True) 13 | 14 | def __repr__(self): 15 | return f"""""" 18 | -------------------------------------------------------------------------------- /backend/api/models/Event.py: -------------------------------------------------------------------------------- 1 | import imp 2 | from tokenize import String 3 | from api.core import Mixin 4 | from .base import db 5 | from mongoengine import * 6 | from api.models import Image 7 | 8 | 9 | class Event(Document, Mixin): 10 | user_id = ObjectIdField(required=True) 11 | title = StringField(required=True) 12 | start_datetime = DateTimeField(required=False) 13 | end_datetime = DateTimeField(required=False) 14 | role = ListField(IntField(), required=True) 15 | description = StringField(required=False) 16 | url = StringField(required=False) 17 | titleTranslated = DictField(required=False) 18 | descriptionTranslated = DictField(required=False) 19 | image_file = EmbeddedDocumentField(Image, required=False) 20 | date_submitted = DateTimeField(required=True) 21 | hub_id = StringField(required=False) 22 | partner_ids = ListField(StringField(), required=False) 23 | 24 | def __repr__(self): 25 | return f"""""" 27 | -------------------------------------------------------------------------------- /backend/api/models/GroupMessage.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from sqlalchemy import false 3 | from .base import db 4 | from flask_mongoengine import Document 5 | from mongoengine import * 6 | from api.models import Availability 7 | 8 | 9 | class GroupMessage(Document, Mixin): 10 | title = StringField(required=False) 11 | body = StringField(required=True) 12 | message_read = BooleanField(required=True) 13 | message_edited = BooleanField(required=False) 14 | sender_id = ObjectIdField(required=True) 15 | hub_user_id = ObjectIdField(required=True) 16 | created_at = DateTimeField(required=True) 17 | parent_message_id = StringField(required=False) 18 | is_deleted = BooleanField(required=False) 19 | 20 | def __repr__(self): 21 | return f"" 22 | -------------------------------------------------------------------------------- /backend/api/models/Guest.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from flask_mongoengine import Document 3 | from mongoengine import * 4 | 5 | 6 | class Guest(Document, Mixin): 7 | """Guest Collection.""" 8 | 9 | firebase_uid = StringField(required=True) 10 | email = StringField(required=True) 11 | name = StringField(required=True) 12 | roomName = StringField(required=False) 13 | 14 | def __repr__(self): 15 | return f"""""" 19 | -------------------------------------------------------------------------------- /backend/api/models/Hub.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from flask_mongoengine import Document 3 | from mongoengine import * 4 | from api.models import Image 5 | 6 | 7 | class Hub(Document, Mixin): 8 | """Hub Collection.""" 9 | 10 | firebase_uid = StringField(required=True) 11 | email = StringField(required=True) 12 | name = StringField(required=True) 13 | image = EmbeddedDocumentField(Image) 14 | url = StringField(required=True) 15 | invite_key = StringField(required=False) 16 | preferred_language = StringField(required=False, default="en-US") 17 | roomName = StringField(required=False) 18 | mentorMentee = StringField(required=False) 19 | 20 | def __repr__(self): 21 | return f"""""" 25 | -------------------------------------------------------------------------------- /backend/api/models/Image.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from mongoengine import * 4 | 5 | 6 | class Image(EmbeddedDocument, Mixin): 7 | """Image embedded within Mentor.""" 8 | 9 | url = StringField(required=True) 10 | image_hash = StringField(required=False) # Legacy 11 | file_name = StringField(required=False) 12 | 13 | def __repr__(self): 14 | return f"""""" 15 | -------------------------------------------------------------------------------- /backend/api/models/Languages.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class Languages(Document, Mixin): 8 | """Model for Languages Options.""" 9 | 10 | name = StringField(required=True) 11 | translations = DictField() 12 | updated_at = DateTimeField(required=True) 13 | 14 | def __repr__(self): 15 | return f"""""" 17 | -------------------------------------------------------------------------------- /backend/api/models/MenteeApplication.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class MenteeApplication(Document, Mixin): 8 | """Model for Mentee application.""" 9 | 10 | email = StringField(required=True) 11 | name = StringField(required=True) 12 | age = StringField(required=False) 13 | organization = StringField() 14 | immigrant_status = ListField(StringField(), required=True) 15 | Country = StringField() 16 | identify = StringField(required=True) 17 | language = ListField(StringField()) 18 | topics = ListField(StringField(), required=True) 19 | workstate = ListField(StringField(), required=True) 20 | isSocial = StringField(required=True) 21 | questions = StringField() 22 | application_state = StringField(required=True) 23 | date_submitted = DateTimeField(required=True) 24 | notes = StringField() 25 | traingStatus = DictField(required=False) 26 | partner = StringField() 27 | 28 | def __repr__(self): 29 | return f"""Mentee Application email: {self.email} 30 | \n name: {self.name} 31 | \n age: {self.age} 32 | \n Country: {self.Country} 33 | \n topics: {self.topics} 34 | \n questions: {self.questions} 35 | \n immigrant_status: {self.immigrant_status} 36 | \n language: {self.language} 37 | \n identify: {self.identify} 38 | \n workstate: {self. workstate} 39 | \n date_submitted: {self.date_submitted} 40 | \n isSocial: {self.isSocial} 41 | \n application_state: {self.application_state}>""" 42 | -------------------------------------------------------------------------------- /backend/api/models/MenteeProfile.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | from api.models import Education, Video, Image, Users, MentorProfile 6 | 7 | 8 | class MenteeProfile(Document, Mixin): 9 | """Mentee Profile Collection.""" 10 | 11 | firebase_uid = StringField() 12 | name = StringField(required=True) 13 | gender = StringField(required=True) 14 | location = StringField() 15 | age = StringField(required=True) 16 | email = StringField(required=True) 17 | phone_number = StringField() 18 | image = EmbeddedDocumentField(Image) 19 | education = ListField(EmbeddedDocumentField(Education)) 20 | isStudent = StringField() 21 | education_level = StringField() 22 | languages = ListField(StringField(), required=True) 23 | biography = StringField() 24 | organization = StringField(required=False) 25 | text_notifications = BooleanField(required=True) 26 | email_notifications = BooleanField(required=True) 27 | is_private = BooleanField(required=True) 28 | video = EmbeddedDocumentField(Video) 29 | favorite_mentors_ids = ListField(StringField()) 30 | specializations = ListField(StringField()) 31 | pair_partner = DictField(required=False) 32 | immigrant_status = ListField(StringField(), required=False) 33 | workstate = ListField(StringField(), required=False) 34 | preferred_language = StringField(required=False, default="en-US") 35 | roomName = StringField(required=False) 36 | timezone = StringField(required=True) 37 | birthday = DateField(required=False) 38 | mentorMentee = StringField(required=False) 39 | 40 | def __repr__(self): 41 | return f"""""" 49 | -------------------------------------------------------------------------------- /backend/api/models/Message.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | from api.models import Users 6 | 7 | 8 | class Message(Document, Mixin): 9 | """User Collection.""" 10 | 11 | message = StringField(required=True) 12 | user_name = StringField(required=True) 13 | user_id = ReferenceField("Users", required=True) 14 | recipient_name = StringField(required=True) 15 | recipient_id = ReferenceField("Users", required=True) 16 | email = StringField() 17 | link = StringField() 18 | time = DateTimeField(required=True) 19 | # read = BooleanField(required=True) 20 | 21 | def __repr__(self): 22 | return f"" 23 | -------------------------------------------------------------------------------- /backend/api/models/Moderator.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from flask_mongoengine import Document 3 | from mongoengine import * 4 | 5 | 6 | class Moderator(Document, Mixin): 7 | """Moderator Collection.""" 8 | 9 | firebase_uid = StringField(required=True) 10 | email = StringField(required=True) 11 | name = StringField(required=True) 12 | roomName = StringField(required=False) 13 | 14 | def __repr__(self): 15 | return f"""""" 19 | -------------------------------------------------------------------------------- /backend/api/models/NewMentorApplication.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from sqlalchemy import false 4 | from .base import db 5 | from mongoengine import * 6 | 7 | 8 | class NewMentorApplication(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | email = StringField(required=True) 12 | name = StringField(required=True) 13 | cell_number = StringField(required=True) 14 | hear_about_us = StringField(required=True) 15 | offer_donation = StringField(required=False) 16 | employer_name = StringField(required=True) 17 | role_description = StringField(required=True) 18 | immigrant_status = BooleanField(required=True) 19 | languages = StringField(required=True) 20 | referral = StringField() 21 | companyTime = StringField() 22 | specialistTime = StringField() 23 | knowledge_location = StringField(required=True) 24 | isColorPerson = BooleanField(required=True) 25 | isMarginalized = BooleanField(required=True) 26 | isFamilyNative = BooleanField(required=True) 27 | isEconomically = BooleanField(required=True) 28 | identify = StringField() 29 | pastLiveLocation = StringField(required=True) 30 | application_state = StringField(required=True) 31 | date_submitted = DateTimeField(required=True) 32 | notes = StringField() 33 | traingStatus = DictField(required=False) 34 | specializations = ListField(StringField(), required=False) 35 | partner = StringField() 36 | 37 | def __repr__(self): 38 | return f"""""" 57 | -------------------------------------------------------------------------------- /backend/api/models/Notifications.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class Notifications(Document, Mixin): 8 | """Model for mentor application.""" 9 | 10 | message = StringField(required=True) 11 | mentorId = StringField(required=True) 12 | date_submitted = DateTimeField(required=True) 13 | readed = BooleanField(required=True) 14 | 15 | def __repr__(self): 16 | return f"""""" 20 | -------------------------------------------------------------------------------- /backend/api/models/PartnerApplication.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class PartnerApplication(Document, Mixin): 8 | """Model for mentor application.""" 9 | 10 | email = StringField(required=True) 11 | organization = StringField(required=True) 12 | contanctPerson = StringField(required=True) 13 | personEmail = StringField(required=True) 14 | relationShip = ListField(StringField(), required=True) 15 | SDGS = ListField(StringField(), required=True) 16 | howBuild = StringField(required=True) 17 | application_state = StringField(required=True) 18 | date_submitted = DateTimeField(required=True) 19 | traingStatus = DictField(required=False) 20 | 21 | def __repr__(self): 22 | return f"""""" 27 | -------------------------------------------------------------------------------- /backend/api/models/PartnerGroupMessage.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | from api.models import Availability 6 | 7 | 8 | class PartnerGroupMessage(Document, Mixin): 9 | body = StringField(required=True) 10 | message_read = BooleanField(required=True) 11 | message_edited = BooleanField(required=False) 12 | sender_id = ObjectIdField(required=True) 13 | created_at = DateTimeField(required=True) 14 | parent_message_id = StringField(required=False) 15 | 16 | def __repr__(self): 17 | return f"" 18 | -------------------------------------------------------------------------------- /backend/api/models/PartnerProfile.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Education, Video, Image 6 | 7 | 8 | class PartnerProfile(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | firebase_uid = StringField() 12 | email = StringField(required=True) 13 | text_notifications = BooleanField(required=True) 14 | email_notifications = BooleanField(required=True) 15 | organization = StringField(required=False) 16 | title = StringField(required=False) 17 | location = StringField(required=False) 18 | person_name = StringField() 19 | regions = ListField(StringField(), required=False) 20 | intro = StringField(required=True) 21 | website = StringField() 22 | linkedin = StringField() 23 | sdgs = ListField(StringField(), required=True) 24 | topics = StringField() 25 | success = StringField(required=False) 26 | open_grants = BooleanField(required=False) 27 | open_projects = BooleanField(required=False) 28 | image = EmbeddedDocumentField(Image) 29 | restricted = BooleanField(default=False) 30 | assign_mentors = ListField(DictField(), required=False) 31 | assign_mentees = ListField(DictField(), required=False) 32 | preferred_language = StringField(required=False, default="en-US") 33 | hub_id = StringField(required=False) 34 | hub_user = DictField(required=False) 35 | hub_user_name = StringField(required=False) 36 | roomName = StringField(required=False) 37 | timezone = StringField(required=True) 38 | mentorMentee = StringField(required=False) 39 | 40 | def __repr__(self): 41 | return f"""""" 44 | -------------------------------------------------------------------------------- /backend/api/models/SignOrigin.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Translations 6 | 7 | 8 | class SignOrigin(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | name = StringField(required=True) 12 | description = StringField(required=True) 13 | nameTranslated = DictField(required=False) 14 | descriptionTranslated = DictField(required=False) 15 | date_submitted = DateTimeField(required=True) 16 | role = StringField(required=False) 17 | filee = FileField() 18 | translations = EmbeddedDocumentField(Translations, required=False) 19 | file_name = StringField() 20 | hub_id = StringField(required=False) 21 | hub_user = DictField(required=False) 22 | 23 | def __repr__(self): 24 | return f"""""" 28 | -------------------------------------------------------------------------------- /backend/api/models/SignedDocs.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Translations 6 | 7 | 8 | class SignedDocs(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | doc_id = StringField(required=False) 12 | training_id = StringField(required=True) 13 | date_submitted = DateTimeField(required=True) 14 | role = StringField(required=False) 15 | user_email = StringField(required=True) 16 | filee = FileField(required=True) 17 | approved = BooleanField(required=False) 18 | hub_id = StringField(required=False) 19 | hub_user = DictField(required=False) 20 | 21 | def __repr__(self): 22 | return f"""""" 24 | -------------------------------------------------------------------------------- /backend/api/models/Specializations.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | 6 | 7 | class Specializations(Document, Mixin): 8 | """Model for Specializations.""" 9 | 10 | name = StringField(required=True) 11 | translations = DictField() 12 | updated_at = DateTimeField(required=True) 13 | 14 | def __repr__(self): 15 | return f"""""" 17 | -------------------------------------------------------------------------------- /backend/api/models/Support.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from flask_mongoengine import Document 3 | from mongoengine import * 4 | 5 | 6 | class Support(Document, Mixin): 7 | """Support Collection.""" 8 | 9 | firebase_uid = StringField(required=True) 10 | email = StringField(required=True) 11 | name = StringField(required=True) 12 | roomName = StringField(required=False) 13 | 14 | def __repr__(self): 15 | return f"""""" 19 | -------------------------------------------------------------------------------- /backend/api/models/Training.py: -------------------------------------------------------------------------------- 1 | from tokenize import String 2 | from api.core import Mixin 3 | from .base import db 4 | from mongoengine import * 5 | from api.models import Translations 6 | 7 | 8 | class Training(Document, Mixin): 9 | """Model for mentor application.""" 10 | 11 | name = StringField(required=True) 12 | url = StringField() 13 | description = StringField(required=True) 14 | nameTranslated = DictField(required=False) 15 | descriptionTranslated = DictField(required=False) 16 | date_submitted = DateTimeField(required=True) 17 | role = StringField(required=True) 18 | filee = FileField() 19 | translations = EmbeddedDocumentField(Translations, required=False) 20 | isVideo = BooleanField(required=True) 21 | requried_sign = BooleanField(required=False) 22 | typee = StringField(required=True) 23 | file_name = StringField() 24 | hub_id = StringField(required=False) 25 | hub_user = DictField(required=False) 26 | signed_data = DictField(required=False) 27 | partner_id = StringField(required=False) 28 | mentor_id = ListField(StringField(), required=False) 29 | mentee_id = ListField(StringField(), required=False) 30 | sort_order = IntField(required=False, default=0) 31 | 32 | def __repr__(self): 33 | return f"""""" 39 | -------------------------------------------------------------------------------- /backend/api/models/Translations.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from mongoengine import * 4 | 5 | 6 | # TODO: Change this to a TranslatedFile model 7 | class Translations(EmbeddedDocument, Mixin): 8 | """Model for Translated File/Document""" 9 | 10 | es_US = FileField(db_field="es-US") 11 | pt_BR = FileField(db_field="pt-BR") 12 | ar = FileField(db_field="ar") 13 | fa_AF = FileField(db_field="fa-AF") 14 | -------------------------------------------------------------------------------- /backend/api/models/Users.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | 6 | 7 | class Users(Document, Mixin): 8 | """User Collection.""" 9 | 10 | firebase_uid = StringField() 11 | email = StringField(required=True) 12 | role = StringField(required=True) 13 | mongooseVersion = IntField(db_field="__v") 14 | roomName = StringField(required=False) 15 | 16 | # legacy fields (DO NOT USE) 17 | password = StringField() 18 | pin = IntField() 19 | expiration = DateTimeField() 20 | verified = BooleanField(required=True) 21 | 22 | def __repr__(self): 23 | return f"" 24 | -------------------------------------------------------------------------------- /backend/api/models/VerifiedEmail.py: -------------------------------------------------------------------------------- 1 | from api.core import Mixin 2 | from .base import db 3 | from flask_mongoengine import Document 4 | from mongoengine import * 5 | 6 | 7 | # is_mentor field isn't scalable for other types of users 8 | # change it if needed for other types 9 | class VerifiedEmail(Document, Mixin): 10 | email = StringField(required=True) 11 | role = StringField(required=True) 12 | password = StringField() 13 | 14 | def __repr__(self): 15 | return f"" 16 | -------------------------------------------------------------------------------- /backend/api/models/__init__.py: -------------------------------------------------------------------------------- 1 | # this file structure follows http://flask.pocoo.org/docs/1.0/patterns/appfactories/ 2 | # initializing db in api.models.base instead of in api.__init__.py 3 | # to prevent circular dependencies 4 | from .base import db 5 | from .Users import Users 6 | from .Education import Education 7 | from .Video import Video 8 | from .Availability import Availability 9 | from .Image import Image 10 | from .Translations import Translations 11 | from .MentorProfile import MentorProfile 12 | from .MenteeApplication import MenteeApplication 13 | from .PartnerApplication import PartnerApplication 14 | from .AppointmentRequest import AppointmentRequest 15 | from .NewMentorApplication import NewMentorApplication 16 | from .VerifiedEmail import VerifiedEmail 17 | from .MenteeProfile import MenteeProfile 18 | from .Message import Message 19 | from .DirectMessage import DirectMessage 20 | from .GroupMessage import GroupMessage 21 | from .PartnerGroupMessage import PartnerGroupMessage 22 | from .Admin import Admin 23 | from .Training import Training 24 | from .Event import Event 25 | from .Hub import Hub 26 | from .PartnerProfile import PartnerProfile 27 | from .MentorApplication import MentorApplication 28 | from .Notifications import Notifications 29 | from .Languages import Languages 30 | from .Countries import Countries 31 | from .Specializations import Specializations 32 | from .Guest import Guest 33 | from .Support import Support 34 | from .Moderator import Moderator 35 | from .SignOrigin import SignOrigin 36 | from .SignedDocs import SignedDocs 37 | from .Announcement import Announcement 38 | from .CommunityLibrary import CommunityLibrary 39 | 40 | __all__ = [ 41 | "db", 42 | "Users", 43 | "Education", 44 | "Video", 45 | "MentorProfile", 46 | "Availability", 47 | "AppointmentRequest", 48 | "Translations", 49 | "Image", 50 | "VerifiedEmail", 51 | "NewMentorApplication", 52 | "MenteeProfile", 53 | "Message", 54 | "DirectMessage", 55 | "Admin", 56 | "MenteeApplication", 57 | "PartnerApplication", 58 | "Training", 59 | "Event", 60 | "Hub", 61 | "PartnerProfile", 62 | "MentorApplication", 63 | "Notifications", 64 | "Languages", 65 | "Countries", 66 | "Specializations", 67 | "Guest", 68 | "Support", 69 | "Moderator", 70 | "SignOrigin", 71 | "SignedDocs", 72 | "GroupMessage", 73 | "PartnerGroupMessage", 74 | "Announcement", 75 | "CommunityLibrary", 76 | ] 77 | 78 | # You must import all of the new Models you create to this page 79 | -------------------------------------------------------------------------------- /backend/api/models/base.py: -------------------------------------------------------------------------------- 1 | from flask_mongoengine import MongoEngine 2 | 3 | # instantiate database object 4 | db = MongoEngine() 5 | -------------------------------------------------------------------------------- /backend/api/models/model_class_possibilities/Model_id_recursion.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Type 2 | 3 | 4 | class ModelSQL(object): 5 | query_class = None # flask_alchemy attribute 6 | query = None # flask_alchemy attribute 7 | 8 | DONOTSEND_MODEL = {"_sa_instance_state"} 9 | DONOTSEND = [] 10 | 11 | def __repr__(self) -> str: 12 | return "<{}>".format(self.__class__.__name__) 13 | 14 | def to_dict_recursive(self) -> dict: 15 | return self._to_dict_recursive(obj_ids_crossed=[id(self)]) 16 | 17 | def _to_dict_recursive(self, obj_ids_crossed: List[int]) -> dict: 18 | """iterate through objects to create a dict 19 | 20 | Keywords arguments : 21 | obj_ids_crossed -- List of objects' id already passed through, provides against circular recursion 22 | 23 | Inside functions : 24 | check_crossed_obj -- Check if object has already been passed through 25 | type_shunt_recursive -- Select actions for each type of attr 26 | """ 27 | 28 | def check_crossed_obj(obj: Type[ModelSQL]) -> any: 29 | if id(obj) in obj_ids_crossed: 30 | return str(obj) 31 | # others possibilities 32 | # return str(obj).join(' ').join(str(obj.id)) 33 | # return obj.id 34 | else: 35 | obj_ids_crossed.append(id(obj)) 36 | return obj._to_dict_recursive(obj_ids_crossed) 37 | 38 | def type_shunt_recursive(attribute: Any) -> Any: 39 | # model 40 | if issubclass(type(attribute), ModelSQL): 41 | return check_crossed_obj(attribute) 42 | # recursive iteration of the list in case of the list is a relationship 43 | elif isinstance(attribute, list): 44 | values = [] 45 | for item in attribute: 46 | values.append(type_shunt_recursive(item)) 47 | return values 48 | # attribute is not an instance of relationship (int, str..) 49 | else: 50 | return attribute 51 | 52 | result = {} 53 | # __mapper__ is equivalent to db.inspect(self) 54 | # but db (database) is not created yet cause we send this model to the constructor 55 | for key in self.__mapper__.attrs.keys(): 56 | if key not in self.DONOTSEND: 57 | attr = getattr(self, key) 58 | result[key] = type_shunt_recursive(attr) 59 | 60 | 61 | return result 62 | -------------------------------------------------------------------------------- /backend/api/utils/firebase.py: -------------------------------------------------------------------------------- 1 | import pyrebase 2 | import os 3 | 4 | client = pyrebase.initialize_app( 5 | { 6 | "apiKey": os.environ.get("FIREBASE_API_KEY"), 7 | "authDomain": "mentee-d0304.firebaseapp.com", 8 | "databaseURL": "", 9 | "storageBucket": "mentee-d0304.appspot.com", 10 | "serviceAccount": os.environ.get("GOOGLE_APPLICATION_CREDENTIALS"), 11 | } 12 | ) 13 | -------------------------------------------------------------------------------- /backend/api/utils/google_storage.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from google.cloud import storage 3 | from api.core import logger 4 | from io import BytesIO 5 | 6 | client = storage.Client() 7 | BUCKET = "app-mentee-global-images" 8 | 9 | 10 | def upload_image_to_storage(image, filename): 11 | """Upload image to Google Cloud Storage""" 12 | bucket = client.get_bucket(BUCKET) 13 | blob = bucket.blob(filename) 14 | blob.upload_from_string(image.read(), content_type="application/jpg") 15 | return blob.public_url 16 | 17 | 18 | def delete_image_from_storage(filename): 19 | """Delete image from Google Cloud Storage""" 20 | bucket = client.get_bucket(BUCKET) 21 | blob = bucket.blob(filename) 22 | blob.delete() 23 | return True 24 | 25 | 26 | def get_image_from_storage(filename): 27 | """Get image from Google Cloud Storage and use it to create a signed URL""" 28 | bucket = client.get_bucket(BUCKET) 29 | blob = bucket.blob(filename) 30 | url = blob.generate_signed_url( 31 | version="v4", 32 | # This URL is valid for 15 minutes 33 | expiration=datetime.timedelta(minutes=15), 34 | # Allow GET requests using this URL. 35 | method="GET", 36 | ) 37 | return url 38 | 39 | 40 | def compress_image(image): 41 | """Compress image to reduce size""" 42 | image = Image.open(image) 43 | image = image.convert("RGB") 44 | image.thumbnail((500, 500)) 45 | image_io = BytesIO() 46 | image.save(image_io, "JPEG", quality=70) 47 | image_io.seek(0) 48 | return image_io 49 | -------------------------------------------------------------------------------- /backend/api/views/admin_notifications.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | from api.core import create_response 3 | from api.models import Notifications 4 | from datetime import datetime 5 | from api.utils.require_auth import admin_only 6 | from datetime import datetime 7 | 8 | admin_notifications = Blueprint("admin_notifications", __name__) # initialize blueprint 9 | 10 | 11 | @admin_notifications.route("/", methods=["GET"]) 12 | @admin_only 13 | def get_notifys(): 14 | notifys = Notifications.objects.order_by("-date_submitted") 15 | 16 | return create_response(data={"notifys": notifys}) 17 | 18 | 19 | ############################################################################# 20 | @admin_notifications.route("/", methods=["GET"]) 21 | @admin_only 22 | def read_notify(id): 23 | try: 24 | notify = Notifications.objects.get(id=id) 25 | notify.readed = True 26 | notify.save() 27 | except: 28 | return create_response(status=422, message="notifys not found") 29 | 30 | return create_response(data={"notify": notify}) 31 | 32 | 33 | ################################################################################ 34 | @admin_notifications.route("/newNotify", methods=["POST"]) 35 | @admin_only 36 | def new_notify(): 37 | try: 38 | message = request.form["message"] 39 | mentorId = request.form["mentorId"] 40 | readed = request.form["readed"] 41 | if readed == "false": 42 | readed = False 43 | else: 44 | readed = True 45 | notify = Notifications( 46 | message=message, 47 | mentorId=mentorId, 48 | readed=readed, 49 | date_submitted=datetime.now(), 50 | ) 51 | notify.save() 52 | except: 53 | return create_response(status=401, message="missing parameters") 54 | 55 | return create_response(status=200, data={"notify": notify}) 56 | -------------------------------------------------------------------------------- /backend/api/views/app_blueprint.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, send_from_directory 2 | 3 | app_blueprint = Blueprint("app_blueprint", __name__) # initialize blueprint 4 | 5 | 6 | @app_blueprint.route("/") 7 | def serve(): 8 | return send_from_directory(current_app.static_folder, "index.html") 9 | -------------------------------------------------------------------------------- /backend/api/views/availability.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request, jsonify 2 | from api.models import Availability, MentorProfile 3 | from api.core import create_response, logger 4 | from api.utils.require_auth import all_users 5 | 6 | availability = Blueprint("availability", __name__) 7 | 8 | 9 | # Get request for avalability for a specific mentor 10 | @availability.route("/", methods=["GET"]) 11 | @all_users 12 | def get_availability(id): 13 | try: 14 | availability = MentorProfile.objects.get(id=id).availability 15 | except: 16 | msg = "No mentor found with that id" 17 | logger.info(msg) 18 | return create_response(status=422, message=msg) 19 | 20 | return create_response(data={"availability": availability}) 21 | 22 | 23 | # Put request to edit availability for a specific mentor 24 | @availability.route("/", methods=["PUT"]) 25 | @all_users 26 | def edit_availability(id): 27 | data = request.get_json().get("Availability") 28 | try: 29 | mentor = MentorProfile.objects.get(id=id) 30 | except: 31 | msg = "No mentor found with that id" 32 | logger.info(msg) 33 | return create_response(status=422, message=msg) 34 | 35 | mentor.availability = [ 36 | Availability( 37 | start_time=availability.get("start_time").get("$date"), 38 | end_time=availability.get("end_time").get("$date"), 39 | ) 40 | for availability in data 41 | ] 42 | mentor.save() 43 | return create_response(status=200, message=f"Success") 44 | -------------------------------------------------------------------------------- /backend/api/views/meeting.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import os.path, json 4 | from datetime import datetime, timedelta 5 | from flask import Blueprint 6 | from api.core import create_response 7 | import sys, os 8 | import base64 9 | 10 | from api.utils.jaas_jwt_builder import JaaSJwtBuilder 11 | 12 | 13 | meeting = Blueprint("meeting", __name__) 14 | 15 | API_KEY = os.environ.get("EIGHT_X_EIGHT_API_KEY") 16 | APP_ID = os.environ.get("EIGHT_X_EIGHT_APP_ID") 17 | ENCODED_PRIVATE_KEY = os.environ.get("EIGHT_X_EIGHT_ENCODED_PRIVATE_KEY") 18 | 19 | 20 | @meeting.route("/generateToken", methods=["GET"]) 21 | def generateToken(): 22 | try: 23 | print(ENCODED_PRIVATE_KEY) 24 | PRIVATE_KEY = base64.b64decode(ENCODED_PRIVATE_KEY) 25 | print(PRIVATE_KEY) 26 | jaasJwt = JaaSJwtBuilder() 27 | token = ( 28 | jaasJwt.withDefaults() 29 | .withApiKey(API_KEY) 30 | .withUserName("User Name") 31 | .withUserEmail("email_address@email.com") 32 | .withModerator(False) 33 | .withAppID(APP_ID) 34 | .withUserAvatar("https://asda.com/avatar") 35 | .signWith(PRIVATE_KEY) 36 | ) 37 | 38 | return create_response(data={"token": token.decode("utf-8"), "appID": APP_ID}) 39 | 40 | except Exception as error: 41 | print(error) 42 | return create_response( 43 | status=422, message=f"Failed to generate token for meeting" 44 | ) 45 | -------------------------------------------------------------------------------- /backend/api/views/mentee.py: -------------------------------------------------------------------------------- 1 | from api.core import create_response, logger 2 | from api.models import MentorProfile, MenteeProfile 3 | from flask import Blueprint, request 4 | from api.utils.constants import Account 5 | from api.utils.require_auth import mentee_only 6 | 7 | mentee = Blueprint("mentee", __name__) 8 | 9 | 10 | @mentee.route("/editFavMentor", methods=["PUT"]) 11 | @mentee_only 12 | def edit_fav_mentor(): 13 | try: 14 | data = request.get_json() 15 | logger.info(data) 16 | mentor_id = data["mentor_id"] 17 | mentee_id = data["mentee_id"] 18 | favorite = bool(data["favorite"]) 19 | except: 20 | msg = "invalid parameters provided" 21 | logger.info(msg) 22 | return create_response(status=422, message=msg) 23 | try: 24 | mentee = MenteeProfile.objects.get(id=mentee_id) 25 | if not favorite and mentor_id in mentee.favorite_mentors_ids: 26 | mentee.favorite_mentors_ids.remove(mentor_id) 27 | msg = ( 28 | f"Deleted mentor: {mentor_id} from mentee: {mentee.name} favorite list" 29 | ) 30 | elif favorite and mentor_id not in mentee.favorite_mentors_ids: 31 | mentee.favorite_mentors_ids.append(mentor_id) 32 | msg = f"Added mentor: {mentor_id} to mentee: {mentee.name} favorite list" 33 | else: 34 | msg = "Request already processed" 35 | mentee.save() 36 | except: 37 | msg = "Failed to saved mentor as favorite" 38 | logger.info(msg) 39 | return create_response(status=422, message=msg) 40 | return create_response(status=200, message=msg) 41 | 42 | 43 | @mentee.route("/favorites/", methods=["GET"]) 44 | @mentee_only 45 | def get_favorites(id): 46 | try: 47 | mentee = MenteeProfile.objects.get(id=id) 48 | except: 49 | msg = f"Failed to fetch mentee by id: {id}" 50 | return create_response(status=422, message=msg) 51 | 52 | favorites = mentee.favorite_mentors_ids 53 | mentor_list = list() 54 | for mentor_id in favorites: 55 | try: 56 | mentor = MentorProfile.objects.only( 57 | "name", "professional_title", "id", "image" 58 | ).get(id=mentor_id) 59 | except: 60 | msg = f"failed to fetch mentor by id: {mentor_id}" 61 | continue 62 | mentor_list.append(mentor) 63 | 64 | return create_response( 65 | status=200, data={"favorites": mentor_list}, message="success" 66 | ) 67 | -------------------------------------------------------------------------------- /backend/api/views/translation.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request 2 | from api.models import ( 3 | MentorProfile, 4 | MenteeProfile, 5 | PartnerProfile, 6 | ) 7 | from api.core import create_response, logger 8 | from api.utils.constants import Account 9 | 10 | translation = Blueprint("translation", __name__) # initialize blueprint 11 | 12 | 13 | @translation.route("/", methods=["GET"]) 14 | def setup_language(): 15 | all_users_types = [MentorProfile, MenteeProfile, PartnerProfile] 16 | for user_type in all_users_types: 17 | users = user_type.objects() 18 | for user in users: 19 | if user.preferred_language == None: 20 | user.preferred_language = "en-US" 21 | user.save() 22 | 23 | return create_response(message="Language setup complete") 24 | -------------------------------------------------------------------------------- /backend/api/views/verify.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, request, jsonify 2 | from api.models import ( 3 | VerifiedEmail, 4 | ) 5 | from api.core import create_response 6 | from api.utils.require_auth import admin_only 7 | 8 | verify = Blueprint("verify", __name__) # initialize blueprint 9 | 10 | 11 | @verify.route("/verifyEmail", methods=["GET"]) 12 | @admin_only 13 | def verify_email(): 14 | email = request.args.get("email", default="") 15 | 16 | try: 17 | account = VerifiedEmail.objects.get(email=email) 18 | except: 19 | return create_response( 20 | data={"is_verified": False}, 21 | status=401, 22 | message="Could not find email in database", 23 | ) 24 | 25 | return create_response( 26 | data={"is_verified": True, "role": account.role}, 27 | status=200, 28 | message="Successfully returned verification", 29 | ) 30 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | # from flask_script import Manager 2 | # from flask_migrate import Migrate, MigrateCommand 3 | import click 4 | from api import create_app, socketio 5 | from flask import request 6 | 7 | 8 | # sets up the app 9 | app = create_app() 10 | 11 | 12 | @app.after_request 13 | def log_request(response): 14 | if response.status_code >= 400: 15 | app.logger.debug( 16 | "%s %s %s %s", 17 | request.method, 18 | request.path, 19 | response.status_code, 20 | response.get_data(), 21 | ) 22 | else: 23 | app.logger.debug("%s %s %s", request.method, request.path, response.status) 24 | 25 | return response 26 | 27 | 28 | @click.group() 29 | def manager(): 30 | """Management script""" 31 | 32 | 33 | @manager.command() 34 | def runserver(): 35 | socketio.run(app, debug=True, host="0.0.0.0", port=8000) 36 | 37 | 38 | if __name__ == "__main__": 39 | manager() 40 | -------------------------------------------------------------------------------- /backend/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pylint>=2.1.1 2 | black==23.1.0 3 | mypy 4 | pytest>=3.7.1 -------------------------------------------------------------------------------- /backend/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.9.7 2 | -------------------------------------------------------------------------------- /backend/scripts.py: -------------------------------------------------------------------------------- 1 | from subprocess import check_call 2 | 3 | 4 | def runserver() -> None: 5 | check_call(["python", "manage.py", "runserver"]) 6 | 7 | 8 | def format(): 9 | check_call(["black", "api/"]) 10 | check_call(["black", "tests/"]) 11 | check_call(["black", "manage.py"]) 12 | check_call(["black", "scripts.py"]) 13 | check_call(["black", "scripts/"]) 14 | -------------------------------------------------------------------------------- /backend/scripts/emails.py: -------------------------------------------------------------------------------- 1 | from apscheduler.schedulers.blocking import BlockingScheduler 2 | import requests 3 | 4 | sched = BlockingScheduler() 5 | 6 | 7 | @sched.scheduled_job("cron", day_of_week="mon", hour=12) 8 | def scheduled_job(): 9 | # python requests 10 | # r = requests.get("localhost:5000/api/notifications/weeklyemails") 11 | r = requests.get("https://app.menteeglobal.org/api/notifications/weeklyemails") 12 | print(r.status_code) 13 | 14 | 15 | sched.start() 16 | -------------------------------------------------------------------------------- /backend/tests/.env.example: -------------------------------------------------------------------------------- 1 | BASE_URL= 2 | 3 | TEST_MENTOR_EMAIL= 4 | TEST_MENTOR_PASSWORD= 5 | TEST_MENTOR_ROLE= 6 | TEST_MENTOR_PROFILE_ID= 7 | TEST_MENTOR_PHONE_NUMBER= 8 | TEST_MENTOR_NAME = "" 9 | 10 | TEST_MENTEE_EMAIL= 11 | TEST_MENTEE_PASSWORD= 12 | TEST_MENTEE_ROLE= 13 | TEST_MENTEE_PROFILE_ID= 14 | TEST_MENTEE_PHONE_NUMBER= 15 | TEST_MENTEE_NAME = "" 16 | 17 | TEST_PARTNER_EMAIL= 18 | TEST_PARTNER_PASSWORD= 19 | TEST_PARTNER_ROLE= 20 | TEST_PARTNER_PROFILE_ID= 21 | TEST_PARTNER_PHONE_NUMBER= 22 | TEST_PARTNER_NAME = "" 23 | 24 | TEST_GUEST_EMAIL= 25 | TEST_GUEST_PASSWORD= 26 | TEST_GUEST_ROLE= 27 | TEST_GUEST_PROFILE_ID= 28 | -------------------------------------------------------------------------------- /backend/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # much of this is taken from http://flask.pocoo.org/docs/1.0/tutorial/tests/ 2 | -------------------------------------------------------------------------------- /backend/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # conftest.py is used by pytest to share fixtures 2 | # https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions 3 | import os 4 | import tempfile 5 | import time 6 | from unittest import mock 7 | import pytest 8 | from flask_migrate import Migrate 9 | 10 | from api import create_app 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | def client(): 15 | app = create_app() 16 | app.app_context().push() 17 | 18 | time.sleep(2) 19 | from api.models import db 20 | 21 | client = app.test_client() 22 | yield client 23 | -------------------------------------------------------------------------------- /backend/tests/test_basic.py: -------------------------------------------------------------------------------- 1 | from api.models import db 2 | import requests 3 | from dotenv import load_dotenv 4 | import os 5 | 6 | load_dotenv() 7 | 8 | 9 | # client passed from client - look into pytest for more info about fixtures 10 | # test client api: http://flask.pocoo.org/docs/1.0/api/#test-client 11 | def test_index(client): 12 | rs = client.get("/api/translation/") 13 | assert ( 14 | rs.status_code == 200 15 | ), f"Basic Test Failed. Server not running properly. {rs.text}" 16 | -------------------------------------------------------------------------------- /backend/tests/test_explore.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from api.models import MenteeProfile, MentorProfile, PartnerProfile 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | 9 | def test_find_mentee(client): 10 | # list all the mentees from the api 11 | response = client.get(f"/api/accounts/2") 12 | data = response.get_json() 13 | 14 | assert response.status_code == 200 15 | 16 | assert "result" in data 17 | result = data["result"] 18 | assert "accounts" in result 19 | 20 | accounts = result["accounts"] 21 | 22 | # There is no need to write assertion on db level data because its change frequently (Database assertion) 23 | # get the public mentee instances in the database 24 | mentee_users = MenteeProfile.objects.filter(is_private=False).count() 25 | 26 | assert len(accounts) > 0 27 | assert ( 28 | len(accounts) == mentee_users 29 | ), "Mentee accounts retrieved from the api does not match the Mentee accounts in the database." 30 | 31 | 32 | def test_find_mentor(client): 33 | # list all the mentors from the api 34 | response = client.get(f"/api/accounts/1") 35 | data = response.get_json() 36 | 37 | assert response.status_code == 200 38 | 39 | assert "result" in data 40 | result = data["result"] 41 | assert "accounts" in result 42 | 43 | accounts = result["accounts"] 44 | # get the mentor instances in the database 45 | mentor_users = MentorProfile.objects.count() 46 | assert len(accounts) > 0 47 | assert ( 48 | len(accounts) == mentor_users 49 | ), "Mentor accounts retrieved from the api does not match the Mentor accounts in the database." 50 | 51 | 52 | def test_find_partner(client): 53 | # list all the partners from the api 54 | response = client.get(f"/api/accounts/3") 55 | data = response.get_json() 56 | 57 | assert response.status_code == 200 58 | 59 | assert "result" in data 60 | result = data["result"] 61 | assert "accounts" in result 62 | 63 | accounts = result["accounts"] 64 | -------------------------------------------------------------------------------- /backend/tests/test_private.py: -------------------------------------------------------------------------------- 1 | import requests, pytest 2 | from api.models import MenteeProfile 3 | import os 4 | from dotenv import load_dotenv 5 | from api.views.main import is_mentee_account_private 6 | 7 | load_dotenv() 8 | 9 | 10 | # the private mentee profiles should not be in the database 11 | def test_mentee_privacy(client): 12 | # create a list of public mentee ids and store ids of all the mentees returned from the api response 13 | public_mentee_ids = [] 14 | 15 | # list all the mentees from the api 16 | response = client.get(f"/api/accounts/2") 17 | data = response.get_json() 18 | 19 | assert response.status_code == 200 20 | 21 | assert "result" in data 22 | result = data["result"] 23 | assert "accounts" in result 24 | 25 | accounts = result["accounts"] 26 | 27 | # find the instances from the database which should be public 28 | private_mentee_users = MenteeProfile.objects.filter(is_private=True) 29 | all_mentee_users = MenteeProfile.objects 30 | 31 | private_mentee_count = private_mentee_users.count() 32 | all_mentee_count = all_mentee_users.count() 33 | 34 | search_mentee_users = all_mentee_count - private_mentee_count 35 | 36 | assert len(accounts) > 0 37 | 38 | # check if the number of public mentees returned from the api is equal to the number of public mentees in the database 39 | assert len(accounts) == search_mentee_users 40 | 41 | for account in accounts: 42 | assert account["is_private"] == False 43 | public_mentee_ids.append(account["_id"]["$oid"]) 44 | 45 | # make sure that the private mentees are not in the list of public mentees 46 | for private_mentee in private_mentee_users: 47 | assert ( 48 | str(private_mentee.id) not in public_mentee_ids 49 | ), "Private mentee account found in the api response" 50 | -------------------------------------------------------------------------------- /backend/tests/test_verify_user.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from api.utils.require_auth import verify_user, AUTHORIZED, UNAUTHORIZED, ALL_USERS 3 | 4 | 5 | class MockRequest: 6 | headers = {"Authorization": "mock_token"} 7 | 8 | 9 | class MockFirebaseAdminAuth: 10 | @staticmethod 11 | def verify_id_token(token): 12 | return {"role": 1} 13 | 14 | 15 | def test_verify_user_authorized(): 16 | with pytest.raises(Exception): 17 | assert verify_user( 18 | 1, request=MockRequest, firebase_admin_auth=MockFirebaseAdminAuth 19 | ) == (AUTHORIZED, None) 20 | 21 | 22 | def test_verify_user_unauthorized(): 23 | with pytest.raises(Exception): 24 | assert verify_user( 25 | 2, request=MockRequest, firebase_admin_auth=MockFirebaseAdminAuth 26 | ) == ( 27 | UNAUTHORIZED, 28 | "Expected response object", 29 | ) 30 | 31 | 32 | def test_verify_user_all_users(): 33 | with pytest.raises(Exception): 34 | assert verify_user( 35 | ALL_USERS, request=MockRequest, firebase_admin_auth=MockFirebaseAdminAuth 36 | ) == ( 37 | AUTHORIZED, 38 | None, 39 | ) 40 | -------------------------------------------------------------------------------- /backend/tests/utils/consts.json: -------------------------------------------------------------------------------- 1 | { 2 | "TEST_MENTOR_ROLE": 1, 3 | "TEST_MENTEE_ROLE": 2, 4 | "TEST_PARTNER_ROLE": 3, 5 | "TEST_GUEST_ROLE": 4, 6 | "TEST_ADMIN_ROLE": 0, 7 | "TEST_MENTOR_PROFILE_ID": "65d3b31c769a310baa7c2788", 8 | "TEST_MENTEE_PROFILE_ID": "63480d1684bc94d912f12e54", 9 | "TEST_PARTNER_PROFILE_ID": "64987c198080f5a69e929b04", 10 | "TEST_GUEST_PROFILE_ID": "64bd2e6b56f2fc2458142dcd", 11 | "TEST_ADMIN_NAME": "candle", 12 | "TEST_RECIPIENT_ID": "640f8a6114b76ff16f517e2f", 13 | "TEST_RECIPIENT_NAME": "RobertoMentee3", 14 | "TEST_MENTOR_NAME": "Roberto Murer Mentor1", 15 | "TEST_MENTEE_NAME": "Robert Mente", 16 | "TEST_ADMIN_PROFILE_ID": "60765e9289899aeee51a8b27" 17 | } 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | frontend: 5 | container_name: mentee-frontend 6 | build: ./frontend 7 | ports: 8 | - '3000:3000' 9 | environment: 10 | - NODE_ENV=development 11 | 12 | backend: 13 | container_name: mentee-backend 14 | build: ./backend 15 | ports: 16 | - '8000:8000' 17 | env_file: 18 | - ./backend/.env -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mentee/fd075229623526ada095591bd7337ba223c405e6/docs/.nojekyll -------------------------------------------------------------------------------- /docs/AnotherReadMe.md: -------------------------------------------------------------------------------- 1 | ## How to Setup Front End 2 | 3 | - cd frontend 4 | - npm install 5 | - npm start 6 | 7 | if you have errors with npm install then try different version of node it works for me in version (v14.20.0) 8 | 9 | ## Set up Mongodb 10 | 11 | - you need to setup mongodb in you Operating system 12 | - if you are windows this an example https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows 13 | 14 | ## Set up backend 15 | 16 | - cd backend 17 | - pip install -r requirements.txt (you need to setup pip if it is not exist first) 18 | - if you face any error try go through requirements comment line contaion this library you got error from then run pip install command again 19 | - after successfull installation run ( python manage.py runserver ) 20 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![logo](media/mentee.png) 4 | 5 | > Documentation 6 | 7 | [GitHub](https://github.com/hack4impact-uiuc/mentee) 8 | [Get Started](README) -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - General 4 | 5 | - [Home](README) 6 | 7 | - Frontend 8 | 9 | - [Getting Started](frontend/setup) 10 | 11 | - Backend 12 | 13 | - [Getting Started](backend/setup) 14 | - [File Structure](backend/file_structure.md) -------------------------------------------------------------------------------- /docs/backend/file_structure.md: -------------------------------------------------------------------------------- 1 | # Repository Contents 2 | 3 | - `api/views/` - Holds files that define your endpoints 4 | - `api/models/` - Holds files that defines your database schema 5 | - `api/__init__.py` - What is initially ran when you start your application 6 | - `api/utils.py` - utility functions and classes - explained [here](https://github.com/tko22/flask-boilerplate/wiki/Conventions) 7 | - `api/core.py` - includes core functionality including error handlers and logger 8 | - `tests/` - Folder holding tests 9 | 10 | #### Others 11 | 12 | - `config.py` - Provides Configuration for the application. There are two configurations: one for development and one for production using Heroku. 13 | - `manage.py` - Command line interface that allows you to perform common functions with a command 14 | - `requirements.txt` - A list of python package dependencies the application requires 15 | - `runtime.txt` & `Procfile` - configuration for Heroku -------------------------------------------------------------------------------- /docs/backend/setup.md: -------------------------------------------------------------------------------- 1 | # Getting Started on Backend 2 | 3 | ## Installing and Running 4 | Make sure you have [Python3](https://realpython.com/installing-python/) and [Poetry](https://python-poetry.org/) installed. 5 | 6 | Install packages: 7 | 8 | ``` 9 | $ poetry install 10 | ``` 11 | To run the server: 12 | ``` 13 | $ poetry run start 14 | ``` 15 | 16 | ## Dependencies 17 | 18 | Documentation for all the libraries we are using 19 | 20 | - [Flask](https://flask.palletsprojects.com/en/1.1.x/) 21 | - [Mongoengine](http://mongoengine.org/) 22 | - [Twilio](twilio.com/docs/libraries/python) 23 | - [SendGrid](https://sendgrid.com/docs/for-developers/) 24 | - [WTForms](https://wtforms.readthedocs.io/en/2.3.x/) 25 | - [WTForms-JSON](https://wtforms-json.readthedocs.io/en/latest/) -------------------------------------------------------------------------------- /docs/frontend/setup.md: -------------------------------------------------------------------------------- 1 | # Getting Started on Frontend 2 | 3 | ## Setup 4 | ### Installing 5 | Install [yarn](https://yarnpkg.com/) if you don't already have it and install react libraries 6 | ``` 7 | $ npm install --global yarn 8 | ``` 9 | NOTE: You'll need to do this every time there is a new library that has been added by another member 10 | ``` 11 | $ yarn 12 | ``` 13 | 14 | ### Running 15 | 16 | ``` 17 | $ yarn start 18 | ``` 19 | 20 | ## Dependencies 21 | The following are libraries we are using in our project. If you add any new ones add them here too! 22 | 23 | They will also be linked to their respective documentation 24 | 25 | - [Ant Design](https://ant-design.gitee.io/components/overview/) 26 | - [Moment.JS](https://momentjs.com/docs/) 27 | - [React Router](https://reactrouter.com/web/guides/quick-start) 28 | - [Axios](https://www.npmjs.com/package/axios) 29 | - [Material UI](https://material-ui.com/getting-started/installation/) -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MENTEE Documentation 6 | 7 | 8 | 12 | 16 | 17 | 18 |
Please wait...
19 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/media/mentee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mentee/fd075229623526ada095591bd7337ba223c405e6/docs/media/mentee.png -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .env 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | .vscode 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | yarn.lock -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1 - the build process 2 | FROM node:16 as build-deps 3 | WORKDIR /usr/src/app 4 | COPY package.json /usr/src/app 5 | RUN yarn 6 | COPY . ./ 7 | ENTRYPOINT [ "yarn", "start" ] 8 | # RUN npm run build 9 | 10 | # Stage 2 - the production environment 11 | # FROM nginx:1.12 12 | # COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html 13 | # EXPOSE 80 14 | # CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@ant-design/icons": "^4.8.0", 7 | "@dnd-kit/core": "^6.3.1", 8 | "@dnd-kit/modifiers": "^9.0.0", 9 | "@dnd-kit/sortable": "^10.0.0", 10 | "@emotion/css": "^11.11.2", 11 | "@jitsi/react-sdk": "^1.4.0", 12 | "@reduxjs/toolkit": "^1.9.3", 13 | "@testing-library/jest-dom": "^5.16.5", 14 | "@testing-library/react": "^14.0.0", 15 | "@testing-library/user-event": "^14.4.3", 16 | "antd": "5.x", 17 | "antd-img-crop": "^4.8.0", 18 | "axios": "^1.3.4", 19 | "compress-create-react-app": "^1.4.2", 20 | "dayjs": "^1.11.9", 21 | "emoji-picker-react": "^4.3.0", 22 | "firebase": "^8.2.9", 23 | "i18next": "^22.5.0", 24 | "i18next-browser-languagedetector": "^7.0.2", 25 | "i18next-http-backend": "^2.2.1", 26 | "moment": "^2.29.4", 27 | "moment-timezone": "^0.5.43", 28 | "pdf-lib": "^1.17.1", 29 | "prettier": "^2.8.4", 30 | "pretty-quick": "^3.1.3", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "react-draggable": "^4.4.6", 34 | "react-i18next": "^12.3.1", 35 | "react-json-to-table": "^0.1.7", 36 | "react-player": "^2.11.2", 37 | "react-redux": "^8.1.1", 38 | "react-resizable": "^3.0.5", 39 | "react-responsive": "^9.0.2", 40 | "react-router": "^5.2.0", 41 | "react-router-dom": "^5.2.0", 42 | "react-scripts": "5.0.1", 43 | "react-signature-canvas": "^1.0.6", 44 | "sass": "^1.50.0", 45 | "socket.io-client": "^4.6.1" 46 | }, 47 | "devDependencies": { 48 | "@babel/plugin-proposal-private-property-in-object": "^7.14.5", 49 | "dotenv": "16.3.1" 50 | }, 51 | "scripts": { 52 | "start": "react-scripts start", 53 | "build": "react-scripts build && compress-cra", 54 | "test": "react-scripts test", 55 | "eject": "react-scripts eject", 56 | "format": "pretty-quick --staged {src,test}/**/*.{js,jsx,json,scss}", 57 | "format:all": "prettier --write {src,test}/**/*.{js,jsx,json,scss}", 58 | "format:check": "prettier --list-different {src,test}/**/*.js" 59 | }, 60 | "eslintConfig": { 61 | "extends": "react-app" 62 | }, 63 | "browserslist": { 64 | "production": [ 65 | ">0.2%", 66 | "not dead", 67 | "not op_mini all" 68 | ], 69 | "development": [ 70 | "last 1 chrome version", 71 | "last 1 firefox version", 72 | "last 1 safari version" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mentee/fd075229623526ada095591bd7337ba223c405e6/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 21 | 30 | MENTEE 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "MENTEE", 3 | "name": "MENTEE open access platform", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/mentee-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hack4impact-uiuc/mentee/fd075229623526ada095591bd7337ba223c405e6/frontend/public/mentee-logo.ico -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import userReducer from "features/userSlice"; 3 | import notificationsReducer from "features/notificationsSlice"; 4 | import messagesReducer from "features/messagesSlice"; 5 | import optionsReducer from "features/optionsSlice"; 6 | import meetingPanelReducer from "features/meetingPanelSlice"; 7 | 8 | export default configureStore({ 9 | reducer: { 10 | user: userReducer, 11 | notifications: notificationsReducer, 12 | messages: messagesReducer, 13 | options: optionsReducer, 14 | meetingPanel: meetingPanelReducer, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/components/AdminDownloadDropdown.js: -------------------------------------------------------------------------------- 1 | import { DownOutlined } from "@ant-design/icons"; 2 | import { Button, Dropdown, Menu } from "antd"; 3 | import React from "react"; 4 | 5 | function AdminDownloadDropdown({ options, title, onClick }) { 6 | const createOverlay = () => { 7 | return ( 8 | 9 | {options.map((option) => { 10 | return ( 11 | onClick(option.value)}> 12 | {option.label} 13 | 14 | ); 15 | })} 16 | 17 | ); 18 | }; 19 | 20 | return ( 21 | 22 | 25 | 26 | ); 27 | } 28 | 29 | export default AdminDownloadDropdown; 30 | -------------------------------------------------------------------------------- /frontend/src/components/HubFooter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout, theme } from "antd"; 3 | import { withRouter, useHistory } from "react-router-dom"; 4 | import { css } from "@emotion/css"; 5 | import { useTranslation } from "react-i18next"; 6 | import "components/css/Navigation.scss"; 7 | // import { ReactComponent as Logo } from "resources/mentee.svg"; 8 | import BigLogoImage from "resources/Mentee_logo_letter.png"; 9 | 10 | const { Footer } = Layout; 11 | 12 | function HubFooter() { 13 | const { 14 | token: { colorBgContainer }, 15 | } = theme.useToken(); 16 | const { t } = useTranslation(); 17 | const history = useHistory(); 18 | // TODO: Add a proper admin notifications dropdown 19 | return ( 20 |
25 |
35 | {t("common.powered_by")} 36 | {""} history.push("/")} 45 | /> 46 |
47 |
48 | ); 49 | } 50 | 51 | export default withRouter(HubFooter); 52 | -------------------------------------------------------------------------------- /frontend/src/components/Initiator.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import { useDispatch } from "react-redux"; 4 | import { useAuth } from "utils/hooks/useAuth"; 5 | import { fetchUser } from "features/userSlice"; 6 | import { fetchOptions } from "features/optionsSlice"; 7 | import { getProfileId, getRole } from "utils/auth.service"; 8 | 9 | function Initiator() { 10 | const { i18n } = useTranslation(); 11 | const profileId = getProfileId(); 12 | const role = getRole(); 13 | const dispatch = useDispatch(); 14 | 15 | useEffect(() => { 16 | dispatch(fetchOptions()); 17 | }, [i18n.language]); 18 | 19 | useEffect(() => { 20 | if (profileId && role) { 21 | dispatch(fetchUser({ id: profileId, role })); 22 | } 23 | }, []); 24 | return null; 25 | } 26 | 27 | export default Initiator; 28 | -------------------------------------------------------------------------------- /frontend/src/components/LanguageDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { updateAndFetchUser } from "features/userSlice"; 4 | import { Dropdown } from "antd"; 5 | import { GlobalOutlined } from "@ant-design/icons"; 6 | import { useTranslation } from "react-i18next"; 7 | import moment from "moment"; 8 | import { ACCOUNT_TYPE, I18N_LANGUAGES } from "utils/consts"; 9 | import { css } from "@emotion/css"; 10 | 11 | function LanguageDropdown({ 12 | size = "1em", 13 | className, 14 | style, 15 | placement = "bottomRight", 16 | }) { 17 | const { t, i18n } = useTranslation(); 18 | const { user, role } = useSelector((state) => state.user); 19 | const dispatch = useDispatch(); 20 | 21 | const languageOptions = I18N_LANGUAGES.map(({ value }) => { 22 | return { 23 | key: value, 24 | label: ( 25 | handleLanguageChange(value)}> 26 | {t(`languages.${value.split("-")[0]}`)} 27 | 28 | ), 29 | }; 30 | }); 31 | 32 | useEffect(() => { 33 | if (user && role !== ACCOUNT_TYPE.ADMIN) 34 | i18n.changeLanguage(user.preferred_language); 35 | }, [user]); 36 | 37 | const handleLanguageChange = (language) => { 38 | i18n.changeLanguage(language); 39 | moment.locale(language); 40 | if (user && role !== ACCOUNT_TYPE.ADMIN) { 41 | dispatch( 42 | updateAndFetchUser({ 43 | data: { preferred_language: language }, 44 | id: user?._id?.$oid, 45 | role, 46 | }) 47 | ); 48 | } 49 | }; 50 | 51 | return ( 52 | 61 | 67 | 68 | ); 69 | } 70 | 71 | export default LanguageDropdown; 72 | -------------------------------------------------------------------------------- /frontend/src/components/MeetingPanel.js: -------------------------------------------------------------------------------- 1 | import { useSelector, useDispatch } from "react-redux"; 2 | import { JaaSMeeting, JitsiMeeting } from "@jitsi/react-sdk"; 3 | import React, { useRef, useState } from "react"; 4 | import { setPanel, removePanel } from "features/meetingPanelSlice"; 5 | 6 | const MeetingPanel = () => { 7 | const dispatch = useDispatch(); 8 | var panelComponent = useSelector( 9 | (state) => state.meetingPanel.panelComponent 10 | ); 11 | 12 | const ClosePanel = (event) => { 13 | if (event === undefined) { 14 | dispatch(removePanel()); 15 | } 16 | }; 17 | 18 | if (panelComponent) { 19 | return ( 20 |
21 | { 23 | iframeRef.style.position = "fixed"; 24 | iframeRef.style.bottom = 0; 25 | iframeRef.style.right = 0; 26 | iframeRef.style.width = "30%"; 27 | iframeRef.style.height = "100%"; 28 | }} 29 | appId={panelComponent.app_id} 30 | roomName={panelComponent.room_name} 31 | jwt={panelComponent.token} 32 | configOverwrite={{ 33 | disableThirdPartyRequests: true, 34 | disableLocalVideoFlip: true, 35 | backgroundAlpha: 0.5, 36 | }} 37 | interfaceConfigOverwrite={{ 38 | VIDEO_LAYOUT_FIT: "nocrop", 39 | MOBILE_APP_PROMO: false, 40 | TILE_VIEW_MAX_COLUMNS: 4, 41 | }} 42 | onReadyToClose={ClosePanel} 43 | /> 44 | 59 |
60 | ); 61 | } else { 62 | return
; 63 | } 64 | }; 65 | 66 | export default MeetingPanel; 67 | -------------------------------------------------------------------------------- /frontend/src/components/MenteeButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "antd"; 3 | import "./css/MenteeButton.scss"; 4 | 5 | function MenteeButton(props) { 6 | const getButtonClass = (theme, focus) => { 7 | let style = ""; 8 | if (theme === "dark") style = "dark-button"; 9 | else if (theme === "light") style = "light-button"; 10 | else style = "regular-button"; 11 | 12 | if (focus) style += "-focus"; 13 | return style; 14 | }; 15 | 16 | return ( 17 | 36 | ); 37 | } 38 | 39 | export default MenteeButton; 40 | -------------------------------------------------------------------------------- /frontend/src/components/MenteeSidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | UserOutlined, 4 | CalendarOutlined, 5 | MailOutlined, 6 | LinkOutlined, 7 | } from "@ant-design/icons"; 8 | import Sidebar from "./Sidebar"; 9 | import { useTranslation } from "react-i18next"; 10 | import { useAuth } from "utils/hooks/useAuth"; 11 | import CreateMeetingLink from "./components/CreateMeetingLink"; 12 | 13 | function MenteeSidebar(props) { 14 | const { t } = useTranslation(); 15 | const { role } = useAuth(); 16 | 17 | const pages = { 18 | appointments: { 19 | name: t("sidebars.appointments"), 20 | path: "/mentee-appointments", 21 | icon: , 22 | }, 23 | profile: { 24 | name: t("sidebars.profile"), 25 | path: "/profile", 26 | icon: , 27 | }, 28 | message: { 29 | name: t("common.messages"), 30 | path: "/messages/" + role, 31 | icon: , 32 | }, 33 | }; 34 | 35 | //return ; 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | {/* Other sidebar content within Sidebar.Item components */} 43 | 44 | ); 45 | } 46 | 47 | export default MenteeSidebar; 48 | -------------------------------------------------------------------------------- /frontend/src/components/MenteeVideo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "components/css/MenteeVideo.scss"; 4 | import { Col, Row } from "antd"; 5 | import ReactPlayer from "react-player"; 6 | import { useTranslation } from "react-i18next"; 7 | 8 | function MenteeVideo({ video }) { 9 | const { t } = useTranslation(); 10 | 11 | return ( 12 |
13 |

14 | {t("commonProfile.videoIntroduction")} 15 |

16 |
17 | 18 | 19 |
20 | {video && ( 21 | 27 | )} 28 |
29 | {video && ( 30 |
31 | {video.title} 32 |
33 | )} 34 | 35 |
36 |
37 | ); 38 | } 39 | 40 | export default MenteeVideo; 41 | -------------------------------------------------------------------------------- /frontend/src/components/MentorAppProgress.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import "./css/MentorApplicationView.scss"; 3 | import { APP_STATUS } from "../utils/consts"; 4 | 5 | function MentorAppProgress({ progress }) { 6 | const [status, setStatus] = useState({}); 7 | 8 | useEffect(() => { 9 | let newStatus = {}; 10 | switch (progress) { 11 | case APP_STATUS.PENDING: 12 | newStatus["style"] = { background: "#DADADA" }; 13 | break; 14 | case APP_STATUS.REVIEWED: 15 | newStatus["style"] = { background: "#FFDB6F" }; 16 | break; 17 | case APP_STATUS.REJECTED: 18 | newStatus["style"] = { background: "#B15858" }; 19 | break; 20 | case APP_STATUS.OFFER_MADE: 21 | newStatus["style"] = { background: "#6FCF97" }; 22 | break; 23 | default: 24 | newStatus["style"] = { background: "#DADADA" }; 25 | break; 26 | } 27 | newStatus["text"] = progress; 28 | setStatus(newStatus); 29 | }, []); 30 | 31 | return ( 32 |
33 |
34 | {status.text} 35 |
36 |
37 | ); 38 | } 39 | 40 | export default MentorAppProgress; 41 | -------------------------------------------------------------------------------- /frontend/src/components/MentorSidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | UserOutlined, 4 | VideoCameraOutlined, 5 | CalendarOutlined, 6 | MailOutlined, 7 | } from "@ant-design/icons"; 8 | import Sidebar from "./Sidebar"; 9 | import { useTranslation } from "react-i18next"; 10 | import { useAuth } from "utils/hooks/useAuth"; 11 | import CreateMeetingLink from "./components/CreateMeetingLink"; 12 | 13 | function MentorSidebar(props) { 14 | const { t } = useTranslation(); 15 | const { role } = useAuth(); 16 | const pages = { 17 | appointments: { 18 | name: t("sidebars.appointments"), 19 | path: "/appointments", 20 | icon: , 21 | }, 22 | videos: { 23 | name: t("sidebars.videos"), 24 | path: "/videos", 25 | icon: , 26 | }, 27 | profile: { 28 | name: t("sidebars.profile"), 29 | path: "/profile", 30 | icon: , 31 | }, 32 | message: { 33 | name: t("common.messages"), 34 | path: "/messages/" + role, 35 | icon: , 36 | }, 37 | }; 38 | 39 | //return ; 40 | 41 | return ( 42 | 43 | 44 | 45 | 46 | {/* Other sidebar content within Sidebar.Item components */} 47 | 48 | ); 49 | } 50 | 51 | export default MentorSidebar; 52 | -------------------------------------------------------------------------------- /frontend/src/components/MentorVideo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactPlayer from "react-player"; 3 | import { DeleteOutlined, PushpinOutlined } from "@ant-design/icons"; 4 | import moment from "moment"; 5 | import { Button, Select } from "antd"; 6 | import "components/css/MentorVideo.scss"; 7 | import { useSelector } from "react-redux"; 8 | 9 | const MentorVideo = ({ 10 | onPin, 11 | onChangeTag, 12 | onDelete, 13 | title, 14 | tag, 15 | id, 16 | date, 17 | video, 18 | }) => { 19 | const options = useSelector((state) => state.options); 20 | 21 | return ( 22 |
23 |
24 | 30 |
31 | {title} 32 |
33 | {moment(date).format("MM/DD/YY")} • {moment(date).fromNow()} 34 |
35 |
36 |
37 | 45 |
46 |
47 |
48 | 42 | 43 | ({ 52 | validator(_, value) { 53 | if (!value || ReactPlayer.canPlay(getFieldValue("url"))) { 54 | return Promise.resolve(); 55 | } 56 | return Promise.reject(new Error("Invalid URL")); 57 | }, 58 | }), 59 | ]} 60 | > 61 | 62 | 63 | 73 |