From 4a4f55cc340a8d53103ad485344f06542cab03f2 Mon Sep 17 00:00:00 2001 From: Ndibe Raymond Olisaemeka Date: Tue, 26 Jan 2021 06:32:37 +0100 Subject: [PATCH] Fifth Iteration of CI/CD (#93) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * added description to the video url field in project creation form issue #50 (#61) * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * switched handling of ssl back to valian/docker-nginx-auto-ssl * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD (#75) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * phase 2 partial ------ 3 (#73) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch * made deploy_frontend.sh more explanatory * separated docker-compose files into dev and prod in preparation for CI/CD --- patch (#77) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch * made deploy_frontend.sh more explanatory * Code Refactor (#67) * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * Issue #54: switched from class based views to function based views, moved styles to seprate files and changed the general structure of the project to be more intuitive * more refactoring * more refactor -- added new prettier rules and prettified more files not being covered by prettier initially * Customized form submission error (#80) * fixed issue #25 --- initial * prettified * fixed issue #34: Increased upload image size, added image compression and functionality to remove image metadata (#79) * Removed line behind dob field label on the signup page (#81) * fixed issue #26: removed line behind DOB input label * fixed issue #26: removed line behind DOB field text in signup --- patch * fixed issue #52: Added help text to project creation desc field (#82) * fixed issue #59: Added projects, followers and following links to profile dropdown menu (#86) * added functionality to show creators we are following (#83) * added functionality to show creators we are following * fixed issue #58: added functionality to show creators we are following --- patch * ci/cd ---- test 1 * First Iteration of CI/CD workflow (#89) * Added more actions to profile dropdown (#84) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD (#75) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * phase 2 partial ------ 3 (#73) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch (#77) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch * made deploy_frontend.sh more explanatory * Code Refactor (#67) * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * Issue #54: switched from class based views to function based views, moved styles to seprate files and changed the general structure of the project to be more intuitive * more refactoring * more refactor -- added new prettier rules and prettified more files not being covered by prettier initially * Customized form submission error (#80) * fixed issue #25 --- initial * prettified * fixed issue #34: Increased upload image size, added image compression and functionality to remove image metadata (#79) * Removed line behind dob field label on the signup page (#81) * fixed issue #26: removed line behind DOB input label * fixed issue #26: removed line behind DOB field text in signup --- patch * fixed issue #52: Added help text to project creation desc field (#82) * fixed issue #59: Added projects, followers and following links to profile dropdown menu * Revert "Added more actions to profile dropdown (#84)" (#85) This reverts commit 271408a0cf132863759bfd8a2d510b07c0106832. * ci/cd ---- test 1 * ci/cd ---- test 2 * Second Iteration of CI/CD (#90) * Added more actions to profile dropdown (#84) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD (#75) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * phase 2 partial ------ 3 (#73) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch (#77) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch * made deploy_frontend.sh more explanatory * Code Refactor (#67) * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * Issue #54: switched from class based views to function based views, moved styles to seprate files and changed the general structure of the project to be more intuitive * more refactoring * more refactor -- added new prettier rules and prettified more files not being covered by prettier initially * Customized form submission error (#80) * fixed issue #25 --- initial * prettified * fixed issue #34: Increased upload image size, added image compression and functionality to remove image metadata (#79) * Removed line behind dob field label on the signup page (#81) * fixed issue #26: removed line behind DOB input label * fixed issue #26: removed line behind DOB field text in signup --- patch * fixed issue #52: Added help text to project creation desc field (#82) * fixed issue #59: Added projects, followers and following links to profile dropdown menu * Revert "Added more actions to profile dropdown (#84)" (#85) This reverts commit 271408a0cf132863759bfd8a2d510b07c0106832. * ci/cd ---- test 1 * ci/cd ---- test 2 * ci/cd ---- test 3 * third iteration of CI/CD (#91) * Added more actions to profile dropdown (#84) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD (#75) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * phase 2 partial ------ 3 (#73) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed bug that occurs when user submit google drive video link (#72) * added functionality to format youtube video url to embedable format * made video url optional * switched image upload location from cloudinary to digital ocean spaces * added functionality to automatically delete image from digitalocean space once image is deleted from db * added image count indicator and made video optional. also added project create button to navbar * removed .ssl from git * untracked .ssl-data * added support for various forms of youtube video url, vimeo and google drive * fixed issues #35, #33, #32, #30, #29 * fixed issue #46 * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * fixed issue #68 * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch (#77) * domain setup step 1 test 5 * domain setup step 2(backend) test 1 * domain setup step2(backend) test2 * removed .ssl-data from .dockerignore * added custom nginx container to handle reverse proxying and https requests * made important changes to deploy_frontend.sh, added google tracking code to index.html, enabled crawling * switched handling of ssl back to valian/docker-nginx-auto-ssl * separated docker-compose files into dev and prod in preparation for CI/CD * separated docker-compose files into dev and prod in preparation for CI/CD -- backend * separated docker-compose files into dev and prod in preparation for CI/CD --- patch * made deploy_frontend.sh more explanatory * Code Refactor (#67) * phase2 patial ----- 2 (#66) * added description to the video url field in project creation form issue #50 (#61) * new deployment changes (#62) * increased pagination limit from 6 to 20 (#63) * phase2 patial ----- 2 (#65) switched handling of ssl back to valian/docker-nginx-auto-ssl * Issue #54: switched from class based views to function based views, moved styles to seprate files and changed the general structure of the project to be more intuitive * more refactoring * more refactor -- added new prettier rules and prettified more files not being covered by prettier initially * Customized form submission error (#80) * fixed issue #25 --- initial * prettified * fixed issue #34: Increased upload image size, added image compression and functionality to remove image metadata (#79) * Removed line behind dob field label on the signup page (#81) * fixed issue #26: removed line behind DOB input label * fixed issue #26: removed line behind DOB field text in signup --- patch * fixed issue #52: Added help text to project creation desc field (#82) * fixed issue #59: Added projects, followers and following links to profile dropdown menu * Revert "Added more actions to profile dropdown (#84)" (#85) This reverts commit 271408a0cf132863759bfd8a2d510b07c0106832. * ci/cd ---- test 1 * ci/cd ---- test 2 * ci/cd ---- test 3 * Update build_deploy_backend.yml manually added manual workflow dispatch to github actions file * ci/cd ---- test 5 ---- patch --- .github/workflows/build_deploy_backend.yml | 29 ++- .github/workflows/build_deploy_frontend.yml | 58 +++-- zubhub_backend/deploy_backend.sh | 26 +- zubhub_backend/docker-compose.prod.yml | 4 +- zubhub_backend/zubhub/creators/models.py | 5 +- zubhub_backend/zubhub/creators/serializers.py | 2 +- zubhub_backend/zubhub/creators/urls.py | 2 + zubhub_backend/zubhub/creators/views.py | 11 + zubhub_frontend/zubhub/deploy_frontend.sh | 3 + .../zubhub/docker-compose.prod.yml | 14 +- zubhub_frontend/zubhub/docker-compose.yml | 4 +- zubhub_frontend/zubhub/package-lock.json | 27 +++ zubhub_frontend/zubhub/package.json | 4 +- zubhub_frontend/zubhub/src/App.js | 12 +- zubhub_frontend/zubhub/src/api/api.js | 10 + .../zubhub/src/assets/js/Compress.js | 39 +++ .../src/assets/js/removeMetaDataWorker.js | 62 +++++ .../js/styles/views/signup/signupStyles.js | 5 + .../zubhub/src/store/actions/authActions.js | 91 ++----- .../src/store/actions/projectActions.js | 30 +-- .../zubhub/src/store/actions/userActions.js | 25 ++ .../zubhub/src/views/PageWrapper.jsx | 42 ++++ .../views/create_project/CreateProject.jsx | 146 ++++++++--- .../src/views/email_confirm/EmailConfirm.jsx | 11 +- .../zubhub/src/views/login/Login.jsx | 21 +- .../views/password_reset/PasswordReset.jsx | 21 +- .../PasswordResetConfirm.jsx | 23 +- .../zubhub/src/views/profile/Profile.jsx | 39 ++- .../views/project_details/ProjectDetails.jsx | 1 - .../zubhub/src/views/signup/Signup.jsx | 31 ++- .../views/user_following/UserFollowing.jsx | 226 ++++++++++++++++++ 31 files changed, 828 insertions(+), 196 deletions(-) create mode 100644 zubhub_frontend/zubhub/src/assets/js/Compress.js create mode 100644 zubhub_frontend/zubhub/src/assets/js/removeMetaDataWorker.js create mode 100644 zubhub_frontend/zubhub/src/views/user_following/UserFollowing.jsx diff --git a/.github/workflows/build_deploy_backend.yml b/.github/workflows/build_deploy_backend.yml index b0e81cf96..289e60112 100644 --- a/.github/workflows/build_deploy_backend.yml +++ b/.github/workflows/build_deploy_backend.yml @@ -5,14 +5,14 @@ on: branches: - master paths: - - "*" + - "zubhub_backend/**" - "!zubhub_backend/.gitignore" - "!zubhub_backend/.env.example" - "!zubhub_backend/.dockerignore" workflow_dispatch: jobs: - path-context: + build: runs-on: ubuntu-latest steps: - name: Set up QEMU @@ -38,3 +38,28 @@ jobs: tags: unstructuredstudio/zubhub-backend:latest - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} + + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: Copy file via scp + uses: appleboy/scp-action@master + env: + HOST: ${{ secrets.DO_BACKEND_HOST }} + USERNAME: ${{ secrets.DO_BACKEND_USERNAME }} + KEY: ${{ secrets.DO_SSHKEY }} + with: + source: "." + target: "/home/zubhub-backend/zubhub" + + - name: Executing remote command + uses: appleboy/ssh-action@master + with: + HOST: ${{ secrets.DO_BACKEND_HOST }} + USERNAME: ${{ secrets.DO_BACKEND_USERNAME }} + KEY: ${{ secrets.DO_SSHKEY }} + script: "cp /home/zubhub-backend/zubhub/zubhub_backend/deploy_backend.sh /home/zubhub-backend/ && sudo bash /home/zubhub-backend/deploy_backend.sh" diff --git a/.github/workflows/build_deploy_frontend.yml b/.github/workflows/build_deploy_frontend.yml index e4930b971..c1cdf34bb 100644 --- a/.github/workflows/build_deploy_frontend.yml +++ b/.github/workflows/build_deploy_frontend.yml @@ -3,9 +3,9 @@ name: Frontend Production Deployment on: push: branches: - - phase2 + - master paths: - - "zubhub_frontend/zubhub/" + - "zubhub_frontend/zubhub/**" - "!zubhub_frontend/zubhub/README.md" - "!zubhub_frontend/zubhub/.env.example" - "!zubhub_frontend/zubhub/.prettierrc.yaml" @@ -13,35 +13,35 @@ on: workflow_dispatch: jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - name: Set up QEMU -# uses: docker/setup-qemu-action@v1 -# - name: Set up Docker Buildx -# uses: docker/setup-buildx-action@v1 -# - name: Login to DockerHub -# uses: docker/login-action@v1 -# with: -# username: ${{ secrets.DOCKERHUB_USERNAME }} -# password: ${{ secrets.DOCKERHUB_TOKEN }} + build: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} -# - name: Checkout files -# uses: actions/checkout@v2 + - name: Checkout files + uses: actions/checkout@v2 + + - name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + context: ./zubhub_frontend/zubhub/ + file: ./zubhub_frontend/zubhub/Dockerfile.prod + push: true + tags: unstructuredstudio/zubhub-frontend:latest + - name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} -# - name: Build and push -# id: docker_build -# uses: docker/build-push-action@v2 -# with: -# context: ./zubhub_frontend/zubhub/ -# file: ./zubhub_frontend/zubhub/Dockerfile.prod -# push: true -# tags: unstructuredstudio/zubhub-frontend:latest -# - name: Image digest -# run: echo ${{ steps.docker_build.outputs.digest }} - deploy: -# needs: build + needs: build runs-on: ubuntu-latest steps: @@ -52,7 +52,6 @@ jobs: env: HOST: ${{ secrets.DO_FRONTEND_HOST }} USERNAME: ${{ secrets.DO_FRONTEND_USERNAME }} - PORT: ${{ secrets.DO_SSHPORT }} KEY: ${{ secrets.DO_SSHKEY }} with: source: "." @@ -66,4 +65,3 @@ jobs: PORT: ${{ secrets.DO_SSHPORT }} KEY: ${{ secrets.DO_SSHKEY }} script: "cp /home/zubhub-frontend/zubhub/zubhub_frontend/zubhub/deploy_frontend.sh /home/zubhub-frontend/ && sudo bash /home/zubhub-frontend/deploy_frontend.sh" - diff --git a/zubhub_backend/deploy_backend.sh b/zubhub_backend/deploy_backend.sh index 9fa91648a..bbeda0f47 100644 --- a/zubhub_backend/deploy_backend.sh +++ b/zubhub_backend/deploy_backend.sh @@ -1,11 +1,23 @@ #! /bin/bash -mv zubhub_backend/.env zubhub/zubhub_backend/.env -rm -rf zubhub_backend -cp -r zubhub/zubhub_backend/ zubhub_backend/ -rm -rf zubhub/ zubhub_backend/.env.example -cd zubhub_backend -docker-compose down -docker-compose -f ./docker-compose.prod.yml up -d --build +echo "copying .env file and ssl folder" +mv /home/zubhub-backend/zubhub_backend/.env /home/zubhub-backend/zubhub/zubhub_backend/.env +echo "done copying .env file and ssl folder" + +echo "removing old project folder" +rm -rf /home/zubhub-backend/zubhub_backend +echo "done removing old project folder" + +echo "coping new project folder" +cp -r /home/zubhub-backend/zubhub/zubhub_backend/ /home/zubhub-backend/zubhub_backend/ +echo "done coping new project folder" + +echo "removing uneccessary files and folders" +rm -rf /home/zubhub-backend/zubhub/ /home/zubhub-backend/zubhub_backend/.env.example +echo "done removing uneccessary files and folders" + +echo "rebuilding containers" +docker-compose -f /home/zubhub-backend/zubhub_backend/docker-compose.prod.yml down +docker-compose -f /home/zubhub-backend/zubhub_backend/docker-compose.prod.yml up -d --build echo "Updated backend" # EOT \ No newline at end of file diff --git a/zubhub_backend/docker-compose.prod.yml b/zubhub_backend/docker-compose.prod.yml index c3ffc502f..43cb6be71 100644 --- a/zubhub_backend/docker-compose.prod.yml +++ b/zubhub_backend/docker-compose.prod.yml @@ -2,10 +2,8 @@ version: "3.3" services: web: + image: unstructuredstudio/zubhub-backend:latest env_file: .env - build: - context: . - dockerfile: ./Dockerfile command: /start restart: on-failure environment: diff --git a/zubhub_backend/zubhub/creators/models.py b/zubhub_backend/zubhub/creators/models.py index b94fae87c..0a667e528 100644 --- a/zubhub_backend/zubhub/creators/models.py +++ b/zubhub_backend/zubhub/creators/models.py @@ -32,12 +32,15 @@ class Creator(AbstractUser): location = models.ForeignKey( Location, null=True, on_delete=models.SET_NULL) bio = models.CharField(max_length=255, blank=True, null=True) - followers = models.ManyToManyField("self", symmetrical=False) + followers = models.ManyToManyField( + "self", symmetrical=False, related_name="following") followers_count = models.IntegerField(blank=True, default=0) + following_count = models.IntegerField(blank=True, default=0) projects_count = models.IntegerField(blank=True, default=0) def save(self, *args, **kwargs): self.avatar = 'https://robohash.org/{0}'.format(self.username) self.followers_count = self.followers.count() + self.following_count = self.following.count() self.projects_count = self.projects.count() super().save(*args, **kwargs) diff --git a/zubhub_backend/zubhub/creators/serializers.py b/zubhub_backend/zubhub/creators/serializers.py index 34f8a4989..316d54749 100644 --- a/zubhub_backend/zubhub/creators/serializers.py +++ b/zubhub_backend/zubhub/creators/serializers.py @@ -17,7 +17,7 @@ class CreatorSerializer(serializers.ModelSerializer): class Meta: model = Creator fields = ('id', 'username', 'email', 'avatar', - 'dateOfBirth', 'bio', 'followers', 'projects_count') + 'dateOfBirth', 'bio', 'followers', 'following_count', 'projects_count') class LocationSerializer(serializers.ModelSerializer): diff --git a/zubhub_backend/zubhub/creators/urls.py b/zubhub_backend/zubhub/creators/urls.py index 32716297e..51b3181e0 100644 --- a/zubhub_backend/zubhub/creators/urls.py +++ b/zubhub_backend/zubhub/creators/urls.py @@ -11,6 +11,8 @@ UserProjectsAPIView.as_view(), name="user_projects"), path('/followers/', UserFollowersAPIView.as_view(), name='user_followers'), + path('/following/', + UserFollowingAPIView.as_view(), name='user_following'), path('/', UserProfileAPIView.as_view(), name='user_profile'), path('/toggle_follow/', ToggleFollowAPIView.as_view(), name="toggle_follow") diff --git a/zubhub_backend/zubhub/creators/views.py b/zubhub_backend/zubhub/creators/views.py index e7b34c840..7696d1fe6 100644 --- a/zubhub_backend/zubhub/creators/views.py +++ b/zubhub_backend/zubhub/creators/views.py @@ -68,6 +68,16 @@ def get_queryset(self): return Creator.objects.get(username=username).followers.all() +class UserFollowingAPIView(ListAPIView): + serializer_class = CreatorSerializer + permission_classes = [AllowAny] + pagination_class = CreatorNumberPagination + + def get_queryset(self): + username = self.kwargs.get("username") + return Creator.objects.get(username=username).following.all() + + class ToggleFollowAPIView(RetrieveAPIView): serializer_class = CreatorSerializer queryset = Creator.objects.all() @@ -83,6 +93,7 @@ def get_object(self): else: obj.followers.add(self.request.user) obj.save() + self.request.user.save() return obj diff --git a/zubhub_frontend/zubhub/deploy_frontend.sh b/zubhub_frontend/zubhub/deploy_frontend.sh index ed9fb985d..e9a93444b 100644 --- a/zubhub_frontend/zubhub/deploy_frontend.sh +++ b/zubhub_frontend/zubhub/deploy_frontend.sh @@ -17,10 +17,13 @@ echo "changing permission of cert storage folder" sudo chown -R nobody:nogroup /home/zubhub-frontend/zubhub_frontend/zubhub/.ssl-data/storage echo "done changing permission of cert storage folder" +echo "removing uneccessary files and folders" +rm -rf /home/zubhub-frontend/zubhub rm -rf /home/zubhub-frontend/zubhub_frontend/zubhub/.env.example rm -rf /home/zubhub-frontend/zubhub_frontend/zubhub/Dockerfile rm -rf /home/zubhub-frontend/zubhub_frontend/zubhub/docker-compose.yml rm -rf /home/zubhub-frontend/zubhub_frontend/zubhub/nginx/dev +echo "done removing uneccessary files and folders" diff --git a/zubhub_frontend/zubhub/docker-compose.prod.yml b/zubhub_frontend/zubhub/docker-compose.prod.yml index e0c527af6..b11bbf883 100644 --- a/zubhub_frontend/zubhub/docker-compose.prod.yml +++ b/zubhub_frontend/zubhub/docker-compose.prod.yml @@ -1,16 +1,14 @@ -version: "3.3" +version: '3.3' services: zubhub_frontend: + image: unstructuredstudio/zubhub-frontend:latest container_name: zubhub_frontend - build: - context: . - dockerfile: Dockerfile.prod volumes: - ./nginx/prod/default.conf:/etc/nginx/conf.d/default.conf restart: on-failure ports: - - "80" + - '80' reverse-proxy: image: valian/docker-nginx-auto-ssl:1.0.0 @@ -22,9 +20,9 @@ services: volumes: - ./.ssl-data:/etc/resty-auto-ssl environment: - ALLOWED_DOMAINS: "(zubhub|www.zubhub).unstructured.studio" - SITES: "zubhub.unstructured.studio=zubhub_frontend;www.zubhub.unstructured.studio=zubhub_frontend" - FORCE_HTTPS: "true" + ALLOWED_DOMAINS: '(zubhub|www.zubhub).unstructured.studio' + SITES: 'zubhub.unstructured.studio=zubhub_frontend;www.zubhub.unstructured.studio=zubhub_frontend' + FORCE_HTTPS: 'true' depends_on: - zubhub_frontend diff --git a/zubhub_frontend/zubhub/docker-compose.yml b/zubhub_frontend/zubhub/docker-compose.yml index 7db4ee546..89392bab4 100644 --- a/zubhub_frontend/zubhub/docker-compose.yml +++ b/zubhub_frontend/zubhub/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.3" +version: '3.3' services: zubhub_frontend: @@ -10,4 +10,4 @@ services: - ./nginx/dev/default.conf:/etc/nginx/conf.d/default.conf restart: on-failure ports: - - "3000:3000" + - '3000:3000' diff --git a/zubhub_frontend/zubhub/package-lock.json b/zubhub_frontend/zubhub/package-lock.json index b1c50af10..20d60cf5d 100644 --- a/zubhub_frontend/zubhub/package-lock.json +++ b/zubhub_frontend/zubhub/package-lock.json @@ -3424,6 +3424,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "blueimp-canvas-to-blob": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.28.0.tgz", + "integrity": "sha512-5q+YHzgGsuHQ01iouGgJaPJXod2AzTxJXmVv90PpGrRxU7G7IqgPqWXz+PBmt3520jKKi6irWbNV87DicEa7wg==" + }, "bn.js": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", @@ -4175,6 +4180,15 @@ } } }, + "compressorjs": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.0.7.tgz", + "integrity": "sha512-ca+H8CGrn0LG103//VQmXBbNdvzvHiW26LGdWncp4RmLNbNQjaaFWIUxMN9++hbhGobLtofkHoxzzXGisNyD3w==", + "requires": { + "blueimp-canvas-to-blob": "^3.28.0", + "is-blob": "^2.1.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7709,6 +7723,11 @@ "binary-extensions": "^2.0.0" } }, + "is-blob": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", + "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -16428,6 +16447,14 @@ "microevent.ts": "~0.1.1" } }, + "workerize-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/workerize-loader/-/workerize-loader-1.3.0.tgz", + "integrity": "sha512-utWDc8K6embcICmRBUUkzanPgKBb8yM1OHfh6siZfiMsswE8wLCa9CWS+L7AARz0+Th4KH4ZySrqer/OJ9WuWw==", + "requires": { + "loader-utils": "^2.0.0" + } + }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/zubhub_frontend/zubhub/package.json b/zubhub_frontend/zubhub/package.json index c24a548ff..472e0d408 100644 --- a/zubhub_frontend/zubhub/package.json +++ b/zubhub_frontend/zubhub/package.json @@ -12,6 +12,7 @@ "@testing-library/user-event": "^12.2.2", "aws-sdk": "^2.813.0", "classnames": "^2.2.6", + "compressorjs": "^1.0.7", "date-fns": "^2.16.1", "formik": "^2.2.5", "nanoid": "^3.1.20", @@ -27,6 +28,7 @@ "redux-thunk": "^2.3.0", "slick-carousel": "^1.8.1", "web-vitals": "^0.2.4", + "workerize-loader": "^1.3.0", "yup": "^0.29.3" }, "scripts": { @@ -55,6 +57,6 @@ ] }, "devDependencies": { - "prettier": "2.2.1" + "prettier": "^2.2.1" } } diff --git a/zubhub_frontend/zubhub/src/App.js b/zubhub_frontend/zubhub/src/App.js index e443f415b..2a75da42b 100644 --- a/zubhub_frontend/zubhub/src/App.js +++ b/zubhub_frontend/zubhub/src/App.js @@ -2,8 +2,6 @@ import React from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; -import { withAPI } from './api'; - import PageWrapper from './views/PageWrapper'; import Signup from './views/signup/Signup'; import Login from './views/login/Login'; @@ -13,6 +11,7 @@ import EmailConfirm from './views/email_confirm/EmailConfirm'; import Profile from './views/profile/Profile'; import UserProjects from './views/user_projects/UserProjects'; import UserFollowers from './views/user_followers/UserFollowers'; +import UserFollowing from './views/user_following/UserFollowing'; import Projects from './views/projects/Projects'; import SavedProjects from './views/saved_projects/SavedProjects'; import CreateProject from './views/create_project/CreateProject'; @@ -95,6 +94,15 @@ function App(props) { )} /> + ( + + + + )} + /> + ( diff --git a/zubhub_frontend/zubhub/src/api/api.js b/zubhub_frontend/zubhub/src/api/api.js index 2e46bd47b..267a0b4bc 100644 --- a/zubhub_frontend/zubhub/src/api/api.js +++ b/zubhub_frontend/zubhub/src/api/api.js @@ -171,6 +171,16 @@ class API { }; /*****************************************************************/ + /********************** get following *******************************/ + get_following = ({ page, username }) => { + const url = page + ? `creators/${username}/following/?${page}` + : `creators/${username}/following/`; + + return this.request({ url }).then(res => res.json()); + }; + /*****************************************************************/ + /********************** get saved *******************************/ get_saved = ({ page, token }) => { const url = page ? `projects/saved/?${page}` : `projects/saved/`; diff --git a/zubhub_frontend/zubhub/src/assets/js/Compress.js b/zubhub_frontend/zubhub/src/assets/js/Compress.js new file mode 100644 index 000000000..f4aec897c --- /dev/null +++ b/zubhub_frontend/zubhub/src/assets/js/Compress.js @@ -0,0 +1,39 @@ +import Compressor from 'compressorjs'; + +const Compress = (images, state, handleSetState) => { + let compressed = []; + + for (let index = 0; index < images.length; index += 1) { + let image = images[index]; + + if (image && image.type.split('/')[1] !== 'gif') { + new Compressor(image, { + quality: 0.6, + convertSize: 100000, + success: result => { + compressed.push(result); + shouldSetImages(compressed, images, state, handleSetState); + }, + error: error => { + console.warn(error.message); + compressed.push(image); + shouldSetImages(compressed, images, state, handleSetState); + }, + }); + } else { + compressed.push(image); + shouldSetImages(compressed, images, state, handleSetState); + } + } +}; + +const shouldSetImages = (compressed, images, state, handleSetState) => { + if (compressed.length === images.length) { + const { image_upload } = state; + image_upload.images_to_upload = compressed; + + handleSetState(image_upload); + } +}; + +export default Compress; diff --git a/zubhub_frontend/zubhub/src/assets/js/removeMetaDataWorker.js b/zubhub_frontend/zubhub/src/assets/js/removeMetaDataWorker.js new file mode 100644 index 000000000..166abf3f2 --- /dev/null +++ b/zubhub_frontend/zubhub/src/assets/js/removeMetaDataWorker.js @@ -0,0 +1,62 @@ +export const removeMetaData = imageArr => { + let newImageArr = []; + + for (let index = 0; index < imageArr.length; index++) { + let fr = new FileReader(); + fr.onload = process; + fr.mainFile = imageArr[index]; + fr.readAsArrayBuffer(imageArr[index]); + } + + function process() { + let dv = new DataView(this.result); + let offset = 0, + recess = 0; + let pieces = []; + let i = 0; + if (dv.getUint16(offset) === 0xffd8) { + offset += 2; + let app1 = dv.getUint16(offset); + offset += 2; + while (offset < dv.byteLength) { + if (app1 === 0xffe1) { + pieces[i] = { recess: recess, offset: offset - 2 }; + recess = offset + dv.getUint16(offset); + i++; + } else if (app1 === 0xffda) { + break; + } + offset += dv.getUint16(offset); + app1 = dv.getUint16(offset); + offset += 2; + } + + if (pieces.length > 0) { + let newPieces = []; + pieces.forEach(function (v) { + newPieces.push(this.result.slice(v.recess, v.offset)); + }, this); + newPieces.push(this.result.slice(recess)); + newImageArr.push( + new Blob(newPieces, { type: imageArr[newImageArr.length].type }), + ); + + if (newImageArr.length === imageArr.length) { + postMessage(newImageArr); + } + } else { + newImageArr.push(this.mainFile); + + if (newImageArr.length === imageArr.length) { + postMessage(newImageArr); + } + } + } else { + newImageArr.push(this.mainFile); + + if (newImageArr.length === imageArr.length) { + postMessage(newImageArr); + } + } + } +}; diff --git a/zubhub_frontend/zubhub/src/assets/js/styles/views/signup/signupStyles.js b/zubhub_frontend/zubhub/src/assets/js/styles/views/signup/signupStyles.js index 751895f43..6b0e75171 100644 --- a/zubhub_frontend/zubhub/src/assets/js/styles/views/signup/signupStyles.js +++ b/zubhub_frontend/zubhub/src/assets/js/styles/views/signup/signupStyles.js @@ -42,6 +42,11 @@ const styles = theme => ({ }, }, }, + DOBInputStyle: { + '&.MuiOutlinedInput-root fieldset legend': { + width: '75.5px !important', + }, + }, secondaryLink: { color: '#00B8C4', '&:hover': { diff --git a/zubhub_frontend/zubhub/src/store/actions/authActions.js b/zubhub_frontend/zubhub/src/store/actions/authActions.js index 877810508..e391e3c76 100644 --- a/zubhub_frontend/zubhub/src/store/actions/authActions.js +++ b/zubhub_frontend/zubhub/src/store/actions/authActions.js @@ -17,27 +17,14 @@ export const login = props => { return API.login(props.values) .then(res => { if (!res.key) { - res = Object.keys(res) - .map(key => res[key]) - .join('\n'); - throw new Error(res); + throw new Error(JSON.stringify(res)); } dispatch({ type: 'SET_AUTH_USER', payload: { token: res.key }, }); }) - .then(() => props.history.push('/profile')) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + .then(() => props.history.push('/profile')); }; }; @@ -85,95 +72,61 @@ export const signup = props => { return API.signup(props.values) .then(res => { if (!res.key) { - res = Object.keys(res) - .map(key => res[key]) - .join('\n'); - throw new Error(res); + throw new Error(JSON.stringify(res)); } dispatch({ type: 'SET_AUTH_USER', payload: { token: res.key }, }); }) - .then(() => props.history.push('/profile')) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + .then(() => props.history.push('/profile')); }; }; export const send_email_confirmation = (props, key) => { return () => { - return API.send_email_confirmation(key) - .then(res => { + return API.send_email_confirmation(key).then(res => { + if (res.detail !== 'ok') { + throw new Error(res.detail); + } else { toast.success('Congratulations!, your email has been confirmed!'); setTimeout(() => { props.history.push('/'); }, 4000); - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + } + }); }; }; export const send_password_reset_link = props => { return () => { - return API.send_password_reset_link(props.values.email) - .then(res => { + return API.send_password_reset_link(props.values.email).then(res => { + if (res.detail !== 'Password reset e-mail has been sent.') { + throw new Error(JSON.stringify(res)); + } else { toast.success('We just sent a password reset link to your email!'); setTimeout(() => { props.history.push('/'); }, 4000); - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + } + }); }; }; export const password_reset_confirm = props => { return () => { - return API.password_reset_confirm(props) - .then(res => { + return API.password_reset_confirm(props).then(res => { + if (res.detail !== 'Password has been reset with the new password.') { + throw new Error(JSON.stringify(res)); + } else { toast.success( 'Congratulations! your password reset was successful! you will now be redirected to login', ); setTimeout(() => { props.history.push('/login'); }, 4000); - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + } + }); }; }; diff --git a/zubhub_frontend/zubhub/src/store/actions/projectActions.js b/zubhub_frontend/zubhub/src/store/actions/projectActions.js index 2254c7a7b..e7d3176f4 100644 --- a/zubhub_frontend/zubhub/src/store/actions/projectActions.js +++ b/zubhub_frontend/zubhub/src/store/actions/projectActions.js @@ -13,28 +13,14 @@ export const set_projects = projects => { export const create_project = props => { return dispatch => { - return API.create_project(props) - .then(res => { - if (!res.comments) { - res = Object.keys(res) - .map(key => res[key]) - .join('\n'); - throw new Error(res); - } else { - toast.success('Your project was created successfully!!'); - return props.history.push('/profile'); - } - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: - 'An error occured while performing this action. Please try again later', - }; - } else { - return { error: error.message }; - } - }); + return API.create_project(props).then(res => { + if (!res.id) { + throw new Error(JSON.stringify(res)); + } else { + toast.success('Your project was created successfully!!'); + return props.history.push('/profile'); + } + }); }; }; diff --git a/zubhub_frontend/zubhub/src/store/actions/userActions.js b/zubhub_frontend/zubhub/src/store/actions/userActions.js index 8e5b6dafc..ffa7212c7 100644 --- a/zubhub_frontend/zubhub/src/store/actions/userActions.js +++ b/zubhub_frontend/zubhub/src/store/actions/userActions.js @@ -106,3 +106,28 @@ export const get_followers = value => { }); }; }; + +export const get_following = value => { + return () => { + return API.get_following(value) + .then(res => { + if (Array.isArray(res.results)) { + return { + following: res.results, + prevPage: res.previous, + nextPage: res.next, + loading: false, + }; + } else { + res = Object.keys(res) + .map(key => res[key]) + .join('\n'); + throw new Error(res); + } + }) + .catch(error => { + toast.warning(error.message); + return { loading: false }; + }); + }; +}; diff --git a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx index d090c93c9..75cbe3c5e 100644 --- a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx +++ b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx @@ -170,6 +170,48 @@ function PageWrapper(props) { + + + + Projects + + + + + + + Followers + + + + + + + Following + + + { - props.setFieldValue(e.currentTarget.name, refs.imageEl.current); +const handleImageFieldChange = (refs, props, state, handleSetState) => { refs.imageCountEl.current.innerText = refs.imageEl.current.files.length; refs.imageCountEl.current.style.fontSize = '0.8rem'; + + props.setFieldValue('project_images', refs.imageEl.current).then(errors => { + if (!errors['project_images']) { + removeMetaData(refs.imageEl.current.files, state, handleSetState); + } + }); +}; + +const removeMetaData = (images, state, handleSetState) => { + const newWorker = worker(); + newWorker.removeMetaData(images); + newWorker.addEventListener('message', e => { + Compress(e.data, state, handleSetState); + }); }; const handleImageButtonClick = (e, props, refs) => { @@ -117,7 +132,7 @@ function CreateProject(props) { useStateUpdateCallback(() => { if ( - state.image_upload.images_to_upload === + state.image_upload.images_to_upload.length === state.image_upload.successful_uploads ) { handleSetState(upload_project()); @@ -213,12 +228,33 @@ function CreateProject(props) { const { image_upload } = state; image_upload.upload_dialog = false; handleSetState({ image_upload }); - return props.create_project({ - ...props.values, - token: props.auth.token, - images: state.image_upload.uploaded_images_url, - video: props.values.video ? props.values.video : '', - }); + return props + .create_project({ + ...props.values, + token: props.auth.token, + images: state.image_upload.uploaded_images_url, + video: props.values.video ? props.values.video : '', + }) + .catch(error => { + const messages = JSON.parse(error.message); + if (typeof messages === 'object') { + let non_field_errors; + Object.keys(messages).forEach(key => { + if (key === 'non_field_errors') { + non_field_errors = { error: messages[key][0] }; + } else { + props.setFieldTouched(key, true, false); + props.setFieldError(key, messages[key][0]); + } + }); + if (non_field_errors) return non_field_errors; + } else { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } + }); }; const init_project = e => { @@ -231,35 +267,30 @@ function CreateProject(props) { props.setFieldTouched('project_images'); props.setFieldTouched('video'); props.setFieldTouched('materials_used'); - props.validateField('title'); - props.validateField('description'); - props.validateField('project_images'); - props.validateField('video'); - props.validateField('materials_used'); - - if ( - props.errors['title'] || - props.errors['description'] || - props.errors['project_images'] || - props.errors['video'] || - props.errors['materials_used'] - ) { - return; - } else if (refs.imageEl.current.files.length === 0) { - handleSetState(upload_project()); - } else { - const project_images = refs.imageEl.current.files; - const { image_upload } = state; - image_upload.images_to_upload = project_images.length; - image_upload.upload_dialog = true; - image_upload.upload_percent = 0; - handleSetState({ image_upload }); + image_field_touched = true; + video_field_touched = true; + + props.validateForm().then(errors => { + if (Object.keys(errors).length > 0) { + return; + } else if (refs.imageEl.current.files.length === 0) { + handleSetState(upload_project()); + } else { + const { image_upload } = state; + image_upload.upload_dialog = true; + image_upload.upload_percent = 0; + handleSetState({ image_upload }); - for (let index = 0; index < project_images.length; index++) { - upload(project_images[index]); + for ( + let index = 0; + index < image_upload.images_to_upload.length; + index++ + ) { + upload(image_upload.images_to_upload[index]); + } } - } + }); } }; @@ -378,6 +409,17 @@ function CreateProject(props) { labelWidth={90} /> + + Tell us something interesting about the project! You + can share what it is about, what inspired you to + make it, your making process, fun and challenging + moments you experienced, etc. + +
{props.touched['description'] && props.errors['description']}
@@ -425,7 +467,14 @@ function CreateProject(props) { id="project_images" name="project_images" multiple - onChange={e => handleImageFieldChange(e, refs, props)} + onChange={e => + handleImageFieldChange( + refs, + props, + state, + handleSetState, + ) + } onBlur={props.handleBlur} /> @@ -460,6 +509,14 @@ function CreateProject(props) { labelWidth={90} /> + + YouTube, Vimeo, Google Drive links are supported + +
{props.touched['video'] && props.errors['video']}
@@ -668,6 +725,19 @@ export default connect( : true; }, ) + .test('not_an_image', 'only images are allowed', value => { + if (value) { + let not_an_image = false; + for (let index = 0; index < value.files.length; index++) { + if (value.files[index].type.split('/')[0] !== 'image') { + not_an_image = true; + } + } + return not_an_image ? false : true; + } else { + return true; + } + }) .test('too_many_images', 'too many images uploaded', value => { if (value) { return value.files.length > 10 ? false : true; @@ -677,12 +747,12 @@ export default connect( }) .test( 'image_size_too_large', - 'one or more of your image is greater than 3mb', + 'one or more of your image is greater than 10mb', value => { if (value) { let image_size_too_large = false; for (let index = 0; index < value.files.length; index++) { - if (value.files[index].size / 1000 > 3072) { + if (value.files[index].size / 1000 > 10240) { image_size_too_large = true; } } diff --git a/zubhub_frontend/zubhub/src/views/email_confirm/EmailConfirm.jsx b/zubhub_frontend/zubhub/src/views/email_confirm/EmailConfirm.jsx index f5d22e46b..c67fd2e06 100644 --- a/zubhub_frontend/zubhub/src/views/email_confirm/EmailConfirm.jsx +++ b/zubhub_frontend/zubhub/src/views/email_confirm/EmailConfirm.jsx @@ -31,7 +31,16 @@ const getUsernameAndKey = queryString => { const confirmEmail = (e, props, state) => { e.preventDefault(); - return props.send_email_confirmation(props, state.key); + return props.send_email_confirmation(props, state.key).catch(error => { + if (error.message.startsWith('Unexpected')) { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } else { + return { error: error.message }; + } + }); }; function EmailConfirm(props) { diff --git a/zubhub_frontend/zubhub/src/views/login/Login.jsx b/zubhub_frontend/zubhub/src/views/login/Login.jsx index 9cc98a2de..5bb75a3ca 100644 --- a/zubhub_frontend/zubhub/src/views/login/Login.jsx +++ b/zubhub_frontend/zubhub/src/views/login/Login.jsx @@ -45,7 +45,26 @@ const handleMouseDownPassword = e => { const login = (e, props) => { e.preventDefault(); - return props.login(props); + return props.login(props).catch(error => { + const messages = JSON.parse(error.message); + if (typeof messages === 'object') { + let non_field_errors; + Object.keys(messages).forEach(key => { + if (key === 'non_field_errors') { + non_field_errors = { error: messages[key][0] }; + } else { + props.setFieldTouched(key, true, false); + props.setFieldError(key, messages[key][0]); + } + }); + return non_field_errors; + } else { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } + }); }; function Login(props) { diff --git a/zubhub_frontend/zubhub/src/views/password_reset/PasswordReset.jsx b/zubhub_frontend/zubhub/src/views/password_reset/PasswordReset.jsx index 5156b237f..cf1ff7ba3 100644 --- a/zubhub_frontend/zubhub/src/views/password_reset/PasswordReset.jsx +++ b/zubhub_frontend/zubhub/src/views/password_reset/PasswordReset.jsx @@ -30,7 +30,26 @@ const useStyles = makeStyles(styles); const sendPasswordResetLink = (e, props) => { e.preventDefault(); - return props.send_password_reset_link(props); + return props.send_password_reset_link(props).catch(error => { + const messages = JSON.parse(error.message); + let non_field_errors; + if (typeof messages === 'object') { + Object.keys(messages).forEach(key => { + if (key !== 'email') { + non_field_errors = { error: messages[key][0] }; + } else { + props.setFieldTouched(key, true, false); + props.setFieldError(key, messages[key][0]); + } + }); + return non_field_errors; + } else { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } + }); }; function PasswordReset(props) { diff --git a/zubhub_frontend/zubhub/src/views/password_reset_confirm/PasswordResetConfirm.jsx b/zubhub_frontend/zubhub/src/views/password_reset_confirm/PasswordResetConfirm.jsx index 1f510d032..b87775979 100644 --- a/zubhub_frontend/zubhub/src/views/password_reset_confirm/PasswordResetConfirm.jsx +++ b/zubhub_frontend/zubhub/src/views/password_reset_confirm/PasswordResetConfirm.jsx @@ -44,7 +44,28 @@ const getUidAndToken = queryString => { const resetPassword = (e, props) => { e.preventDefault(); const { uid, token } = getUidAndToken(props.location.search); - return props.password_reset_confirm({ ...props.values, uid, token }); + return props + .password_reset_confirm({ ...props.values, uid, token }) + .catch(error => { + const messages = JSON.parse(error.message); + if (typeof messages === 'object') { + let non_field_errors; + Object.keys(messages).forEach(key => { + if (key !== 'new_password1' && key !== 'new_password2') { + non_field_errors = { error: messages[key][0] }; + } else { + props.setFieldTouched(key, true, false); + props.setFieldError(key, messages[key][0]); + } + }); + return non_field_errors; + } else { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } + }); }; const handleClickShowPassword = (field, state) => { diff --git a/zubhub_frontend/zubhub/src/views/profile/Profile.jsx b/zubhub_frontend/zubhub/src/views/profile/Profile.jsx index bbfa64ad3..e74b91762 100644 --- a/zubhub_frontend/zubhub/src/views/profile/Profile.jsx +++ b/zubhub_frontend/zubhub/src/views/profile/Profile.jsx @@ -64,9 +64,16 @@ const updateProfile = (e, props, state, newUserNameEL) => { username: username.value, }) .then(res => { + if (!res.id) { + res = Object.keys(res) + .map(key => res[key]) + .join('\n'); + throw new Error(res); + } username.value = ''; return { ...res, ...handleToggleEditProfileModal(state) }; - }); + }) + .catch(error => ({ dialogError: error.message })); } else { return handleToggleEditProfileModal(state); } @@ -128,6 +135,7 @@ function Profile(props) { openEditProfileModal: false, loading: true, profile: {}, + dialogError: null, }); React.useEffect(() => { @@ -142,7 +150,13 @@ function Profile(props) { } }; - const { results: projects, profile, loading, openEditProfileModal } = state; + const { + results: projects, + profile, + loading, + openEditProfileModal, + dialogError, + } = state; if (loading) { return ; @@ -251,6 +265,17 @@ function Profile(props) { {profile.followers.length} Followers + + + {profile.following_count} Following + + @@ -327,6 +352,16 @@ function Profile(props) { aria-labelledby="edit user profile" > Edit User Profile + + {dialogError !== null && ( + + {dialogError} + + )} + {' '} { if (props.values.location.length < 1) { props.validateField('location'); } else { - return props.signup(props); + return props.signup(props).catch(error => { + const messages = JSON.parse(error.message); + if (typeof messages === 'object') { + let non_field_errors; + Object.keys(messages).forEach(key => { + if (key === 'non_field_errors') { + non_field_errors = { error: messages[key][0] }; + } else if (key === 'location') { + props.setFieldTouched('user_location', true, false); + props.setFieldError('user_location', messages[key][0]); + } else { + props.setFieldTouched(key, true, false); + props.setFieldError(key, messages[key][0]); + } + }); + return non_field_errors; + } else { + return { + error: + 'An error occured while performing this action. Please try again later', + }; + } + }); } }; @@ -228,7 +250,10 @@ function Signup(props) { Date Of Birth { + const username = props.match.params.username; + return props.get_following({ page, username }); +}; + +const toggle_follow = (e, props, state, id) => { + e.preventDefault(); + if (!props.auth.token) { + props.history.push('/login'); + } else { + return props + .toggle_follow({ id, token: props.auth.token }) + .then(res => { + if (res.profile && res.profile.username) { + // const following = state.following.map(creator => + // creator.id !== res.profile.id ? creator : res.profile, + // ); + const { following } = state; + following.forEach((creator, index) => { + if (creator.id === res.profile.id) { + following.splice(index, 1); + } + }); + return { following }; + } else { + res = Object.keys(res) + .map(key => res[key]) + .join('\n'); + throw new Error(res); + } + }) + .catch(error => { + if (error.message.startsWith('Unexpected')) { + toast.warning( + 'An error occured while performing this action. Please try again later', + ); + } else { + toast.warning(error.message); + } + return { loading: false }; + }); + } +}; + +const buildFollowing = (following, classes, props, state, handleSetState) => + following.map(creator => ( + + + + + {creator.id !== props.auth.id ? ( + + handleSetState(toggle_follow(e, props, state, id)) + } + primaryButtonStyle + > + {creator.followers.includes(props.auth.id) + ? 'Unfollow' + : 'Follow'} + + ) : null} + + {creator.username} + + + + + )); + +function UserFollowing(props) { + const classes = useStyles(); + + const [state, setState] = React.useState({ + following: [], + prevPage: null, + nextPage: null, + loading: true, + }); + + React.useEffect(() => { + handleSetState(fetchPage(null, props)); + }, []); + + const handleSetState = obj => { + if (obj) { + Promise.resolve(obj).then(obj => { + setState({ ...state, ...obj }); + }); + } + }; + + const { following, prevPage, nextPage, loading } = state; + const username = props.match.params.username; + if (loading) { + return ; + } else if (following && following.length > 0) { + return ( + + + + + + Creators {username} is following + + + {buildFollowing(following, classes, props, state, handleSetState)} + + + {prevPage ? ( + } + onClick={(e, page = prevPage.split('?')[1]) => { + handleSetState({ loading: true }); + handleSetState(fetchPage(page, props)); + }} + primaryButtonStyle + > + Prev + + ) : null} + {nextPage ? ( + } + onClick={(e, page = nextPage.split('?')[1]) => { + handleSetState({ loading: true }); + handleSetState(fetchPage(page, props)); + }} + primaryButtonStyle + > + Next + + ) : null} + + + + ); + } else { + return ; + } +} + +UserFollowing.propTypes = { + auth: PropTypes.object.isRequired, + toggle_follow: PropTypes.func.isRequired, + get_following: PropTypes.func.isRequired, +}; + +const mapStateToProps = state => { + return { + auth: state.auth, + }; +}; + +const mapDispatchToProps = dispatch => { + return { + toggle_follow: value => { + return dispatch(UserActions.toggle_follow(value)); + }, + get_following: value => { + return dispatch(UserActions.get_following(value)); + }, + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(UserFollowing);