├── .dockerignore ├── .editorconfig ├── .env.dev ├── .env.test ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── bug_report.md │ ├── enhancement.md │ ├── feature-request.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── image-workflow.yaml ├── .gitignore ├── .travis.yml ├── Dockerfile.dev ├── Dockerfile.prod ├── LICENSE ├── README.md ├── app.js ├── app ├── controllers │ ├── activity.js │ ├── analytics.js │ ├── auth.js │ ├── comment.js │ ├── email.js │ ├── event.js │ ├── notification.js │ ├── organization.js │ ├── post.js │ ├── project.js │ ├── proposal.js │ ├── ticket.js │ ├── urlShortner.js │ ├── user.js │ └── wikis.js ├── middleware │ ├── OAuthMiddlewares.js │ ├── activate.js │ ├── activity.js │ ├── auth.js │ ├── isOAuthAllowed.js │ ├── maintenance.js │ ├── passportOAuth.js │ ├── rateLimiter.js │ └── sanitise.js ├── models │ ├── Activity.js │ ├── Comment.js │ ├── Event.js │ ├── Notifications.js │ ├── Organisation.js │ ├── Post.js │ ├── Project.js │ ├── Proposal.js │ ├── ProposalNotification.js │ ├── Ticket.js │ ├── UrlShortner.js │ └── User.js ├── routes │ ├── activity.js │ ├── analytics.js │ ├── auth.js │ ├── comment.js │ ├── event.js │ ├── index.js │ ├── notification.js │ ├── organisation.js │ ├── post.js │ ├── project.js │ ├── proposal.js │ ├── ticket.js │ ├── urlShortner.js │ ├── user.js │ └── wikis.js └── utils │ ├── activity-helper.js │ ├── collections.js │ ├── compressAndUpload.js │ ├── console-helper.js │ ├── fileToBuffer.js │ ├── format-user.js │ ├── notif-helper.js │ ├── notificationTags.js │ ├── paginate.js │ ├── permission.js │ ├── proposal-notif-helper.js │ ├── response-helper.js │ ├── settingHelpers.js │ ├── ticket-helper.js │ ├── uploadToAWS.js │ └── wikis-helper.js ├── bin └── www ├── config ├── fileHandlingConstants.js ├── gAnalytics.js ├── mongoose.js ├── passport.js ├── redis.js └── winston.js ├── docker-compose.dev.yml ├── docker-compose.prod.yml ├── docs ├── ansible │ └── ansible.md ├── docs.md └── wikis │ └── wikis.md ├── infra ├── backend.yml ├── frontend.yml ├── group_vars │ └── all.yml ├── hosts ├── roles │ ├── setup_client │ │ ├── defaults │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── main.yml │ │ │ └── nodejs.yml │ │ └── templates │ │ │ └── env.j2 │ ├── setup_dbs │ │ ├── defaults │ │ │ └── main.yml │ │ ├── files │ │ │ └── mongodb.conf │ │ ├── handlers │ │ │ └── main.yml │ │ ├── tasks │ │ │ ├── create_org.yml │ │ │ ├── main.yml │ │ │ ├── mongo.yml │ │ │ └── redis.yml │ │ └── templates │ │ │ └── organization.j2 │ ├── setup_nginx │ │ ├── tasks │ │ │ └── main.yml │ │ └── templates │ │ │ ├── etc_config.j2 │ │ │ └── nginx_conf.j2 │ └── setup_server │ │ ├── defaults │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ └── templates │ │ └── ecosystem.config.j2 └── site.yml ├── package-lock.json ├── package.json ├── public └── stylesheets │ └── style.css ├── scripts └── install.sh ├── socket.js ├── test ├── comment.test.js ├── event.test.js ├── organisation.test.js ├── post.test.js ├── project.test.js ├── proposal.test.js ├── rateLimit.test.js ├── url.test.js └── user.test.js └── views ├── emailTemplate.ejs ├── error.ejs └── index.ejs /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .github 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | PORT=5000 2 | NODE_ENV="development" 3 | JWT_SECRET="thisismysupersecrettokenjustkidding" 4 | DATABASE_URL="mongodb://localhost:27017/donut-development" 5 | SENDGRID_API_KEY='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' 6 | SENDGRID_FROM_EMAIL_ADDRESS='services@codeuino.com' 7 | SOCKET_PORT=8810 8 | clientbaseurl="http://localhost:3000" 9 | SOCKET_PORT=8810 10 | REDIS_HOST="redis" 11 | REDIS_PORT=6379 12 | REDIS_PASSWORD="auth" 13 | REDIS_DB=0 14 | REDIS_ACTIVITY_DB=1 15 | GITHUB_OAUTH_APP_CLIENTID="a3e08516c35fe7e83f43" 16 | GITHUB_OAUTH_APP_CLIENTSECRET="8393d95c3bfeeb0943045f447a9d055cb660bce0" 17 | GOOGLE_OAUTH_CLIENT_ID="" 18 | GOOGLE_OAUTH_CLIENT_SECRET="" 19 | GOOGLE_OAUTH_CALLBACK="http://localhost:5000/user/auth/google/callback" 20 | GITHUB_OAUTH_CLIENT_ID="" 21 | GITHUB_OAUTH_CLIENT_SECRET="" 22 | GITHUB_OAUTH_CALLBACK="http://localhost:5000/user/auth/github/callback" 23 | AWS_ACCESS_KEY="" 24 | AWS_SECRET_KEY="" 25 | AWS_STORAGE_REGION="" 26 | AWS_BUCKET_NAME="" 27 | # session token only if needed! 28 | AWS_SESSION_TOKEN='' 29 | MAX_WINDOW_REQUEST_COUNT= 30 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | NODE_ENV=testing 3 | JWT_SECRET=thisismysupersecrettokenjustkidding 4 | DATABASE_URL=mongodb+srv://donut-admin:5cdS2C2g3wRAdQWp@donut-users-hdawt.mongodb.net/donut-testing?retryWrites=true&w=majority 5 | SENDGRID_API_KEY = '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' 6 | SOCKET_PORT = 8810 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "jest": true 7 | }, 8 | "extends": [ 9 | "standard" 10 | ], 11 | "globals": { 12 | "Atomics": "readonly", 13 | "SharedArrayBuffer": "readonly" 14 | }, 15 | "parserOptions": { 16 | "ecmaVersion": 2018 17 | }, 18 | "rules": { 19 | } 20 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: donut 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to Reproduce** 14 | Steps to reproduce the behaviour: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behaviour** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Console Log Output** 27 | If available, add console log output. 28 | 29 | **Desktop (please complete the following information):** 30 | - Browser [e.g. chrome, safari] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behaviour: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behaviour** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Console Log Output** 27 | If available, add console log output. 28 | 29 | **Desktop (please complete the following information):** 30 | - Browser [e.g. chrome, safari] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Describe the enhancement request. 4 | title: "[ENHANCEMENT]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the enhancement you'd like** 11 | A clear and concise description of what you want to enhance. 12 | 13 | **Describe approaches if you have thought of any** 14 | A clear and concise description of any approaches solutions you've considered. 15 | 16 | **Additional context** 17 | Add any resources that will be helpful for this issue. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Pull Request template 3 | about: Describe the PR. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### **Problem** 11 | Github Issue Number: # 12 | Describe the problem you are trying to achieve. Please provide in detail_ 13 | 14 | ### **Solution of problem** 15 | How did you solve this problem? 16 | 17 | ### **In Case of UI Changes** 18 | **Before Changes Screenshot** [How was it earlier, add it here] 19 | 20 | **After Changes Screenshot** [How it looks now, add it here] 21 | 22 | ### **Type of Change** 23 | [ ] Bug fix 24 | [ ] New Feature 25 | [ ] Development of UI/UX prototypes 26 | [ ] Small refactor 27 | [ ] Change in Documentation 28 | 29 | ### **Checklist** 30 | [ ] My code follows the same style as the codebase 31 | [ ] My Code change requires a change in documentation 32 | [ ] I have updated the Readme accordingly 33 | [ ] I made PR against **development branch** 34 | [ ] I have run the test cases locally and it's passing. 35 | [ ] I have squashed my commits 36 | -------------------------------------------------------------------------------- /.github/workflows/image-workflow.yaml: -------------------------------------------------------------------------------- 1 | name: donut-server-image-ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - development 7 | 8 | tags: 9 | - v* 10 | 11 | env: 12 | IMAGE_NAME: donut-server:latest 13 | REPO_NAME: codeuino1 14 | REGISTRY_NAME: registry.hub.docker.com 15 | 16 | jobs: 17 | push: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Build image 23 | run: docker build . --file Dockerfile.prod --tag $IMAGE_NAME 24 | 25 | - name: Log into registry 26 | run: echo "{{ secrets.DOCKER_PASSWORD }}" | docker login -u {{ secrets.DOCKER_USERNAME }} --password-stdin 27 | - name: Push image 28 | run: | 29 | IMAGE_ID=$REGISTRY_NAME/$REPO_NAME/$IMAGE_NAME 30 | 31 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 32 | 33 | echo IMAGE_ID=$IMAGE_ID 34 | echo VERSION=$VERSION 35 | 36 | docker tag $IMAGE_NAME $IMAGE_ID:$VERSION 37 | docker push $IMAGE_ID:$VERSION 38 | docker tag $IMAGE_NAME $IMAGE_ID:latest 39 | docker push $IMAGE_ID:$VERSION 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.prod 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # next.js build output 79 | .next 80 | 81 | # nuxt.js build output 82 | .nuxt 83 | 84 | # gatsby files 85 | .cache/ 86 | 87 | # vuepress build output 88 | .vuepress/dist 89 | 90 | # Serverless directories 91 | .serverless/ 92 | 93 | # FuseBox cache 94 | .fusebox/ 95 | 96 | # DynamoDB Local files 97 | .dynamodb/ 98 | 99 | .DS_Store 100 | 101 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8.11.3 -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | ENV NODE_ENV="development" 4 | 5 | # Copy package.json file into container 6 | COPY package.json package.json 7 | COPY package-lock.json package-lock.json 8 | 9 | # Install node modules 10 | RUN npm install && \ 11 | npm install --only=dev && \ 12 | npm cache clean --force --loglevel=error 13 | 14 | # Volume to mount source code into container 15 | VOLUME [ "/server" ] 16 | 17 | # move to the source code directory 18 | WORKDIR /server 19 | 20 | # Start the server 21 | CMD mv ../node_modules . && npm run dev 22 | -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | ENV NODE_ENV="production" 4 | 5 | WORKDIR /server 6 | 7 | RUN git clone https://github.com/codeuino/social-platform-donut-backend.git 8 | 9 | WORKDIR /server/social-platform-donut-backend 10 | 11 | RUN npm install && \ 12 | npm install pm2@latest -g && \ 13 | npm cache clean --force --loglevel=error 14 | 15 | # Start the server 16 | CMD [ "pm2", "start", "./bin/www", "--time", "--no-daemon" ] 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # social-platform-donut-backend 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | GitHub issues 15 | 16 | 17 | GitHub license 18 | 19 | 20 | CodeFactor 21 | 22 |

23 | 24 | 25 | ## Prerequisite: 26 | 27 | These are the requirement that should be installed locally on your machine. 28 | 29 | - Node.js 30 | - MongoDB 31 | - Redis 32 | 33 | 34 | ## How to setup node.js on your machine? 35 | 36 | - Move to: [link](https://nodejs.org/en/download/) choose the operating system as per your machine and start downloading and setup by clicking recommended settings in the installation wizard. 37 | 38 | ## How to setup MongoDB on your machine? 39 | 40 | - Move to: [link](https://docs.mongodb.com/manual/administration/install-community/) look at the left sidebar and choose the operating system according to your machine. Then follow the steps recommended in the official docs. 41 | 42 | ``` 43 | Note: You can also use the MongoDB servers like Mlab or MongoDB Cluster server 44 | ``` 45 | 46 | ## How to setup redis on your machine? 47 | 48 | - Follow the steps provided in the [link](https://auth0.com/blog/introduction-to-redis-install-cli-commands-and-data-types/) to install redis on your operating system 49 | 50 | ## How to set up this project locally? 51 | 52 | - Move to: https://github.com/codeuino/social-platform-donut-backend 53 | - Fork the repo 54 | - Clone the repo using: 55 | ```sh 56 | git clone https://github.com/codeuino/social-platform-donut-backend.git 57 | ``` 58 | - Now move to the project directory on your machine. 59 | ``` 60 | cd social-platform-donut-backend 61 | ``` 62 | - Now use ```git checkout development``` to move to the development branch. 63 | - Install all the dependencies using: 64 | ```sh 65 | npm install 66 | ``` 67 | - Run the development server using: 68 | ```sh 69 | npm run dev 70 | ``` 71 | - Now the server is running on PORT 5000 or the PORT mentioned in the environment **.env.dev** variables 72 | 73 | ``` 74 | Note: Setup the environment variables as mentioned below 75 | ``` 76 | 77 | 78 | ## Run unit test 79 | Use the given below command to run all the unit test cases. 80 | ``` 81 | npm run test 82 | ``` 83 | 84 | 85 | ## What are the environment variables required to run the project locally on a machine? 86 | - Follow these steps to set the environment variable for development: 87 | - Move to the root directory of the project and open **.env.dev** (for development) or **.env.test** (for testing) 88 | - PORT = 5000 89 | - NODE_ENV = "development" 90 | - JWT_SECRET="" 91 | - DATABASE_URL="" 92 | - SENDGRID_API_KEY = '' 93 | - SOCKET_PORT = 8810 94 | 95 | Note: To get **SENDGRID_API_KEY** follow the Sendgrid official [docs](https://sendgrid.com/docs/ui/account-and-settings/api-keys/#creating-an-api-key) 96 | 97 | ## Workflow (After setup) 98 | 99 | Must follow the steps to use the platform: 100 | 1. Create an organization - [API](https://docs.codeuino.org/donut-social-networking-platform/rest-apis/organization-api#create-an-organization) 101 | 2. Register as an admin - [API](https://docs.codeuino.org/donut-social-networking-platform/rest-apis/post-api#create-a-user) 102 | 3. Now login and use the features implemented - [API](https://docs.codeuino.org/donut-social-networking-platform/rest-apis/post-api#login-user) 103 | 4. To know more about features please go through the docs - [Docs](https://docs.codeuino.org/donut-social-networking-platform/rest-apis/post-api) 104 | 105 | ``` 106 | NOTE: Please make sure when you setup for the first time your database is empty. 107 | ``` 108 | 109 | 110 | ## Allowed HTTPs requests: 111 |
112 | POST    : To create resource 
113 | PATCH   : To Update resource
114 | GET     : Get a resource or list of resources
115 | DELETE  : To delete resource
116 | 
117 | 118 | ## Description Of Donut API Server Responses: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 |
CodeNameDetails
200OKthe request was successful.
201Createdthe request was successful and a resource was created.
204No Contentthe request was successful but there is no representation to return (i.e. the response is empty).
400Bad Requestthe request could not be understood or was missing required parameters.
401Unauthorizedauthentication failed or user doesn't have permissions for requested operation.
403Forbiddenaccess denied.
404Not Foundresource was not found.
405Method Not Allowedrequested method is not supported for resource.
409Conflictresourse with given id already exist.
429Too many requestssent too many requests to the server in short span of time
176 |

177 | 178 | ## Contributing 💻 179 | We are happy to see you here and we welcome your contributions towards Donut-Platform. 180 | Contributions are not limited to coding only, you can help in many other ways which includes leaving constructive feedback to people's Pull Request threads also. 181 | 182 | Donut platform also provides an extensive list of issues, some of them includes labels like good-first-issue, help-wanted. You can take a look at good-first-issue issues if you are new here but you are free to choose any issue you would like to work on. 183 | 184 | If there's no issue available currently, you can setup the project locally and find out the bugs/new features and open issues for that and discuss the bugs or features with the project maintainers or admins. 185 | 186 | After choosing an issue and doing changes in the code regarding that, you can open up a Pull Request (PR) to development branch to get your work reviewed and merged! -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('./config/mongoose') 2 | const express = require('express') 3 | const morgan = require('morgan') 4 | const cookieParser = require('cookie-parser') 5 | const createError = require('http-errors') 6 | const path = require('path') 7 | const socket = require('socket.io') 8 | const multer = require('multer') 9 | const bodyParser = require('body-parser') 10 | const cors = require('cors') 11 | const helmet = require('helmet') 12 | const hpp = require('hpp') 13 | var winston = require('./config/winston') 14 | const rateLimiter = require('./app/middleware/rateLimiter') 15 | const sanitizer = require('./app/middleware/sanitise') 16 | const fileConstants = require('./config/fileHandlingConstants') 17 | 18 | const indexRouter = require('./app/routes/index') 19 | const authRouter = require('./app/routes/auth') 20 | const usersRouter = require('./app/routes/user') 21 | const postRouter = require('./app/routes/post') 22 | const eventRouter = require('./app/routes/event') 23 | const shortUrlRouter = require('./app/routes/urlShortner') 24 | const organizationRouter = require('./app/routes/organisation') 25 | const commentRouter = require('./app/routes/comment') 26 | const projectRouter = require('./app/routes/project') 27 | const notificationRouter = require('./app/routes/notification') 28 | const proposalRouter = require('./app/routes/proposal') 29 | const analyticsRouter = require('./app/routes/analytics') 30 | const wikisRouter = require('./app/routes/wikis') 31 | const activityRouter = require('./app/routes/activity') 32 | const ticketRouter = require('./app/routes/ticket') 33 | const passport = require('passport') 34 | const app = express() 35 | const server = require('http').Server(app) 36 | const clientbaseurl = process.env.clientbaseurl || 'http://localhost:3000' 37 | const passportOAuth = require('./app/middleware/passportOAuth') 38 | 39 | app.use(cors({origin: clientbaseurl, credentials: true})) 40 | 41 | app.use(bodyParser.json({ limit: '200mb' })) 42 | app.use(cookieParser()) 43 | app.use(bodyParser.urlencoded(fileConstants.fileParameters)) 44 | 45 | // PassportJS for OAuth 46 | app.use(passport.initialize()) 47 | passportOAuth.initGoogleAuth() 48 | passportOAuth.initGitHubAuth() 49 | 50 | if (process.env.NODE_ENV !== 'testing') { 51 | server.listen(process.env.SOCKET_PORT || 8810) 52 | } 53 | // WARNING: app.listen(80) will NOT work here! 54 | 55 | const io = socket.listen(server) 56 | let count = 0 57 | io.on('connection', (socket) => { 58 | console.log('socket connected count ', count++) 59 | io.emit('user connected') 60 | }) 61 | 62 | // view engine setup 63 | app.set('views', path.join(__dirname, 'views')) 64 | app.set('view engine', 'ejs') 65 | 66 | morgan.token('data', (req, res) => { 67 | return JSON.stringify(req.body) 68 | }) 69 | 70 | app.use( 71 | morgan( 72 | ':remote-addr - :remote-user [:date[clf]] ":method :url" :status :res[content-length] ":referrer" ":user-agent" :data', 73 | { stream: winston.stream } 74 | ) 75 | ) 76 | 77 | app.use(express.json()) 78 | app.use(express.urlencoded({ extended: false })) 79 | app.use(cookieParser()) 80 | app.use(express.static(path.join(__dirname, 'public'))) 81 | app.use((req, res, next) => { 82 | req.io = io 83 | next() 84 | }) 85 | 86 | // TO PREVENT DOS ATTACK AND RATE LIMITER 87 | app.use(rateLimiter.customRateLimiter) 88 | 89 | // TO PREVENT XSS ATTACK 90 | app.use(sanitizer.cleanBody) 91 | app.use(helmet()) 92 | 93 | // TO PREVENT CLICK JACKING 94 | app.use((req, res, next) => { 95 | res.append('X-Frame-Options', 'Deny') 96 | res.set('Content-Security-Policy', "frame-ancestors 'none';") 97 | next() 98 | }) 99 | 100 | // TO PREVENT THE QUERY PARAMETER POLLUTION 101 | app.use(hpp()) 102 | 103 | app.use('/notification', notificationRouter) 104 | app.use('/', indexRouter) 105 | app.use('/auth', authRouter) 106 | app.use('/user', usersRouter) 107 | app.use('/post', postRouter) 108 | app.use('/org', organizationRouter) 109 | app.use('/event', eventRouter) 110 | app.use('/shortUrl', shortUrlRouter) 111 | app.use('/comment', commentRouter) 112 | app.use('/project', projectRouter) 113 | app.use('/proposal', proposalRouter) 114 | app.use('/analytics', analyticsRouter) 115 | app.use('/wikis', wikisRouter) 116 | app.use('/activity', activityRouter) 117 | app.use('/ticket', ticketRouter) 118 | 119 | // catch 404 and forward to error handler 120 | app.use(function (req, res, next) { 121 | next(createError(404, "route doesn't exist")) 122 | }) 123 | 124 | // error handler 125 | app.use(function (err, req, res, next) { 126 | // set locals, only providing error in development 127 | res.locals.message = err.message 128 | res.locals.error = req.app.get('env') === 'development' ? err : {} 129 | 130 | // To include winston logging (Error) 131 | winston.error( 132 | `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip} - ${req.body}` 133 | ) 134 | 135 | // render the error page 136 | res.status(err.status || 500) 137 | res.render('error') 138 | 139 | // Socket event error handler (On max event) 140 | req.io.on('error', function (err) { 141 | console.error('------REQ ERROR') 142 | console.error(err.stack) 143 | }) 144 | next() 145 | }) 146 | 147 | module.exports = { app, io } 148 | -------------------------------------------------------------------------------- /app/controllers/activity.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const Activity = require('../models/Activity') 3 | 4 | const HttpStatus = require('http-status-codes') 5 | module.exports = { 6 | 7 | getActivity: async (req, res, next) => { 8 | // userID whose activity will be fetched by admin 9 | const { id } = req.params 10 | 11 | try { 12 | // Check if user exists 13 | const user = await User.findById(req.user._id) 14 | if (!user) { 15 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) 16 | } 17 | 18 | // check if not admin 19 | if (user.isAdmin !== true) { 20 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' }) 21 | } 22 | 23 | const data = await Activity.findOne({ userId: id }) 24 | return res.status(HttpStatus.OK).json({ activity: data.activity }) 25 | } catch (error) { 26 | if (process.env.NODE_ENV !== 'production') { 27 | console.log(error.name, '-', error.message) 28 | } 29 | return res.status(HttpStatus.BAD_REQUEST).json({ error: error.message }) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/controllers/analytics.js: -------------------------------------------------------------------------------- 1 | const { google } = require('googleapis') 2 | const analytics = google.analytics('v3') 3 | const jwt = require('../../config/gAnalytics') 4 | const viewId = process.env.VIEW_ID 5 | const HANDLER = require('../utils/response-helper') 6 | const HttpStatus = require('http-status-codes') 7 | 8 | module.exports = { 9 | getBrowser: async (req, res, next) => { 10 | const { startDate, endDate, proposalId } = req.body 11 | console.log(req.body) 12 | try { 13 | const result = await analytics.data.ga.get({ 14 | auth: jwt, 15 | ids: `ga:${viewId}`, 16 | metrics: 'ga:users', 17 | dimensions: ['ga:browser'], 18 | 'start-date': startDate, 19 | 'end-date': endDate, 20 | filters: `ga:pagePath==/${proposalId}` 21 | }) 22 | res.status(HttpStatus.OK).json({ analytics: result.data.rows }) 23 | } catch (error) { 24 | HANDLER.handleError(res, error) 25 | } 26 | }, 27 | 28 | getCountries: async (req, res, next) => { 29 | const { startDate, endDate, proposalId } = req.body 30 | 31 | try { 32 | const result = await analytics.data.ga.get({ 33 | auth: jwt, 34 | ids: `ga:${viewId}`, 35 | metrics: 'ga:users', 36 | dimensions: ['ga:country'], 37 | 'start-date': startDate, 38 | 'end-date': endDate, 39 | filters: `ga:pagePath==/${proposalId}` 40 | }) 41 | res.status(HttpStatus.OK).json({ analytics: result.data.rows }) 42 | } catch (error) { 43 | HANDLER.handleError(res, error) 44 | } 45 | }, 46 | 47 | getDevice: async (req, res, next) => { 48 | const { startDate, endDate, proposalId } = req.body 49 | 50 | try { 51 | const result = await analytics.data.ga.get({ 52 | auth: jwt, 53 | ids: `ga:${viewId}`, 54 | metrics: 'ga:users', 55 | dimensions: ['ga:deviceCategory'], 56 | 'start-date': startDate, 57 | 'end-date': endDate, 58 | filters: `ga:pagePath==/${proposalId}` 59 | }) 60 | res.status(HttpStatus.OK).json({ analytics: result.data.rows }) 61 | } catch (error) { 62 | HANDLER.handleError(res, error) 63 | } 64 | }, 65 | 66 | getTopProposals: async (req, res, next) => { 67 | const { startDate, endDate } = req.body 68 | 69 | try { 70 | const result = await analytics.data.ga.get({ 71 | auth: jwt, 72 | ids: `ga:${viewId}`, 73 | metrics: 'ga:pageviews', 74 | dimensions: ['ga:pagePath'], 75 | 'start-date': startDate, 76 | 'end-date': endDate, 77 | filters: 'ga:pagePath!=/homepage' 78 | }) 79 | res.status(HttpStatus.OK).json({ analytics: result.data }) 80 | } catch (error) { 81 | HANDLER.handleError(res, error) 82 | } 83 | }, 84 | 85 | getProposalViews: async (req, res, next) => { 86 | const { startDate, endDate, proposalId } = req.body 87 | 88 | try { 89 | const result = await analytics.data.ga.get({ 90 | auth: jwt, 91 | ids: `ga:${viewId}`, 92 | metrics: 'ga:pageviews', 93 | dimensions: ['ga:date'], 94 | 'start-date': startDate, 95 | 'end-date': endDate, 96 | filters: `ga:pagePath==/${proposalId}` 97 | }) 98 | 99 | res.status(HttpStatus.OK).json({ analytics: result.data.rows }) 100 | } catch (error) { 101 | HANDLER.handleError(res, error) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/controllers/auth.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const HttpStatus = require('http-status-codes') 3 | const activityHelper = require('../utils/activity-helper') 4 | 5 | module.exports = { 6 | authenticateUser: async (req, res, next) => { 7 | const email = escape(req.body.email) 8 | const password = escape(req.body.password) 9 | try { 10 | const user = await User.findByCredentials(email, password) 11 | const token = await user.generateAuthToken() 12 | res.cookie('token', token, { httpOnly: true }).send({ user: user }) 13 | } catch (error) { 14 | res.status(HttpStatus.BAD_REQUEST).json({ error: error.message }) 15 | } 16 | }, 17 | logout: (req, res, next) => { 18 | activityHelper.addActivityToDb(req, res) 19 | res.clearCookie('token') 20 | res.status(HttpStatus.OK).json({ success: 'ok' }) 21 | }, 22 | logoutAll: (req, res, next) => { 23 | activityHelper.addActivityToDb(req, res) 24 | res.status(HttpStatus.OK).json({ success: 'ok' }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/controllers/comment.js: -------------------------------------------------------------------------------- 1 | const HANDLER = require('../utils/response-helper') 2 | const HttpStatus = require('http-status-codes') 3 | const CommentModel = require('../models/Comment') 4 | const permission = require('../utils/permission') 5 | const helper = require('../utils/paginate') 6 | const activityTracker = require('../utils/activity-helper') 7 | const collectionTypes = require('../utils/collections') 8 | 9 | module.exports = { 10 | // CREATE COMMENT (ISSUE IN CREATE COMMENT ) 11 | comment: async (req, res, next) => { 12 | const { id } = req.params 13 | const userId = req.user.id.toString() 14 | try { 15 | const comment = new CommentModel(req.body) 16 | comment.userId = userId 17 | comment.postId = id // added postId 18 | await comment.save() 19 | activityTracker.addToRedis(req, res, next, collectionTypes.COMMENT, comment._id) 20 | return res.status(HttpStatus.CREATED).json({ comment: comment }) 21 | } catch (error) { 22 | HANDLER.handleError(res, error) 23 | } 24 | }, 25 | 26 | // DELETE COMMENT 27 | delete: async (req, res, next) => { 28 | const { id } = req.params 29 | try { 30 | const comment = await CommentModel.findById(id) 31 | if (!comment) { 32 | return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) 33 | } 34 | // Add rights for admins and moderators as well (TODO) 35 | if (! await permission.check(req, res, comment.userId)) { 36 | return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) 37 | } 38 | await CommentModel.findByIdAndRemove(id) 39 | res.status(HttpStatus.OK).json({ comment: comment }) 40 | } catch (error) { 41 | HANDLER.handleError(res, error) 42 | } 43 | }, 44 | 45 | // UPDATE COMMENT 46 | update: async (req, res, next) => { 47 | const { id } = req.params 48 | const updates = Object.keys(req.body) 49 | const valid = ['content'] 50 | const isValidOperation = updates.every((update) => { 51 | return valid.includes(update) 52 | }) 53 | if (!isValidOperation) { 54 | return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong Update Request' }) 55 | } 56 | try { 57 | const comment = await CommentModel.findById(id) 58 | if (!comment) { 59 | return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) 60 | } 61 | // also add admin or moderator control (TODO) 62 | if (! await permission.check(req, res, comment.userId)) { 63 | return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong update' }) 64 | } 65 | updates.forEach(update => { 66 | comment[update] = req.body[update] 67 | }) 68 | await comment.save() 69 | activityTracker.addToRedis(req, res, next, collectionTypes.COMMENT, comment._id) 70 | return res.status(HttpStatus.OK).json({ comment: comment }) 71 | } catch (error) { 72 | HANDLER.handleError(res, error) 73 | } 74 | }, 75 | 76 | // GET ALL COMMENTS OF A POST BY postId 77 | getCommentByPost: async (req, res, next) => { 78 | const { id } = req.params 79 | try { 80 | const comments = await CommentModel.find({ postId: id }, {}, helper.paginate(req)) 81 | .populate('userId', ['name.firstName', 'name.lastName']) 82 | .sort({ updatedAt: -1 }) 83 | .lean() 84 | .exec() 85 | if (comments === undefined || comments.length === 0) { 86 | return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such post' }) 87 | } 88 | return res.status(HttpStatus.OK).json({ comments: comments }) 89 | } catch (error) { 90 | HANDLER.handleError(res, error) 91 | } 92 | }, 93 | 94 | // UPVOTE COMMENT 95 | upvote: async (req, res, next) => { 96 | const { id } = req.params 97 | const userId = req.user.id.toString() 98 | try { 99 | const comment = await CommentModel.findById(id) 100 | if (!comment) { 101 | return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment found' }) 102 | } 103 | // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT 104 | comment.votes.upVotes.user.filter(user => { 105 | if (JSON.stringify(user) === JSON.stringify(userId)) { 106 | return res.status(HttpStatus.BAD_REQUEST).json({ 107 | error: 'Bad request' 108 | }) 109 | } 110 | }) 111 | // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT 112 | comment.votes.downVotes.user.filter(user => { 113 | if (JSON.stringify(user) === JSON.stringify(userId)) { 114 | comment.votes.downVotes.user.remove(user) 115 | } 116 | }) 117 | comment.votes.upVotes.user.unshift(userId) 118 | await comment.save() 119 | return res.status(HttpStatus.OK).json({ comment: comment }) 120 | } catch (error) { 121 | HANDLER.handleError(res, error) 122 | } 123 | }, 124 | 125 | // DOWNVOTE COMMENT 126 | downvote: async (req, res, next) => { 127 | const { id } = req.params 128 | const userId = req.user.id.toString() 129 | try { 130 | const comment = await CommentModel.findById(id) 131 | if (!comment) { 132 | return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment found' }) 133 | } 134 | // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT 135 | comment.votes.downVotes.user.filter(user => { 136 | if (JSON.stringify(user) === JSON.stringify(userId)) { 137 | return res.status(HttpStatus.BAD_REQUEST).json({ 138 | error: 'Bad request' 139 | }) 140 | } 141 | }) 142 | // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT 143 | comment.votes.upVotes.user.filter(user => { 144 | if (JSON.stringify(user) === JSON.stringify(userId)) { 145 | comment.votes.upVotes.user.remove(user) 146 | } 147 | }) 148 | comment.votes.downVotes.user.unshift(userId) 149 | await comment.save() 150 | return res.status(HttpStatus.OK).json({ comment: comment }) 151 | } catch (error) { 152 | HANDLER.handleError(res, error) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /app/controllers/email.js: -------------------------------------------------------------------------------- 1 | const sendgridMail = require('@sendgrid/mail') 2 | const ejs = require('ejs') 3 | const path = require('path') 4 | const sendGridApi = process.env.SENDGRID_API_KEY || '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' 5 | const SENDGRID_FROM_EMAIL_ADDRESS = process.env.SENDGRID_FROM_EMAIL_ADDRESS || 'services@codeuino.com' 6 | sendgridMail.setApiKey(sendGridApi) 7 | 8 | module.exports = { 9 | sendEmail: async (req, res, next, token) => { 10 | const filePath = path.join(__dirname, '/../../views/emailTemplate.ejs') 11 | ejs.renderFile(filePath, { token: token }, (err, data) => { 12 | if (err) { 13 | console.log('Error in renderFile ', err) 14 | } else { 15 | const message = { 16 | to: req.body.email, 17 | from: SENDGRID_FROM_EMAIL_ADDRESS, 18 | subject: `Welcome to Donut ${req.body.name.firstName}`, 19 | html: data 20 | } 21 | sendgridMail.send(message).then( 22 | () => { 23 | console.log('sending email') 24 | }, 25 | (error) => { 26 | console.log('error in sending email ', error) 27 | if (error.response) { 28 | console.error(error.response.body) 29 | } 30 | } 31 | ) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/controllers/notification.js: -------------------------------------------------------------------------------- 1 | const HANDLER = require('../utils/response-helper') 2 | const HttpStatus = require('http-status-codes') 3 | const Notifications = require('../models/Notifications') 4 | const helper = require('../utils/paginate') 5 | const User = require('../models/User') 6 | const ProposalNotifications = require('../models/ProposalNotification') 7 | 8 | module.exports = { 9 | // GET ALL THE NOTIFICATIONS FOR ALL 10 | getOrgNotifications: async (req, res, next) => { 11 | try { 12 | const notifications = await Notifications.find( 13 | {}, 14 | {}, 15 | helper.paginate(req) 16 | ) 17 | .lean() 18 | .sort({ createdAt: -1 }) 19 | .exec() 20 | return res.status(HttpStatus.OK).json({ notifications }) 21 | } catch (error) { 22 | HANDLER.handleError(res, error) 23 | } 24 | }, 25 | // GET LOGGED IN USER NOTIFICATIONS 26 | getUserNotification: async (req, res, next) => { 27 | const userId = req.user._id 28 | try { 29 | const user = await User.findById(userId) 30 | if (!user) { 31 | return res 32 | .status(HttpStatus.BAD_REQUEST) 33 | .json({ msg: 'No such user exists!' }) 34 | } 35 | // get all notifications of existing user 36 | const notifications = user.notifications 37 | if (notifications.length === 0) { 38 | return res.status(HttpStatus.OK).json({ msg: 'No new notifications!' }) 39 | } 40 | return res.status(HttpStatus.OK).json({ notifications }) 41 | } catch (error) { 42 | HANDLER.handleError(res, error) 43 | } 44 | }, 45 | 46 | getProposalNotifications: async (req, res, next) => { 47 | try { 48 | const notifications = await ProposalNotifications.find({}) 49 | return res.status(HttpStatus.OK).json({ notifications }) 50 | } catch (error) { 51 | HANDLER.handleError(res, error) 52 | } 53 | }, 54 | 55 | // GET LOGGED IN USER TICKET NOTIFICATIONS 56 | getTicketNotifications: async (req, res, next) => { 57 | const userId = req.user._id 58 | try { 59 | const user = await User.findById(userId) 60 | if (!user) { 61 | return res 62 | .status(HttpStatus.BAD_REQUEST) 63 | .json({ msg: 'No such user exists!' }) 64 | } 65 | // get ticket notifications of existing user 66 | const notifications = user.ticketNotifications 67 | if (notifications.length === 0) { 68 | return res.status(HttpStatus.OK).json({ msg: 'No ticket notifications!' }) 69 | } 70 | return res.status(HttpStatus.OK).json({ notifications }) 71 | } catch (error) { 72 | HANDLER.handleError(res, error) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/controllers/project.js: -------------------------------------------------------------------------------- 1 | const Project = require('../models/Project') 2 | const HANDLER = require('../utils/response-helper') 3 | const HttpStatus = require('http-status-codes') 4 | const helper = require('../utils/paginate') 5 | const permission = require('../utils/permission') 6 | const settingsHelper = require('../utils/settingHelpers') 7 | const activityTracker = require('../utils/activity-helper') 8 | const collectionTypes = require('../utils/collections') 9 | 10 | module.exports = { 11 | createProject: async (req, res, next) => { 12 | try { 13 | const project = await new Project(req.body) 14 | project.createdBy = req.user._id 15 | await project.save() 16 | activityTracker.addToRedis(req, res, next, collectionTypes.PROJECT, project._id) 17 | return res.status(HttpStatus.CREATED).json({ project }) 18 | } catch (error) { 19 | HANDLER.handleError(res, error) 20 | } 21 | }, 22 | getAllProjects: async (req, res, next) => { 23 | try { 24 | const projects = await Project.find({}, {}, helper.paginate(req)) 25 | .populate('createdBy', '_id name.firstName name.lastName email') 26 | .sort({ updatedAt: -1 }) 27 | .exec() 28 | return res.status(HttpStatus.OK).json({ projects }) 29 | } catch (error) { 30 | HANDLER.handleError(res, error) 31 | } 32 | }, 33 | getProjectById: async (req, res, next) => { 34 | const { id } = req.params 35 | try { 36 | const project = await Project.findById(id) 37 | .populate('createdBy', '_id name.firstName name.lastName email') 38 | .lean() 39 | .exec() 40 | if (!project) { 41 | return res.status(HttpStatus.OK).json({ msg: 'Post doesn\'t exists!' }) 42 | } 43 | return res.status(HttpStatus.OK).json({ project }) 44 | } catch (error) { 45 | HANDLER.handleError(res, error) 46 | } 47 | }, 48 | updateProject: async (req, res, next) => { 49 | const { id } = req.params 50 | const updates = Object.keys(req.body) 51 | const allowedUpdates = [ 52 | 'projectName', 53 | 'description', 54 | 'imgUrl', 55 | 'img', 56 | 'version', 57 | 'links', 58 | 'contributors', 59 | 'maintainers' 60 | ] 61 | const isValidOperation = updates.every((update) => { 62 | return allowedUpdates.includes(update) 63 | }) 64 | try { 65 | const project = await Project.findById(id) 66 | .populate('createdBy', '_id name.firstName name.lastName email') 67 | .exec() 68 | if (!project) { 69 | return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such project exits!' }) 70 | } 71 | // permission check for admin and creator || is edit allowed 72 | if (! await permission.check(req, res, project.createdBy) || (!settingsHelper.canEdit())) { 73 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Bad Update Request!' }) 74 | } 75 | // if allowed check edit limit 76 | if (!settingsHelper.isEditAllowedNow(project.createdAt)) { 77 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Edit limit expired!' }) 78 | } 79 | // check if valid edit 80 | if (!isValidOperation) { 81 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid update!' }) 82 | } 83 | updates.forEach((update) => { 84 | project[update] = req.body[update] 85 | }) 86 | await project.save() 87 | return res.status(HttpStatus.OK).json({ project }) 88 | } catch (error) { 89 | HANDLER.handleError(res, error) 90 | } 91 | }, 92 | deleteProject: async (req, res, next) => { 93 | const { id } = req.params 94 | try { 95 | const project = await Project.findById(id) 96 | if (!project) { 97 | return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such project exits!' }) 98 | } 99 | // check if admin or user who created this project 100 | if (permission.check(req, res, project.createdBy)) { 101 | await Project.findByIdAndRemove(id) 102 | return res.status(HttpStatus.OK).json({ msg: 'Project deleted!' }) 103 | } 104 | activityTracker.addToRedis(req, res, next, collectionTypes.PROJECT, project._id) 105 | return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Not permitted!' }) 106 | } catch (error) { 107 | HANDLER.handleError(res, error) 108 | } 109 | }, 110 | projectCreatedByUser: async (req, res, next) => { 111 | try { 112 | const { id } = req.params 113 | const projects = await Project.find({ createdBy: id }, {}, helper.paginate(req)) 114 | .populate('createdBy', '_id name.firstName name.lastName email') 115 | .sort({ updatedAt: -1 }) 116 | .exec() 117 | return res.status(HttpStatus.OK).json({ projects }) 118 | } catch (error) { 119 | HANDLER.handleError(res, error) 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/controllers/urlShortner.js: -------------------------------------------------------------------------------- 1 | const UrlModel = require('../models/UrlShortner') 2 | const HttpStatus = require('http-status-codes') 3 | const validator = require('validator') 4 | 5 | module.exports = { 6 | redirect: async (req, res) => { 7 | try { 8 | const { urlcode } = req.params 9 | const url = await UrlModel.findOne({ urlCode: urlcode }) 10 | if (url) { 11 | return res.status(HttpStatus.OK).redirect(url.longUrl) 12 | } else { 13 | return res.status(HttpStatus.NOT_FOUND).json('No url found!') 14 | } 15 | } catch (error) { 16 | res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error!') 17 | } 18 | }, 19 | 20 | shorten: async (req, res) => { 21 | var { longUrl } = req.body 22 | var baseurl = req.get('host') 23 | var urlCode = Date.now() 24 | if (validator.isURL(longUrl)) { 25 | try { 26 | var url = await UrlModel.findOne({ longUrl }) 27 | if (url) { 28 | return res.status(HttpStatus.OK).json(url) 29 | } 30 | var shortUrl = baseurl + '/' + urlCode 31 | url = new UrlModel({ 32 | longUrl: longUrl, 33 | shortUrl: shortUrl, 34 | urlCode: urlCode 35 | }) 36 | await url.save() 37 | res.status(HttpStatus.CREATED).json(url) 38 | } catch (error) { 39 | console.log(error) 40 | return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error') 41 | } 42 | } else { 43 | res.status(HttpStatus.NOT_FOUND).json('invalid long url') 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/controllers/wikis.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const HttpStatus = require('http-status-codes') 3 | const WikiHelper = require('../utils/wikis-helper') 4 | const HANDLER = require('../utils/response-helper') 5 | const Organization = require('../models/Organisation') 6 | const { changeFileOnRemote, addPageToIndex, fetchPagesIndex, updatePagesIndex, getOpts, getOrgId } = WikiHelper 7 | 8 | const clientId = process.env.GITHUB_OAUTH_APP_CLIENTID 9 | const clientSecret = process.env.GITHUB_OAUTH_APP_CLIENTSECRET 10 | 11 | const githubAPI = 'https://api.github.com' 12 | let accessToken = null 13 | let orgId = null 14 | 15 | module.exports = { 16 | 17 | getWikis: async (req, res, next) => { 18 | try { 19 | if (!accessToken || !orgId) { 20 | const Org = await Organization.find({}).lean().exec() 21 | if (Org[0].wikis.accessToken && Org[0].wikis.orgId) { 22 | accessToken = Org[0].wikis.accessToken 23 | orgId = Org[0].wikis.orgId 24 | WikiHelper.setOrgId(orgId) 25 | WikiHelper.setOpts(accessToken) 26 | } 27 | } 28 | if (!accessToken || !orgId) { 29 | res.status(HttpStatus.OK).json({ wikis: 'NO_ACCESS_TOKEN' }) 30 | } else { 31 | const data = await addPageToIndex(await fetchPagesIndex(), 'Home') 32 | res.status(HttpStatus.OK).json({ wikis: data }) 33 | } 34 | } catch (error) { 35 | HANDLER.handleError(res, error) 36 | } 37 | }, 38 | 39 | getPage: async (req, res, next) => { 40 | try { 41 | let { title, ref } = req.query 42 | if (!ref) { 43 | ref = 'master' 44 | } 45 | const data = await addPageToIndex(await fetchPagesIndex(), title, ref) 46 | res.status(HttpStatus.OK).json({ wikis: data }) 47 | } catch (err) { 48 | res.status(HttpStatus.BAD_REQUEST).json({ Error: err.message }) 49 | } 50 | }, 51 | 52 | editPage: async (req, res, next) => { 53 | const { title, content, comments } = req.body 54 | try { 55 | await changeFileOnRemote(title, content, `${title} changes - ${comments}`) 56 | if (title !== '_Sidebar') { 57 | const data = await addPageToIndex(await fetchPagesIndex(), title) 58 | res.status(HttpStatus.OK).json({ wikis: data }) 59 | } else { 60 | await updatePagesIndex() 61 | const data = await addPageToIndex(await fetchPagesIndex(), 'Home') 62 | res.status(HttpStatus.OK).json({ wikis: data }) 63 | } 64 | } catch (err) { 65 | res.status(HttpStatus.BAD_REQUEST).json({ Error: err.message }) 66 | } 67 | }, 68 | 69 | deletePage: async (req, res, next) => { 70 | const { title } = req.body 71 | try { 72 | const baseUrl = `${githubAPI}/repos/${getOrgId()}/Donut-wikis-backup` 73 | const data = { 74 | message: `${title} deleted`, 75 | sha: (await axios.get(`${baseUrl}/contents/${title}.md`, getOpts())).data.sha 76 | } 77 | const deleteCommit = (await axios.delete(`${baseUrl}/contents/${title}.md`, { 78 | data: data, 79 | headers: getOpts().headers 80 | })).data.commit.sha 81 | const issueNumber = await WikiHelper.getFileIssueNumber(title) 82 | await axios.post(`${baseUrl}/issues/${issueNumber}/comments`, { body: deleteCommit }, getOpts()) 83 | const newIssueTitle = `${title}-deleted-${deleteCommit.substring(0, 8)}` 84 | await axios.patch(`${baseUrl}/issues/${issueNumber}`, { title: newIssueTitle }, getOpts()) 85 | await updatePagesIndex() 86 | await WikiHelper.clearPageFromCache(title) 87 | res.status(HttpStatus.OK).json({ wikis: await addPageToIndex(await fetchPagesIndex(), 'Home') }) 88 | } catch (err) { 89 | res.status(HttpStatus.BAD_REQUEST).json({ Error: err.message }) 90 | } 91 | }, 92 | 93 | newPage: async (req, res, next) => { 94 | const { title, content, comments } = req.body 95 | try { 96 | await changeFileOnRemote(title, content, `${title} initial commit - ${comments}`, true) 97 | await updatePagesIndex() 98 | const data = await addPageToIndex(await fetchPagesIndex(), title) 99 | res.status(HttpStatus.OK).json({ wikis: data }) 100 | } catch (err) { 101 | res.status(HttpStatus.BAD_REQUEST).json({ Error: err.message }) 102 | } 103 | }, 104 | 105 | oauthCheck: async (req, res, next) => { 106 | if (!accessToken) { 107 | console.log('redirected to github auth') 108 | res.status(HttpStatus.OK).json({ 109 | redirect: true, 110 | redirect_url: `https://github.com/login/oauth/authorize?client_id=${clientId}&scope=repo` 111 | }) 112 | } else { 113 | res.status(HttpStatus.OK).json({ 114 | redirect: false 115 | }) 116 | } 117 | }, 118 | 119 | oauthCallback: async (req, res, next) => { 120 | const body = { 121 | client_id: clientId, 122 | client_secret: clientSecret, 123 | code: req.query.code 124 | } 125 | const opts = { headers: { accept: 'application/json' } } 126 | try { 127 | const resp = await axios.post('https://github.com/login/oauth/access_token', body, opts) 128 | accessToken = resp.data.access_token 129 | WikiHelper.setOpts(accessToken) 130 | const Org = await Organization.find({}).exec() 131 | orgId = await WikiHelper.getOrg() 132 | Org[0].wikis.accessToken = accessToken 133 | Org[0].wikis.orgId = orgId 134 | await Org[0].save() 135 | await WikiHelper.createRepo() 136 | await updatePagesIndex() 137 | res.redirect(`${process.env.clientbaseurl}/wikis`) 138 | } catch (err) { 139 | res.status(500).json({ message: err.message }) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/middleware/OAuthMiddlewares.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport') 2 | const HttpStatus = require('http-status-codes') 3 | const afterAuthRedirect = (process.env.clientbaseurl + '/login') || 'http://localhost:3000/login' 4 | 5 | const passportGoogleAuthenticate = async (req, res, next) => { 6 | passport.authenticate('google', { scope: ['profile','email'], session: false })(req, res, next) 7 | } 8 | const passportGoogleAuthenticateCallback = async (req, res, next) => { 9 | passport.authenticate('google', (err, details) => { 10 | if(err) { 11 | return res.status(HttpStatus.UNAUTHORIZED).json({ 12 | msg: 'Something went wrong while authenticating!' 13 | }) 14 | } 15 | if(details.token===undefined || !details.token) { 16 | res.redirect(afterAuthRedirect) 17 | }else { 18 | res.cookie('token', details.token, { httpOnly: true }).redirect(afterAuthRedirect) 19 | } 20 | })(req, res, next) 21 | } 22 | 23 | const passportGitHubAuthenticate = async (req, res, next) => { 24 | passport.authenticate('github', {scope: ['user:email'] , session: false})(req, res, next) 25 | } 26 | const passportGitHubAuthenticateCallback = async (req, res, next) => { 27 | passport.authenticate('github', (err, details) => { 28 | if(err) { 29 | console.log(err) 30 | return res.status(HttpStatus.UNAUTHORIZED).json({ 31 | msg: 'Something went wrong while authenticating!' 32 | }) 33 | } 34 | if(details.token===undefined || !details.token) { 35 | res.redirect(afterAuthRedirect) 36 | }else { 37 | res.cookie('token', details.token, { httpOnly: true }).redirect(afterAuthRedirect) 38 | } 39 | })(req, res, next) 40 | } 41 | 42 | module.exports = {passportGoogleAuthenticate, passportGoogleAuthenticateCallback, passportGitHubAuthenticate, passportGitHubAuthenticateCallback} 43 | -------------------------------------------------------------------------------- /app/middleware/activate.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const HttpStatus = require('http-status-codes') 3 | 4 | const isActivated = async (req, res, next) => { 5 | const { email } = req.body 6 | try { 7 | const user = await User.findOne({ email: email }) 8 | if (!user) { 9 | next(new Error('No such user is found!')) 10 | } 11 | if (user && !user.isActivated) { 12 | next(new Error('Please activate the account!')) 13 | } 14 | } catch (Error) { 15 | return res.status(HttpStatus.BAD_REQUEST).json({ Error }) 16 | } 17 | } 18 | 19 | module.exports = isActivated 20 | -------------------------------------------------------------------------------- /app/middleware/activity.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const redis = require('../../config/redis') 3 | 4 | const activity = async (req, res, next) => { 5 | var redisClient = redis.redisClient 6 | var route = req.originalUrl.replace(/\?.*$/, '') 7 | var method = req.method 8 | var userID = req.user.id.toString() 9 | console.log('req.body ', req.body) 10 | console.log('res.locals.data ', res.locals.data) 11 | console.log('route ', route) 12 | console.log('methods ', method) 13 | 14 | if (route === '/user/logout') { 15 | var activityData = await redisClient.lrange(userID, 0, -1) 16 | const data = await User.findOne({ 17 | _id: userID 18 | }) 19 | var activityElement = { 20 | route: '', 21 | method: '', 22 | collectionType: '', 23 | id: '', 24 | timestamp: '' 25 | } 26 | for (let index = 0; index < activityData.length; index++) { 27 | var activityDataElement = activityData[index].split(',') 28 | activityElement.route = activityDataElement[0] 29 | activityElement.method = activityDataElement[1] 30 | activityElement.collectionType = activityDataElement[2] 31 | activityElement.id = activityDataElement[3] 32 | activityElement.timestamp = activityDataElement[4] 33 | data.activity.unshift(activityElement) 34 | } 35 | await data.update() 36 | console.log('DATA') 37 | console.log(data) 38 | // clear data from redis 39 | await redisClient.del(userID) 40 | } else if (method !== 'GET') { 41 | var objectID = res.locals.data._id 42 | userID = objectID 43 | var timeStamp = Date() 44 | var collectionType = res.locals.collectionType 45 | if (typeof res.locals.data.userId !== 'undefined') { 46 | userID = res.locals.data.userId 47 | } 48 | // example /auth/login,POST,user,5ed09e9d446f2b1c208b6ba8,Thu Jul 23 2020 20:28:29 GMT+0530 (India Standard Time) 49 | activityElement = route.concat(',', method, ',', collectionType, ',', objectID, ',', timeStamp) 50 | // userID => [(route, collection, method, objectID), (route,method, collection, objectID) ...] 51 | await redisClient.rpush(userID, activityElement) 52 | next() 53 | } 54 | } 55 | 56 | module.exports = activity 57 | -------------------------------------------------------------------------------- /app/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const User = require('../models/User') 3 | const HttpStatus = require('http-status-codes') 4 | 5 | const auth = async (req, res, next) => { 6 | try { 7 | const token = req.cookies.token || '' 8 | if(!token) { 9 | throw Error('unauthorized access') 10 | } 11 | const decoded = jwt.verify(token, process.env.JWT_SECRET) 12 | const user = await User.findOne({ 13 | _id: decoded._id, 14 | 'tokens.token': token 15 | }) 16 | .populate('followings', [ 17 | 'name.firstName', 18 | 'name.lastName', 19 | 'info.about.designation', 20 | '_id', 21 | 'isAdmin' 22 | ]) 23 | .populate('followers', [ 24 | 'name.firstName', 25 | 'name.lastName', 26 | 'info.about.designation', 27 | '_id', 28 | 'isAdmin' 29 | ]) 30 | .populate('blocked', [ 31 | 'name.firstName', 32 | 'name.lastName', 33 | 'info.about.designation', 34 | '_id', 35 | 'isAdmin' 36 | ]) 37 | .exec() 38 | // console.log(user) 39 | 40 | if (!user) { 41 | throw new Error() 42 | } else { 43 | req.token = token 44 | req.user = user 45 | next() 46 | } 47 | } catch (error) { 48 | res.status(HttpStatus.UNAUTHORIZED).send({ error: 'Please authenticate' }) 49 | } 50 | } 51 | 52 | module.exports = auth 53 | -------------------------------------------------------------------------------- /app/middleware/isOAuthAllowed.js: -------------------------------------------------------------------------------- 1 | const Organisation = require('../models/Organisation') 2 | const HttpStatus = require('http-status-codes') 3 | const afterAuthRedirect = (process.env.clientbaseurl + '/login') || 'http://localhost:3000/login' 4 | 5 | const isOAuthAllowed = async (req, res, next) => { 6 | try { 7 | const org = await Organisation.find({}) 8 | if (org.length === 0) { 9 | return res.status(HttpStatus.NOT_FOUND).json({ 10 | error: 'Organization does not exist!' 11 | }) 12 | } 13 | switch (req._parsedUrl.path.slice(6, 12)) { 14 | case 'google': { 15 | if(org[0].options.authentication.google) { 16 | next() 17 | }else { 18 | throw new Error('Google OAuth Not Allowed') 19 | } 20 | break 21 | } 22 | case 'github': { 23 | if(org[0].options.authentication.github) { 24 | next() 25 | }else { 26 | throw new Error('GitHub OAuth Not Allowed') 27 | } 28 | break 29 | } 30 | default: { 31 | if(org[0].options.authentication.email) { 32 | next() 33 | }else { 34 | throw new Error('Email Auth has beed turned off!') 35 | } 36 | break 37 | } 38 | } 39 | } catch (Error) { 40 | res.redirect(afterAuthRedirect) 41 | } 42 | } 43 | 44 | module.exports = isOAuthAllowed 45 | -------------------------------------------------------------------------------- /app/middleware/maintenance.js: -------------------------------------------------------------------------------- 1 | const Organization = require('../models/Organisation') 2 | const HttpStatus = require('http-status-codes') 3 | 4 | const isUnderMaintenance = async (req, res, next) => { 5 | try { 6 | const org = await Organization.find({}) 7 | if (!org) { 8 | next(new Error('No org is found!')) 9 | } 10 | if (org[0] && org[0].isMaintenance) { 11 | return res.status(HttpStatus.SERVICE_UNAVAILABLE).json({ 12 | msg: 'Organization is kept under maintenance!' 13 | }) 14 | } else { 15 | next() 16 | } 17 | } catch (Error) { 18 | return res.status(HttpStatus.BAD_REQUEST).json({ Error }) 19 | } 20 | } 21 | 22 | module.exports = isUnderMaintenance 23 | -------------------------------------------------------------------------------- /app/middleware/passportOAuth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | const GitHubStrategy = require('passport-github2').Strategy; 4 | const user = require('../controllers/user'); 5 | const GOOGLE_OAUTH_CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID || ''; 6 | const GOOGLE_OAUTH_CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET || ''; 7 | const GOOGLE_OAUTH_CALLBACK = process.env.GOOGLE_OAUTH_CALLBACK || ''; 8 | const GITHUB_OAUTH_CLIENT_ID = process.env.GITHUB_OAUTH_CLIENT_ID || ''; 9 | const GITHUB_OAUTH_CLIENT_SECRET = process.env.GITHUB_OAUTH_CLIENT_SECRET || ''; 10 | const GITHUB_OAUTH_CALLBACK = process.env.GITHUB_OAUTH_CALLBACK || ''; 11 | 12 | module.exports = { 13 | initGoogleAuth: () => { 14 | passport.use(new GoogleStrategy({ 15 | clientID: GOOGLE_OAUTH_CLIENT_ID, 16 | clientSecret: GOOGLE_OAUTH_CLIENT_SECRET, 17 | callbackURL: GOOGLE_OAUTH_CALLBACK, 18 | proxy: true 19 | }, (accessToken, refreshToken, profile, next) => { 20 | 21 | const provider='google'; 22 | user.findOrCreateForOAuth(profile, provider) 23 | .then(details => { 24 | if (details) { 25 | next(null, details); 26 | } else { 27 | next(null, false) 28 | } 29 | }).catch(err=>next(err)) 30 | })) 31 | }, 32 | initGitHubAuth: () => { 33 | passport.use(new GitHubStrategy({ 34 | clientID: GITHUB_OAUTH_CLIENT_ID, 35 | clientSecret: GITHUB_OAUTH_CLIENT_SECRET, 36 | callbackURL: GITHUB_OAUTH_CALLBACK, 37 | proxy: true, 38 | scope: ['user:email'] 39 | }, (accessToken, refreshToken, profile, next) => { 40 | const provider='github' 41 | user.findOrCreateForOAuth(profile, provider) 42 | .then(details => { 43 | if (details) { 44 | next(null, details) 45 | } else { 46 | next(null, false) 47 | } 48 | }).catch(err=>next(err)) 49 | })) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/middleware/rateLimiter.js: -------------------------------------------------------------------------------- 1 | const redis = require('../../config/redis') 2 | const redisClient = redis.redisClient 3 | const moment = require('moment') 4 | const WINDOW_SIZE_IN_HOURS = 24 5 | const MAX_WINDOW_REQUEST_COUNT = process.env.MAX_WINDOW_REQUEST_COUNT || 500 6 | const WINDOW_LOG_INTERVAL_IN_HOURS = 1 7 | 8 | module.exports = { 9 | customRateLimiter: (req, res, next) => { 10 | try { 11 | // check if redis exists 12 | if (!redisClient) { 13 | throw new Error('RedisClient not found on the server') 14 | } 15 | // if exists check if request made earlier from same ip 16 | redisClient.get(req.ip, (err, reply) => { 17 | if (err) { 18 | console.log('Error in fetching data from redis', err) 19 | } 20 | const currentRequestTime = moment() 21 | // if no reply from redis then store the users request to the server in redis 22 | if (reply === null || reply === undefined) { 23 | const newRecord = [] 24 | const info = { 25 | requestTimeStamp: currentRequestTime.unix(), 26 | requestCount: 1 27 | } 28 | newRecord.unshift(info) 29 | // set to redis => ip => [{ requestTimeStamp, requestCount }] 30 | redisClient.set(req.ip, JSON.stringify(newRecord)) 31 | next() 32 | } else { 33 | // if record is found, parse it's value and calculate number of requests users has made within the last window 34 | const data = JSON.parse(reply) 35 | 36 | const windowStartTimestamp = moment() 37 | .subtract(WINDOW_SIZE_IN_HOURS, 'hours') 38 | .unix() 39 | 40 | const requestsWithinWindow = data.filter(entry => { 41 | return entry.requestTimeStamp > windowStartTimestamp 42 | }) 43 | 44 | const totalWindowRequestsCount = requestsWithinWindow.reduce((accumulator, entry) => { 45 | return accumulator + entry.requestCount 46 | }, 0) 47 | 48 | // if number of requests made is greater than or equal to the desired maximum, return error 49 | if (totalWindowRequestsCount >= MAX_WINDOW_REQUEST_COUNT) { 50 | return res.status(429).json({ 51 | error: `You have exceeded the ${MAX_WINDOW_REQUEST_COUNT} requests in ${WINDOW_SIZE_IN_HOURS} hrs limit!` 52 | }) 53 | } else { 54 | // if number of requests made is less than allowed maximum, log new entry 55 | const lastRequestLog = data[data.length - 1] 56 | const potentialCurrentWindowIntervalStartTimeStamp = currentRequestTime 57 | .subtract(WINDOW_LOG_INTERVAL_IN_HOURS, 'hours') 58 | .unix() 59 | 60 | // if interval has not passed since last request log, increment counter 61 | if (lastRequestLog.requestTimeStamp > potentialCurrentWindowIntervalStartTimeStamp) { 62 | lastRequestLog.requestCount++ 63 | data[data.length - 1] = lastRequestLog 64 | } else { 65 | // if interval has passed, log new entry for current user and timestamp 66 | data.unshift({ 67 | requestTimeStamp: currentRequestTime.unix(), 68 | requestCount: 1 69 | }) 70 | } 71 | redisClient.set(req.ip, JSON.stringify(data)) 72 | next() 73 | } 74 | } 75 | }) 76 | } catch (error) { 77 | console.log('Error in rateLimiter', error) 78 | next(error) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/middleware/sanitise.js: -------------------------------------------------------------------------------- 1 | const sanitize = require('mongo-sanitize') 2 | module.exports = { 3 | cleanBody: (req, res, next) => { 4 | req.body = sanitize(req.body) 5 | next() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/models/Activity.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const activitySchema = new Schema({ 5 | userId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'User' 8 | }, 9 | activity: [{ 10 | route: { 11 | type: String, 12 | required: true 13 | }, 14 | method: { 15 | type: String, 16 | required: true 17 | }, 18 | collectionType: { 19 | type: String, 20 | required: true 21 | }, 22 | id: { 23 | type: String, 24 | required: true 25 | }, 26 | timestamp: { 27 | type: String, 28 | required: true 29 | } 30 | }] 31 | }) 32 | 33 | module.exports = mongoose.model('Activity', activitySchema) 34 | -------------------------------------------------------------------------------- /app/models/Comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const validator = require('validator') 3 | const Schema = mongoose.Schema 4 | 5 | const commentSchema = new Schema({ 6 | userId: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'User' 9 | }, 10 | postId: { 11 | type: Schema.Types.ObjectId, 12 | ref: 'Post' 13 | }, 14 | content: { 15 | type: String, 16 | trim: true, 17 | required: true, 18 | validate (content) { 19 | if (validator.isEmpty(content)) { 20 | throw new Error('Comment can not be empty!') 21 | } 22 | } 23 | }, 24 | votes: { 25 | upVotes: { 26 | user: [{ 27 | type: Schema.Types.ObjectId, 28 | ref: 'User' 29 | }] 30 | }, 31 | downVotes: { 32 | user: [{ 33 | type: Schema.Types.ObjectId, 34 | ref: 'User' 35 | }] 36 | } 37 | }, 38 | createdAt: { 39 | type: Date, 40 | default: Date.now() 41 | }, 42 | updatedAt: { 43 | type: Date, 44 | default: Date.now() 45 | } 46 | }) 47 | 48 | module.exports = mongoose.model('Comment', commentSchema) 49 | -------------------------------------------------------------------------------- /app/models/Event.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const validator = require('validator') 3 | const Schema = mongoose.Schema 4 | 5 | const eventSchema = new Schema({ 6 | eventName: { 7 | type: String, 8 | trim: true, 9 | required: true, 10 | minlength: 5, 11 | validate (eventName) { 12 | if (validator.isEmpty(eventName)) { 13 | throw new Error('Event name is required!') 14 | } 15 | if (!validator.isLength(eventName, { min: 5 })) { 16 | throw new Error('Event name should be min 5 characters long!') 17 | } 18 | } 19 | }, 20 | description: { 21 | shortDescription: { 22 | type: String, 23 | trim: true, 24 | required: true, 25 | minlength: 5, 26 | validate (description) { 27 | if (!validator.isLength(description, { min: 5 })) { 28 | throw new Error('Short description should be min 5 characters long! ') 29 | } 30 | if (validator.isEmpty(description)) { 31 | throw new Error('Short description of event is required!') 32 | } 33 | } 34 | }, 35 | longDescription: { 36 | type: String, 37 | trim: true, 38 | required: true, 39 | minlength: 10, 40 | validate (longDescription) { 41 | if (!validator.isLength(longDescription, { min: 10 })) { 42 | throw new Error('Long description should be min 10 characters long! ') 43 | } 44 | if (validator.isEmpty(longDescription)) { 45 | throw new Error('Long description of event is required!') 46 | } 47 | } 48 | } 49 | }, 50 | eventTime: { 51 | type: String 52 | }, 53 | rsvpYes: [{ 54 | type: Schema.Types.ObjectId, 55 | ref: 'User' 56 | }], 57 | rsvpMaybe: [{ 58 | type: Schema.Types.ObjectId, 59 | ref: 'User' 60 | }], 61 | rsvpNo: [{ 62 | type: Schema.Types.ObjectId, 63 | ref: 'User' 64 | }], 65 | slots: { 66 | type: String, 67 | default: 0, 68 | validate (slots) { 69 | if (!validator.isNumeric(slots)) { 70 | throw new Error('Slots should be a number') 71 | } 72 | } 73 | }, 74 | location: { 75 | type: String, 76 | required: true, 77 | trim: true, 78 | validate (location) { 79 | if (validator.isEmpty(location)) { 80 | throw new Error('Event location is required!') 81 | } 82 | } 83 | }, 84 | eventDate: { 85 | type: Date, 86 | required: true, 87 | default: Date.now() 88 | }, 89 | createdBy: { 90 | type: Schema.Types.ObjectId, 91 | ref: 'User' 92 | }, 93 | isOnline: { 94 | type: Boolean, 95 | default: false 96 | }, 97 | createdAt: { 98 | type: Date, 99 | required: true, 100 | default: Date.now() 101 | }, 102 | updatedAt: { 103 | type: Date, 104 | required: true, 105 | default: Date.now() 106 | } 107 | }) 108 | module.exports = mongoose.model('Event', eventSchema) 109 | -------------------------------------------------------------------------------- /app/models/Notifications.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const NotificationSchema = new Schema({ 5 | heading: { 6 | type: String 7 | }, 8 | content: { 9 | type: String 10 | }, 11 | tag: { 12 | type: String 13 | }, 14 | createdAt: { 15 | type: Date, 16 | default: Date.now() 17 | } 18 | }) 19 | module.exports = mongoose.model('Notification', NotificationSchema) 20 | -------------------------------------------------------------------------------- /app/models/Organisation.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | const validator = require('validator') 4 | 5 | const orgSchema = new Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | trim: true, 10 | minlength: 3, 11 | validate (name) { 12 | if (validator.isEmpty(name)) { 13 | throw new Error('Organization name is required!') 14 | } 15 | if (!validator.isLength(name, { min: 3 })) { 16 | throw new Error('Organization name should be min 3 characters long!') 17 | } 18 | } 19 | }, 20 | description: { 21 | shortDescription: { 22 | type: String, 23 | required: true, 24 | trim: true, 25 | minlength: 5, 26 | validate (shortDescription) { 27 | if (validator.isEmpty(shortDescription)) { 28 | throw new Error('Short description is required!') 29 | } 30 | if (!validator.isLength(shortDescription, { min: 5 })) { 31 | throw new Error('Short description should be min 5 characters long!') 32 | } 33 | } 34 | }, 35 | longDescription: { 36 | type: String, 37 | trim: true, 38 | minlength: 10, 39 | validate (longDescription) { 40 | if (validator.isEmpty(longDescription)) { 41 | throw new Error('Long description is required!') 42 | } 43 | if (!validator.isLength(longDescription, { min: 10 })) { 44 | throw new Error('Long description should be min 10 characters long!') 45 | } 46 | } 47 | } 48 | }, 49 | image: { 50 | name: String, 51 | contentType: String, 52 | key: String, 53 | href: { 54 | type: String, 55 | trim: true, 56 | validator (imgUrl) { 57 | if (!validator.isURL(imgUrl)) { 58 | throw new Error('Invalid image URL!') 59 | } 60 | } 61 | } 62 | }, 63 | imgUrl: { 64 | type: String, 65 | trim: true, 66 | validator (imgUrl) { 67 | if (!validator.isURL(imgUrl)) { 68 | throw new Error('Invalid image URL!') 69 | } 70 | } 71 | }, 72 | contactInfo: { 73 | email: { 74 | type: String, 75 | required: true, 76 | validate (email) { 77 | if (validator.isEmpty(email)) { 78 | throw new Error('EmailId or org is required!') 79 | } 80 | if (!validator.isEmail(email)) { 81 | throw new Error('Invalid emailId') 82 | } 83 | } 84 | }, 85 | adminEmail: { 86 | type: String, 87 | trim: true 88 | }, 89 | website: { 90 | type: String, 91 | trim: true, 92 | required: true, 93 | validate (website) { 94 | if (validator.isEmpty(website)) { 95 | throw new Error('Organization website is required!') 96 | } 97 | if (!validator.isURL(website)) { 98 | throw new Error('Invalid website url!') 99 | } 100 | } 101 | }, 102 | chattingPlatform: [ 103 | { 104 | _id: false, 105 | link: { 106 | type: String 107 | } 108 | } 109 | ] 110 | }, 111 | options: { 112 | _id: false, 113 | settings: { 114 | enableEmail: { 115 | type: Boolean, 116 | default: true 117 | }, 118 | language: { 119 | type: String, 120 | enum: ['English', 'French', 'German'], 121 | default: 'English' 122 | }, 123 | canEdit: { 124 | type: Boolean, 125 | default: true 126 | }, 127 | editingLimit: { 128 | type: String, 129 | default: 'Always' 130 | }, 131 | timeFormat: { 132 | type: String, 133 | enum: ['24', '12'], 134 | default: '12' 135 | } 136 | }, 137 | permissions: { 138 | sendInvite: { 139 | type: String, 140 | enum: ['BOTH', 'ADMINS', 'NONE'], 141 | default: 'BOTH' 142 | }, 143 | canCreateManage: { 144 | type: String, 145 | enum: ['BOTH', 'ADMINS', 'MEMBERS'], 146 | default: 'BOTH' 147 | }, 148 | canChangeEmail: { 149 | type: Boolean, 150 | default: true 151 | }, 152 | canChangeName: { 153 | type: Boolean, 154 | default: true 155 | } 156 | }, 157 | authentication: { 158 | email: { 159 | type: Boolean, 160 | default: true 161 | }, 162 | google: { 163 | type: Boolean, 164 | default: false 165 | }, 166 | github: { 167 | type: Boolean, 168 | default: false 169 | }, 170 | gitlab: { 171 | type: Boolean, 172 | default: false 173 | } 174 | } 175 | }, 176 | adminInfo: { 177 | _id: false, 178 | adminId: [{ 179 | type: Schema.Types.ObjectId, 180 | ref: 'User' 181 | }] 182 | }, 183 | moderatorInfo: { 184 | _id: false, 185 | adminId: [{ 186 | type: Schema.Types.ObjectId, 187 | ref: 'User' 188 | }] 189 | }, 190 | isArchived: { 191 | type: Boolean, 192 | default: false 193 | }, 194 | isMaintenance: { 195 | type: Boolean, 196 | default: false 197 | }, 198 | createdAt: { 199 | type: Date, 200 | required: true, 201 | default: Date.now() 202 | }, 203 | updatedAt: { 204 | type: Date, 205 | required: true, 206 | default: Date.now() 207 | }, 208 | wikis: { 209 | accessToken: { type: String, default: null }, 210 | orgId: { type: String, default: null } 211 | } 212 | }) 213 | module.exports = mongoose.model('Organization', orgSchema) 214 | -------------------------------------------------------------------------------- /app/models/Post.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const validator = require('validator') 3 | const Schema = mongoose.Schema 4 | 5 | const PostSchema = new Schema({ 6 | content: { 7 | type: String, 8 | trim: true, 9 | required: true, 10 | validate (content) { 11 | if (validator.isEmpty(content)) { 12 | throw new Error('Post content can not be empty!') 13 | } 14 | } 15 | }, 16 | userId: { 17 | type: Schema.Types.ObjectId, 18 | ref: 'User' 19 | }, 20 | image: { 21 | name: String, 22 | contentType: String, 23 | key: String, 24 | href: { 25 | type: String, 26 | trim: true, 27 | validator (imgUrl) { 28 | if (!validator.isURL(imgUrl)) { 29 | throw new Error('Invalid image URL!') 30 | } 31 | } 32 | } 33 | }, 34 | imgUrl: { 35 | type: String, 36 | trim: true, 37 | validator (imgUrl) { 38 | if (!validator.isURL(imgUrl)) { 39 | throw new Error('Invalid image URL!') 40 | } 41 | } 42 | }, 43 | votes: { 44 | upVotes: { 45 | user: [{ 46 | type: Schema.Types.ObjectId, 47 | ref: 'User' 48 | }] 49 | }, 50 | heart: { 51 | user: [{ 52 | type: Schema.Types.ObjectId, 53 | ref: 'User' 54 | }] 55 | }, 56 | happy: { 57 | user: [{ 58 | type: Schema.Types.ObjectId, 59 | ref: 'User' 60 | }] 61 | }, 62 | donut: { 63 | user: [{ 64 | type: Schema.Types.ObjectId, 65 | ref: 'User' 66 | }] 67 | } 68 | }, 69 | comments: { 70 | type: Schema.Types.ObjectId, 71 | ref: 'Comment' 72 | }, 73 | isPinned: { 74 | type: Boolean, 75 | default: false 76 | }, 77 | createdAt: { 78 | type: Date, 79 | required: true, 80 | default: Date.now() 81 | }, 82 | updatedAt: { 83 | type: Date, 84 | required: true, 85 | default: Date.now() 86 | } 87 | }) 88 | 89 | module.exports = mongoose.model('Post', PostSchema) 90 | -------------------------------------------------------------------------------- /app/models/Project.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | const validator = require('validator') 4 | 5 | const projectSchema = new Schema({ 6 | projectName: { 7 | type: String, 8 | required: true, 9 | trim: true, 10 | minlength: 3, 11 | validate (projectName) { 12 | if (validator.isEmpty(projectName)) { 13 | throw new Error('Project name is required!') 14 | } 15 | if (!validator.isLength(projectName, { min: 3 })) { 16 | throw new Error('Project name should be 3 characters long!') 17 | } 18 | } 19 | }, 20 | description: { 21 | short: { 22 | type: String, 23 | required: true, 24 | trim: true, 25 | validate (short) { 26 | if (validator.isEmpty(short)) { 27 | throw new Error('Short description for the project is required!') 28 | } 29 | } 30 | }, 31 | long: { 32 | type: String, 33 | trim: true 34 | } 35 | }, 36 | techStacks: [], 37 | image: { 38 | type: Buffer, 39 | contentType: String 40 | }, 41 | imgUrl: { 42 | type: String, 43 | trim: true, 44 | validator (imgUrl) { 45 | if (!validator.isURL(imgUrl)) { 46 | throw new Error('Invalid image URL!') 47 | } 48 | } 49 | }, 50 | version: { 51 | type: String, 52 | trim: true 53 | }, 54 | links: [ 55 | { 56 | githubLink: { 57 | type: String, 58 | trim: true, 59 | validate (githubLink) { 60 | if (!validator.isURL(githubLink)) { 61 | throw new Error('Invalid project url!') 62 | } 63 | } 64 | }, 65 | bitbucketLink: { 66 | type: String, 67 | trim: true 68 | } 69 | } 70 | ], 71 | contributors: [{ 72 | type: Schema.Types.ObjectId, 73 | ref: 'User' 74 | }], 75 | maintainers: [{ 76 | type: Schema.Types.ObjectId, 77 | ref: 'User' 78 | }], 79 | createdBy: { 80 | type: Schema.Types.ObjectId, 81 | ref: 'User' 82 | }, 83 | createdAt: { 84 | type: Date, 85 | default: Date.now() 86 | }, 87 | updatedAt: { 88 | type: Date, 89 | default: Date.now() 90 | } 91 | }) 92 | 93 | module.exports = mongoose.model('Project', projectSchema) 94 | -------------------------------------------------------------------------------- /app/models/Proposal.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const proposalSchema = new Schema( 5 | { 6 | title: { 7 | type: String, 8 | required: true 9 | }, 10 | organization: { 11 | type: String 12 | }, 13 | content: { 14 | type: String, 15 | required: true 16 | }, 17 | proposalStatus: { 18 | type: String, 19 | default: 'draft' 20 | }, 21 | creator: { 22 | type: Schema.Types.ObjectId, 23 | ref: 'User', 24 | required: true 25 | }, 26 | proposalDescription: { 27 | type: String 28 | }, 29 | attachments: [{ fileLink: String, s3_key: String }], 30 | 31 | createdAt: { 32 | type: Date, 33 | required: true, 34 | default: Date.now() 35 | }, 36 | updatedAt: { 37 | type: Date, 38 | required: true, 39 | default: Date.now() 40 | }, 41 | comments: [{ userName: String, comment: String }] 42 | }, 43 | { timestamps: true } 44 | ) 45 | 46 | module.exports = mongoose.model('Proposal', proposalSchema) 47 | -------------------------------------------------------------------------------- /app/models/ProposalNotification.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const ProposalNotification = new Schema({ 5 | heading: { 6 | type: String 7 | }, 8 | proposal: { 9 | type: String 10 | }, 11 | content: { 12 | type: String 13 | }, 14 | tag: { 15 | type: String 16 | }, 17 | createdAt: { 18 | type: Date, 19 | default: new Date().toISOString().substring(0, 25) 20 | } 21 | }) 22 | module.exports = mongoose.model('ProposalNotification', ProposalNotification) 23 | -------------------------------------------------------------------------------- /app/models/Ticket.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const validator = require('validator') 3 | const Schema = mongoose.Schema 4 | 5 | const ticketCommentSchema = new Schema({ 6 | createdBy: { 7 | userId: { 8 | type: Schema.Types.ObjectId, 9 | ref: 'User' 10 | }, 11 | name: { 12 | type: String, 13 | trim: true 14 | }, 15 | email: { 16 | type: String, 17 | trim: true 18 | }, 19 | shortDescription: { 20 | type: String, 21 | trim: true 22 | }, 23 | designation: { 24 | type: String, 25 | trim: true 26 | }, 27 | location: { 28 | type: String, 29 | trim: true 30 | } 31 | }, 32 | content: { 33 | type: String, 34 | trim: true, 35 | required: true, 36 | validate (content) { 37 | if (validator.isEmpty(content)) { 38 | throw new Error('Comment can not be empty!') 39 | } 40 | } 41 | }, 42 | votes: { 43 | upVotes: { 44 | user: [{ 45 | type: Schema.Types.ObjectId, 46 | ref: 'User' 47 | }] 48 | }, 49 | downVotes: { 50 | user: [{ 51 | type: Schema.Types.ObjectId, 52 | ref: 'User' 53 | }] 54 | } 55 | }, 56 | createdAt: { 57 | type: Date, 58 | required: true, 59 | default: Date.now() 60 | }, 61 | updatedAt: { 62 | type: Date, 63 | required: true, 64 | default: Date.now() 65 | } 66 | }) 67 | 68 | const ticketSchema = new Schema({ 69 | title: { 70 | type: String, 71 | trim: true, 72 | required: true 73 | }, 74 | number: { 75 | type: Number, 76 | required: true 77 | }, 78 | createdBy: { 79 | id: { 80 | type: Schema.Types.ObjectId, 81 | ref: 'User' 82 | }, 83 | name: { 84 | type: String, 85 | trim: true 86 | }, 87 | email: { 88 | type: String, 89 | trim: true 90 | }, 91 | shortDescription: { 92 | type: String, 93 | trim: true 94 | }, 95 | designation: { 96 | type: String, 97 | trim: true 98 | }, 99 | location: { 100 | type: String, 101 | trim: true 102 | } 103 | }, 104 | status: { 105 | type: String, 106 | enum: ['OPEN', 'CLOSED', 'PENDING', 'SOLVED', 'ON_HOLD'], 107 | default: 'OPEN', 108 | required: true 109 | }, 110 | shortDescription: { 111 | type: String, 112 | required: true, 113 | trim: true, 114 | minlength: 5, 115 | validate (shortDescription) { 116 | if (validator.isEmpty(shortDescription)) { 117 | throw new Error('Short description is required!') 118 | } 119 | if (!validator.isLength(shortDescription, { min: 10 })) { 120 | throw new Error('Short description should be min 5 characters long!') 121 | } 122 | } 123 | }, 124 | content: { 125 | type: String, 126 | trim: true, 127 | minlength: 10, 128 | validate (longDescription) { 129 | if (validator.isEmpty(longDescription)) { 130 | throw new Error('Long description is required!') 131 | } 132 | if (!validator.isLength(longDescription, { min: 10 })) { 133 | throw new Error('Long description should be min 10 characters long!') 134 | } 135 | } 136 | }, 137 | tags: [ 138 | { 139 | type: String, 140 | trim: true, 141 | validate: (value) => { 142 | if (!validator.isLength(value, { min: 0, max: 20 })) { 143 | throw new Error('Tags should have between 0 to 20 characters') 144 | } 145 | } 146 | } 147 | ], 148 | history: [ 149 | { 150 | type: { 151 | type: String, 152 | trim: true 153 | }, 154 | title: { 155 | old: { 156 | type: String, 157 | trim: true 158 | }, 159 | new: { 160 | type: String, 161 | trim: true 162 | } 163 | }, 164 | status: { 165 | type: String, 166 | trim: true 167 | }, 168 | shortDescription: { 169 | type: String, 170 | trim: true 171 | }, 172 | content: { 173 | type: String, 174 | trim: true 175 | }, 176 | tag: { 177 | type: String, 178 | trim: true 179 | }, 180 | updatedAt: { 181 | type: Date, 182 | required: true, 183 | default: Date.now() 184 | }, 185 | updatedBy: { 186 | userId: { 187 | type: Schema.Types.ObjectId, 188 | ref: 'User' 189 | }, 190 | name: { 191 | type: String, 192 | trim: true 193 | } 194 | } 195 | } 196 | ], 197 | comments: [ticketCommentSchema], // mongoose subdocument 198 | createdAt: { 199 | type: Date, 200 | required: true, 201 | default: Date.now() 202 | }, 203 | updatedAt: { 204 | type: Date, 205 | required: true, 206 | default: Date.now() 207 | } 208 | }) 209 | 210 | module.exports = mongoose.model('Ticket', ticketSchema) 211 | -------------------------------------------------------------------------------- /app/models/UrlShortner.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const urlShortnerSchema = new mongoose.Schema({ 4 | longUrl: { 5 | type: String, 6 | required: true 7 | }, 8 | urlCode: { 9 | type: String 10 | }, 11 | shortUrl: { 12 | type: String 13 | } 14 | }) 15 | 16 | const shortURL = mongoose.model('shortURL', urlShortnerSchema) 17 | module.exports = shortURL 18 | -------------------------------------------------------------------------------- /app/models/User.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const bcrypt = require('bcrypt') 3 | const validator = require('validator') 4 | const jwt = require('jsonwebtoken') 5 | 6 | const saltRounds = 8 7 | 8 | // user schema 9 | const UserSchema = new mongoose.Schema({ 10 | name: { 11 | firstName: { 12 | type: String, 13 | trim: true, 14 | required: true, 15 | validate (firstName) { 16 | if (validator.isEmpty(firstName)) { 17 | throw new Error('First name field can not be empty!') 18 | } 19 | } 20 | }, 21 | lastName: { 22 | type: String, 23 | trim: true, 24 | validate (lastName) { 25 | if (validator.isEmpty(lastName)) { 26 | throw new Error('Last name field can not be empty!') 27 | } 28 | } 29 | } 30 | }, 31 | email: { 32 | type: String, 33 | trim: true, 34 | required: true, 35 | unique: true, 36 | lowercase: true, 37 | validate (email) { 38 | if (!validator.isEmail(email)) { 39 | throw new Error('Invalid emailId') 40 | } 41 | if (validator.isEmpty(email)) { 42 | throw new Error('Email is required!') 43 | } 44 | } 45 | }, 46 | phone: { 47 | type: String, 48 | trim: true, 49 | minlength: 10, 50 | validate (phone) { 51 | if (!validator.isLength(phone, { min: 10, max: 10 })) { 52 | throw new Error('Phone number is invalid!') 53 | } 54 | } 55 | }, 56 | password: { 57 | type: String, 58 | trim: true, 59 | minlength: 6, 60 | validate (password) { 61 | if (!validator.isLength(password, { min: 6 })) { 62 | throw new Error('Password should be min 6 characters long!') 63 | } 64 | if (validator.isEmpty(password)) { 65 | throw new Error('Password is required!') 66 | } 67 | } 68 | }, 69 | provider: { 70 | type: String, 71 | enum: ['google', 'github', 'email'], 72 | default: 'email' 73 | }, 74 | socialMedia: { 75 | youtube: { 76 | type: String 77 | }, 78 | facebook: { 79 | type: String 80 | }, 81 | twitter: { 82 | type: String 83 | }, 84 | github: { 85 | type: String 86 | }, 87 | linkedin: { 88 | type: String 89 | } 90 | }, 91 | info: { 92 | about: { 93 | shortDescription: { 94 | type: String, 95 | validate (shortDescription) { 96 | if (validator.isEmpty(shortDescription)) { 97 | throw new Error('Short description is required') 98 | } 99 | } 100 | }, 101 | longDescription: { 102 | type: String 103 | }, 104 | website: { 105 | type: String, 106 | trim: true, 107 | validate (website) { 108 | if (!validator.isURL(website)) { 109 | throw new Error('Invalid website link!') 110 | } 111 | } 112 | }, 113 | designation: { 114 | type: String, 115 | trim: true 116 | }, 117 | education: [ 118 | { 119 | _id: false, 120 | school: { 121 | schoolName: { 122 | type: String, 123 | trim: true 124 | }, 125 | year: { 126 | type: String 127 | } 128 | } 129 | } 130 | ], 131 | skills: [ 132 | { 133 | type: String 134 | } 135 | ], 136 | location: { 137 | type: String, 138 | trim: true 139 | } 140 | } 141 | }, 142 | orgId: { 143 | type: mongoose.Schema.Types.ObjectId, 144 | ref: 'Organization' 145 | }, 146 | notifications: [ 147 | { 148 | heading: { 149 | type: String 150 | }, 151 | content: { 152 | type: String 153 | }, 154 | tag: { 155 | type: String 156 | } 157 | } 158 | ], 159 | proposalNotifications: [ 160 | { 161 | heading: { 162 | type: String 163 | }, 164 | content: { 165 | type: String 166 | }, 167 | tag: { 168 | type: String 169 | }, 170 | createdAt: { 171 | type: Date, 172 | required: true, 173 | default: Date.now() 174 | } 175 | } 176 | ], 177 | ticketNotifications: [ 178 | { 179 | heading: { 180 | type: String 181 | }, 182 | content: { 183 | type: String 184 | }, 185 | tag: { 186 | type: String 187 | }, 188 | createdAt: { 189 | type: Date, 190 | default: Date.now() 191 | } 192 | } 193 | ], 194 | followers: [ 195 | { 196 | type: mongoose.Schema.Types.ObjectId, 197 | ref: 'User' 198 | } 199 | ], 200 | followings: [ 201 | { 202 | type: mongoose.Schema.Types.ObjectId, 203 | ref: 'User' 204 | } 205 | ], 206 | blocked: [ 207 | { 208 | type: mongoose.Schema.Types.ObjectId, 209 | ref: 'User' 210 | } 211 | ], 212 | pinned: { 213 | _id: false, 214 | postId: [ 215 | { 216 | type: mongoose.Schema.Types.ObjectId, 217 | ref: 'Post' 218 | } 219 | ] 220 | }, 221 | firstRegister: { 222 | type: Boolean, 223 | default: false 224 | }, 225 | isAdmin: { 226 | type: Boolean, 227 | default: false 228 | }, 229 | isTicketsModerator: { 230 | type: Boolean, 231 | default: false 232 | }, 233 | isActivated: { 234 | type: Boolean, 235 | default: false 236 | }, 237 | isRemoved: { 238 | type: Boolean, 239 | default: false 240 | }, 241 | createdAt: { 242 | type: Date, 243 | required: true, 244 | default: Date.now(), 245 | select: true 246 | }, 247 | updatedAt: { 248 | type: Date, 249 | required: true, 250 | default: Date.now() 251 | }, 252 | tokens: [ 253 | { 254 | token: { 255 | type: String, 256 | required: true 257 | } 258 | } 259 | ] 260 | }) 261 | 262 | // generate auth token 263 | // Schema Methods, needs to be invoked by an instance of a Mongoose document 264 | UserSchema.methods.generateAuthToken = async function () { 265 | const user = this 266 | const token = jwt.sign( 267 | { _id: user._id.toString() }, 268 | process.env.JWT_SECRET 269 | ) 270 | 271 | user.tokens = user.tokens.concat({ token: token }) 272 | await user.save() 273 | 274 | return token 275 | } 276 | 277 | // Schema Statics are methods that can be invoked directly by a Model 278 | UserSchema.statics.findByCredentials = async (email, password) => { 279 | const user = await User.findOne({ 280 | email: email 281 | }) 282 | 283 | if (!user) { 284 | throw new Error('No such user') 285 | } else if(!user.hasOwnProperty('password') && user.provider!=='email'){ 286 | throw new Error(`Please use ${user.provider} to login!`) 287 | } else { 288 | const isMatch = await bcrypt.compare(password, user.password) 289 | if (!isMatch) { 290 | throw new Error('Incorrect password provided') 291 | } else { 292 | return user 293 | } 294 | } 295 | } 296 | 297 | // hash user password before saving into database 298 | UserSchema.pre('save', async function (next) { 299 | const user = this 300 | 301 | if (user.isModified('password')) { 302 | user.password = await bcrypt.hash(user.password, saltRounds) 303 | } 304 | 305 | next() 306 | }) 307 | 308 | const User = mongoose.model('User', UserSchema) 309 | module.exports = User 310 | -------------------------------------------------------------------------------- /app/routes/activity.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const activityController = require('../controllers/activity') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | const auth = require('../middleware/auth') 7 | 8 | // get a User activity 9 | router.get( 10 | '/user/:id', 11 | isUnderMaintenance, 12 | auth, 13 | activityController.getActivity 14 | ) 15 | 16 | module.exports = router 17 | -------------------------------------------------------------------------------- /app/routes/analytics.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const analyticsController = require('../controllers/analytics') 4 | const auth = require('../middleware/auth') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // Get Browser analytics 8 | router.post('/browser', isUnderMaintenance, auth, analyticsController.getBrowser) 9 | 10 | // Get country analytics 11 | router.post('/countries', isUnderMaintenance, auth, analyticsController.getCountries) 12 | 13 | // Get Device analytics 14 | router.post('/device', isUnderMaintenance, auth, analyticsController.getDevice) 15 | 16 | // Get most viewed Proposals 17 | router.post('/mostviewed', isUnderMaintenance, auth, analyticsController.getTopProposals) 18 | 19 | // Get Views of a specific proposal 20 | router.post('/views', isUnderMaintenance, auth, analyticsController.getProposalViews) 21 | 22 | module.exports = router 23 | -------------------------------------------------------------------------------- /app/routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const authController = require('../controllers/auth') 4 | // const isActivated = require('../middleware/activate') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // user login 8 | router.post( 9 | '/login', 10 | isUnderMaintenance, 11 | authController.authenticateUser 12 | ) 13 | 14 | // user logout 15 | router.post( 16 | '/logout', 17 | authController.logout 18 | ) 19 | 20 | // logout all sessions 21 | router.post( 22 | '/logoutAll', 23 | authController.logoutAll 24 | ) 25 | 26 | module.exports = router 27 | -------------------------------------------------------------------------------- /app/routes/comment.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const auth = require('../middleware/auth') 4 | const commentController = require('../controllers/comment') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // CREATE COMMENT 8 | router.post( 9 | '/:id', 10 | isUnderMaintenance, 11 | auth, 12 | commentController.comment 13 | ) 14 | 15 | // DELETE COMMENT BY ID 16 | router.delete( 17 | '/:id', 18 | isUnderMaintenance, 19 | auth, 20 | commentController.delete 21 | ) 22 | 23 | // UPDATE COMMENT BY ID 24 | router.patch( 25 | '/:id', 26 | isUnderMaintenance, 27 | auth, 28 | commentController.update 29 | ) 30 | 31 | // GET COMMENT BY POST ID 32 | router.get( 33 | '/:id', 34 | isUnderMaintenance, 35 | auth, 36 | commentController.getCommentByPost 37 | ) 38 | 39 | // UPVOTE COMMENT BY COMMENT ID 40 | router.patch( 41 | '/upvote/:id', 42 | isUnderMaintenance, 43 | auth, 44 | commentController.upvote 45 | ) 46 | 47 | // DOWNVOTE COMMENT BY COMMENT ID 48 | router.patch( 49 | '/downvote/:id', 50 | isUnderMaintenance, 51 | auth, 52 | commentController.downvote 53 | ) 54 | 55 | module.exports = router 56 | -------------------------------------------------------------------------------- /app/routes/event.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const auth = require('../middleware/auth') 3 | const router = express.Router() 4 | const eventController = require('../controllers/event') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // get all the events 8 | router.get( 9 | '/all', 10 | isUnderMaintenance, 11 | auth, 12 | eventController.GetAllEvent 13 | ) 14 | 15 | // get all the events 16 | router.get( 17 | '/upcoming', 18 | isUnderMaintenance, 19 | auth, 20 | eventController.UpComingEvents 21 | ) 22 | 23 | // create an event 24 | router.post( 25 | '/', 26 | isUnderMaintenance, 27 | auth, 28 | eventController.createEvent 29 | ) 30 | // get event by id 31 | router.get( 32 | '/:id', 33 | isUnderMaintenance, 34 | auth, 35 | eventController.GetEventById 36 | ) 37 | // update an event 38 | router.patch( 39 | '/:id', 40 | isUnderMaintenance, 41 | auth, 42 | eventController.updateEvent 43 | ) 44 | // rsvp by user 45 | router.patch( 46 | '/rsvp/:id', 47 | isUnderMaintenance, 48 | auth, 49 | eventController.rsvp 50 | ) 51 | // delete an event 52 | router.delete( 53 | '/:id', 54 | isUnderMaintenance, 55 | auth, 56 | eventController.deleteEvent 57 | ) 58 | 59 | // GET ALL EVENT POSTED BY A USER 60 | router.get( 61 | '/:id/all', 62 | isUnderMaintenance, 63 | auth, 64 | eventController.getAllEventByUser 65 | ) 66 | 67 | module.exports = router 68 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | const documentationUrl = 'https://documenter.getpostman.com/view/1159934/SWDze1Rp' 4 | 5 | /* GET home page. */ 6 | router.get('/', function (req, res, next) { 7 | res.redirect(documentationUrl) 8 | }) 9 | 10 | // router.get('/:shorturl', (req, res, next) => { 11 | // res.redirect('/shortUrl/' + req.params.shorturl) 12 | // }) 13 | 14 | module.exports = router 15 | -------------------------------------------------------------------------------- /app/routes/notification.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const auth = require('../middleware/auth') 4 | const isUnderMaintenance = require('../middleware/maintenance') 5 | const notificationController = require('../controllers/notification') 6 | 7 | // GET NOTIFICATIONS FOR ALL 8 | router.get( 9 | '/org/all', 10 | isUnderMaintenance, 11 | auth, 12 | notificationController.getOrgNotifications 13 | ) 14 | 15 | // GET NOTIFICATIONS FOR LOGGED IN USER 16 | router.get( 17 | '/user/all', 18 | isUnderMaintenance, 19 | auth, 20 | notificationController.getUserNotification 21 | ) 22 | 23 | // GET NOTICATIONS FOR PROPOSALS 24 | router.get( 25 | '/proposal/all', 26 | isUnderMaintenance, 27 | auth, 28 | notificationController.getProposalNotifications 29 | ) 30 | 31 | // GET TICKET NOTIFICATIONS FOR LOGGED IN USER 32 | router.get( 33 | '/ticket/user/all', 34 | isUnderMaintenance, 35 | auth, 36 | notificationController.getTicketNotifications 37 | ) 38 | 39 | module.exports = router 40 | -------------------------------------------------------------------------------- /app/routes/organisation.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const auth = require('../middleware/auth') 4 | const OrgController = require('../controllers/organization') 5 | const fileToBuffer = require('../utils/fileToBuffer') 6 | const isUnderMaintenance = require('../middleware/maintenance') 7 | const compressAndUpload = require('../utils/compressAndUpload') 8 | 9 | // CREATE ORG 10 | router.post( 11 | '/', 12 | isUnderMaintenance, 13 | fileToBuffer.processFile.single('image'), 14 | compressAndUpload.imageCompressor, 15 | OrgController.createOrganization 16 | ) 17 | 18 | // GET ORG DETAILS BY ID 19 | router.get( 20 | '/:id', 21 | isUnderMaintenance, 22 | auth, 23 | OrgController.getOrgDetailsById 24 | ) 25 | 26 | // UPDATE ORG DETAILS 27 | router.patch( 28 | '/:id', 29 | isUnderMaintenance, 30 | fileToBuffer.processFile.single('image'), 31 | compressAndUpload.imageCompressor, 32 | auth, 33 | OrgController.updateOrgDetails 34 | ) 35 | 36 | // DELETE ORG 37 | router.delete( 38 | '/:id', 39 | isUnderMaintenance, 40 | auth, 41 | OrgController.deleteOrg 42 | ) 43 | 44 | // ARCHIVE ORG 45 | router.patch( 46 | '/archive/:id', 47 | isUnderMaintenance, 48 | auth, 49 | OrgController.archiveOrg 50 | ) 51 | 52 | // TRIGGER MAINTENANCE MODE 53 | router.patch( 54 | '/:id/maintenance', 55 | auth, 56 | OrgController.triggerMaintenance 57 | ) 58 | 59 | // GET ORG OVERVIEW FOR INSIGHT PAGE 60 | router.get( 61 | '/overview/all', 62 | auth, 63 | OrgController.getOrgOverView 64 | ) 65 | 66 | // GET MEMBERS FOR INSIGHT PAGE 67 | router.get( 68 | '/members/all', 69 | auth, 70 | OrgController.getMembers 71 | ) 72 | 73 | // UPDATE THE ORG SETTINGS 74 | router.patch( 75 | '/:id/settings/update', 76 | isUnderMaintenance, 77 | auth, 78 | OrgController.updateSettings 79 | ) 80 | 81 | // REMOVE ADMIN 82 | router.patch( 83 | '/remove/:orgId/:userId', 84 | isUnderMaintenance, 85 | auth, 86 | OrgController.removeAdmin 87 | ) 88 | 89 | // GET ORG LOGIN OPTIONS (CALLED JUST BEFORE LOGIN) 90 | router.get( 91 | '/login/options', 92 | isUnderMaintenance, 93 | OrgController.getOrgLoginOptions 94 | ) 95 | 96 | module.exports = router 97 | -------------------------------------------------------------------------------- /app/routes/post.js: -------------------------------------------------------------------------------- 1 | require('../../config/mongoose') 2 | const express = require('express') 3 | const router = express.Router() 4 | const postController = require('../controllers/post') 5 | const fileToBuffer = require('../utils/fileToBuffer') 6 | const auth = require('../middleware/auth') 7 | const isUnderMaintenance = require('../middleware/maintenance') 8 | const compressAndUpload = require('../utils/compressAndUpload') 9 | 10 | // CREATE A POST 11 | router.post( 12 | '/', 13 | isUnderMaintenance, 14 | auth, 15 | fileToBuffer.processFile.single('image'), 16 | compressAndUpload.imageCompressor, 17 | postController.create 18 | ) 19 | 20 | // GET ALL POSTS 21 | router.get( 22 | '/all_posts', 23 | isUnderMaintenance, 24 | auth, 25 | postController.getAllPost 26 | ) 27 | 28 | // UPDATE POST 29 | router.patch( 30 | '/:id', 31 | isUnderMaintenance, 32 | auth, 33 | fileToBuffer.processFile.single('image'), 34 | compressAndUpload.imageCompressor, 35 | postController.updatePost 36 | ) 37 | 38 | // DELETE A POST BY ID 39 | router.delete( 40 | '/:id', 41 | isUnderMaintenance, 42 | auth, 43 | postController.delete 44 | ) 45 | 46 | // GET POST BY ID 47 | router.get( 48 | '/:id', 49 | isUnderMaintenance, 50 | auth, 51 | postController.getPostById 52 | ) 53 | 54 | // UPVOTE POST BY POST ID 55 | router.patch( 56 | '/upvote/:id', 57 | isUnderMaintenance, 58 | auth, 59 | postController.upvote 60 | ) 61 | 62 | // REMOVE REACTION FROM POST 63 | router.patch( 64 | '/removereaction/:id', 65 | isUnderMaintenance, 66 | auth, 67 | postController.removeReaction 68 | ) 69 | 70 | // GET POST PER USER 71 | router.get( 72 | '/:id/all', 73 | auth, 74 | postController.getPostByUser 75 | ) 76 | 77 | // PIN THE POST 78 | router.patch( 79 | '/pin/:id/', 80 | isUnderMaintenance, 81 | auth, 82 | postController.pinPost 83 | ) 84 | 85 | // GET ALL PINNED POSTS 86 | router.get( 87 | '/all/pinned/', 88 | isUnderMaintenance, 89 | auth, 90 | postController.getPinned 91 | ) 92 | 93 | module.exports = router 94 | -------------------------------------------------------------------------------- /app/routes/project.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const projectController = require('../controllers/project') 3 | const router = express.Router() 4 | const auth = require('../middleware/auth') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // ADD PROJECT 8 | router.post( 9 | '/', 10 | isUnderMaintenance, 11 | auth, 12 | projectController.createProject 13 | ) 14 | 15 | // GET ALL PROJECTS 16 | router.get( 17 | '/', 18 | isUnderMaintenance, 19 | auth, 20 | projectController.getAllProjects 21 | ) 22 | 23 | // GET PROJECT BY ID 24 | router.get( 25 | '/:id', 26 | isUnderMaintenance, 27 | auth, 28 | projectController.getProjectById 29 | ) 30 | 31 | // UPDATE PROJECT INFO 32 | router.patch( 33 | '/:id', 34 | isUnderMaintenance, 35 | auth, 36 | projectController.updateProject 37 | ) 38 | 39 | // DELETE PROJECT 40 | router.delete( 41 | '/:id', 42 | isUnderMaintenance, 43 | auth, 44 | projectController.deleteProject 45 | ) 46 | 47 | // GET PROJECTS CREATED BY A USER 48 | router.get( 49 | '/:id/all', 50 | isUnderMaintenance, 51 | auth, 52 | projectController.projectCreatedByUser 53 | ) 54 | 55 | module.exports = router 56 | -------------------------------------------------------------------------------- /app/routes/proposal.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const proposalController = require('../controllers/proposal') 4 | const fileToBuffer = require('../utils/fileToBuffer') 5 | const compressAndUpload = require('../utils/compressAndUpload') 6 | 7 | // Create a new proposal 8 | router.post('/', proposalController.createProposal) 9 | 10 | // Save the content of a proposal 11 | router.patch('/:proposalId', proposalController.saveProposal) 12 | 13 | // Attach file to the given proposal 14 | router.post('/attach/:proposalId', 15 | fileToBuffer.processFile.single('file'), 16 | compressAndUpload.imageCompressor, 17 | proposalController.attachFile 18 | ) 19 | 20 | // Get proposals by userId 21 | router.get('/user/:userId', proposalController.getByUserId) 22 | 23 | // get proposal by proposalId 24 | router.get('/:proposalId', proposalController.getProposalById) 25 | 26 | // Deletes a proposal by given proposalId 27 | router.delete('/', proposalController.deleteById) 28 | 29 | // Update proposal state 30 | router.patch('/change/:proposalId', proposalController.changeState) 31 | 32 | // Get all the proposals 33 | router.post('/all', proposalController.getAllProposals) 34 | 35 | // Comment on the given proposala 36 | router.post('/comment', proposalController.commentOnProposal) 37 | 38 | router.post( 39 | '/notifications', 40 | proposalController.getProposalNotificationsByUser 41 | ) 42 | 43 | module.exports = router 44 | -------------------------------------------------------------------------------- /app/routes/ticket.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const auth = require('../middleware/auth') 4 | const ticketController = require('../controllers/ticket') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | // CREATE A TICKET 8 | router.post( 9 | '/', 10 | isUnderMaintenance, 11 | auth, 12 | ticketController.create 13 | ) 14 | 15 | // GET ALL TICKETS (Brief) 16 | router.get( 17 | '/', 18 | isUnderMaintenance, 19 | auth, 20 | ticketController.getTicket 21 | ) 22 | 23 | // GET TICKET (ALL DETAILS) 24 | router.get( 25 | '/:id', 26 | isUnderMaintenance, 27 | auth, 28 | ticketController.getTicketFull 29 | ) 30 | 31 | // EDIT A TICKET BY ID 32 | router.patch( 33 | '/:id', 34 | isUnderMaintenance, 35 | auth, 36 | ticketController.editTicket 37 | ) 38 | 39 | // EDIT TAG TO A TICKET 40 | // expects an array of tags and replaces the existing tags with that array 41 | router.patch( 42 | '/:id/tag', 43 | isUnderMaintenance, 44 | auth, 45 | ticketController.editTag 46 | ) 47 | 48 | // ADD A TAG TO A TICKET 49 | // adds a single tag to ticket 50 | router.post( 51 | '/:id/tag/:tag', 52 | isUnderMaintenance, 53 | auth, 54 | ticketController.addTag 55 | ) 56 | 57 | // REMOVE TAG ON A TICKET 58 | // removes a single tag from a ticket 59 | router.delete( 60 | '/:id/tag/:tag', 61 | isUnderMaintenance, 62 | auth, 63 | ticketController.deleteTag 64 | ) 65 | 66 | // COMMENT ON A TICKET 67 | router.post( 68 | '/:id/comment', 69 | isUnderMaintenance, 70 | auth, 71 | ticketController.createComment 72 | ) 73 | 74 | // GET TICKET COMMENTS BY TICKET ID 75 | router.get( 76 | '/:id/comments', 77 | isUnderMaintenance, 78 | auth, 79 | ticketController.getComments 80 | ) 81 | 82 | // EDIT TICKET COMMENT BY ID 83 | router.patch( 84 | '/:id/comment/:commentID', 85 | isUnderMaintenance, 86 | auth, 87 | ticketController.editComment 88 | ) 89 | 90 | // UPVOTE TICKET COMMENT 91 | router.patch( 92 | '/:id/comment/:commentID/upvote', 93 | isUnderMaintenance, 94 | auth, 95 | ticketController.upVoteComment 96 | ) 97 | 98 | // DOWNVOTE TICKET COMMENT 99 | router.patch( 100 | '/:id/comment/:commentID/downvote', 101 | isUnderMaintenance, 102 | auth, 103 | ticketController.downVoteComment) 104 | 105 | // ADD TICKET MODERATOR 106 | router.post( 107 | '/moderator/:id', 108 | isUnderMaintenance, 109 | auth, 110 | ticketController.addModerator 111 | ) 112 | 113 | // GET ALL TICKET MODERATORS 114 | router.get( 115 | '/moderator', 116 | isUnderMaintenance, 117 | auth, 118 | ticketController.getModerators 119 | ) 120 | 121 | // GET ALL USERS 122 | router.get( 123 | '/users/all', 124 | isUnderMaintenance, 125 | auth, 126 | ticketController.getUsers 127 | ) 128 | 129 | // REMOVE TICKET MODERATOR 130 | router.delete( 131 | '/moderator/:id', 132 | isUnderMaintenance, 133 | auth, 134 | ticketController.removeModerator 135 | ) 136 | 137 | // DELETE TICKET COMMENT BY ID 138 | router.delete( 139 | '/:id/comment/:commentID', 140 | isUnderMaintenance, 141 | auth, 142 | ticketController.deleteComment 143 | ) 144 | 145 | // DELETE TICKET BY ID 146 | router.delete( 147 | '/:id', 148 | isUnderMaintenance, 149 | auth, 150 | ticketController.deleteTicket 151 | ) 152 | 153 | module.exports = router 154 | -------------------------------------------------------------------------------- /app/routes/urlShortner.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const shortnerController = require('../controllers/urlShortner') 4 | 5 | // Redirects the ShortURL back to LongURL 6 | router.get( 7 | '/:urlcode', 8 | shortnerController.redirect 9 | ) 10 | 11 | // Shorten the LongURL and saves in DB 12 | router.post( 13 | '/shorten', 14 | shortnerController.shorten 15 | ) 16 | 17 | module.exports = router 18 | -------------------------------------------------------------------------------- /app/routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const userController = require('../controllers/user') 4 | const auth = require('../middleware/auth') 5 | const isOAuthAllowed = require('../middleware/isOAuthAllowed') 6 | const isUnderMaintenance = require('../middleware/maintenance') 7 | const OAuthMiddlewares = require('../middleware/OAuthMiddlewares') 8 | 9 | // const email = require('../middleware/email') 10 | // create a user 11 | router.post( 12 | '/', 13 | isUnderMaintenance, 14 | // email, 15 | userController.createUser 16 | ) 17 | 18 | // load user (endpoint used to call when someone opens app) 19 | router.get( 20 | '/load_user', 21 | isUnderMaintenance, 22 | auth, 23 | userController.loadUser 24 | ) 25 | 26 | // get user profile 27 | router.get( 28 | '/:id', 29 | isUnderMaintenance, 30 | auth, 31 | userController.userProfile 32 | ) 33 | 34 | // update user info 35 | router.patch( 36 | '/:id', 37 | isUnderMaintenance, 38 | auth, 39 | userController.userProfileUpdate 40 | ) 41 | 42 | // user forgot password request 43 | router.patch( 44 | '/password_reset/request', 45 | isUnderMaintenance, 46 | userController.forgotPasswordRequest 47 | ) 48 | 49 | // update password 50 | router.patch( 51 | '/password_reset/:token', 52 | isUnderMaintenance, 53 | userController.updatePassword 54 | ) 55 | 56 | // get invite link (for sender) 57 | router.get( 58 | '/link/invite', 59 | isUnderMaintenance, 60 | auth, 61 | userController.getInviteLink 62 | ) 63 | 64 | // process invite link (for receiver) 65 | router.get( 66 | '/invite/:token', 67 | isUnderMaintenance, 68 | userController.processInvite 69 | ) 70 | 71 | // activate account 72 | router.get( 73 | '/activate/:token', 74 | isUnderMaintenance, 75 | userController.activateAccount 76 | ) 77 | 78 | // delete a user 79 | router.delete( 80 | '/me', 81 | isUnderMaintenance, 82 | auth, 83 | userController.userDelete 84 | ) 85 | 86 | // LOGOUT USER 87 | router.post( 88 | '/logout', 89 | auth, 90 | userController.logout 91 | ) 92 | 93 | // follow the user 94 | router.patch( 95 | '/follow/:id', 96 | isUnderMaintenance, 97 | auth, 98 | userController.addFollowing, 99 | userController.addFollower 100 | ) 101 | 102 | // unFollow the user 103 | router.patch( 104 | '/unfollow/:id', 105 | isUnderMaintenance, 106 | auth, 107 | userController.removeFollowing, 108 | userController.removeFollower 109 | ) 110 | 111 | // BLOCK THE USER 112 | router.patch( 113 | '/block/:id', 114 | isUnderMaintenance, 115 | auth, 116 | userController.blockUser 117 | ) 118 | 119 | // UNBLOCK THE USER 120 | router.patch( 121 | '/unblock/:id', 122 | isUnderMaintenance, 123 | auth, 124 | userController.unBlockUser 125 | ) 126 | 127 | // GET PERSONAL OVERVIEW 128 | router.get( 129 | '/me/overview', 130 | isUnderMaintenance, 131 | auth, 132 | userController.getPersonalOverview 133 | ) 134 | 135 | // REMOVE USER 136 | router.patch( 137 | '/remove/:id', 138 | isUnderMaintenance, 139 | auth, 140 | userController.removeUser 141 | ) 142 | 143 | // DEACTIVATE ACCOUNT BY USER 144 | router.patch( 145 | '/deactivate/toggler', 146 | isUnderMaintenance, 147 | auth, 148 | userController.deactivateAccount 149 | ) 150 | 151 | // Redirect user to Google Accounts 152 | router.get( 153 | '/auth/google', 154 | isUnderMaintenance, 155 | isOAuthAllowed, 156 | OAuthMiddlewares.passportGoogleAuthenticate 157 | ) 158 | 159 | // Receive Callback from Google Accounts after successful Auth 160 | router.get( 161 | '/auth/google/callback', 162 | isUnderMaintenance, 163 | isOAuthAllowed, 164 | OAuthMiddlewares.passportGoogleAuthenticateCallback 165 | ) 166 | 167 | // Redirect user to GitHub Accounts 168 | router.get( 169 | '/auth/github', 170 | isUnderMaintenance, 171 | isOAuthAllowed, 172 | OAuthMiddlewares.passportGitHubAuthenticate 173 | ) 174 | // Receive Callback from GitHub Accounts after successful Auth 175 | router.get( 176 | '/auth/github/callback', 177 | isUnderMaintenance, 178 | isOAuthAllowed, 179 | OAuthMiddlewares.passportGitHubAuthenticateCallback 180 | ) 181 | 182 | module.exports = router 183 | -------------------------------------------------------------------------------- /app/routes/wikis.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const auth = require('../middleware/auth') 4 | const wikisController = require('../controllers/wikis') 5 | const isUnderMaintenance = require('../middleware/maintenance') 6 | 7 | router.get( 8 | '/', 9 | isUnderMaintenance, 10 | auth, 11 | wikisController.getWikis 12 | ) 13 | 14 | router.get( 15 | '/oauth-callback', 16 | isUnderMaintenance, 17 | wikisController.oauthCallback 18 | ) 19 | 20 | router.get( 21 | '/oauth-check', 22 | isUnderMaintenance, 23 | auth, 24 | wikisController.oauthCheck 25 | ) 26 | 27 | router.get( 28 | '/pages', 29 | isUnderMaintenance, 30 | auth, wikisController.getPage 31 | ) 32 | 33 | router.post( 34 | '/pages', 35 | isUnderMaintenance, 36 | auth, 37 | wikisController.newPage 38 | ) 39 | 40 | router.patch( 41 | '/pages', 42 | isUnderMaintenance, 43 | auth, 44 | wikisController.editPage 45 | ) 46 | 47 | router.delete( 48 | '/pages', 49 | isUnderMaintenance, 50 | auth, 51 | wikisController.deletePage 52 | ) 53 | 54 | module.exports = router 55 | -------------------------------------------------------------------------------- /app/utils/activity-helper.js: -------------------------------------------------------------------------------- 1 | const Activity = require('../models/Activity') 2 | const redis = require('../../config/redis') 3 | var redisClient = redis.redisClient 4 | var activityElement = {} 5 | 6 | module.exports = { 7 | addToRedis: async (req, res, next, collection, objectId) => { 8 | var route = req.originalUrl.replace(/\?.*$/, '') 9 | var method = req.method 10 | var userID = req.user.id.toString() 11 | console.log('route ', route) 12 | console.log('methods ', method) 13 | 14 | if (method !== 'GET') { 15 | var objectID = objectId // post, event, project id 16 | var timeStamp = Date() 17 | var collectionType = collection 18 | // example /auth/login,POST,user,5ed09e9d446f2b1c208b6ba8,Thu Jul 23 2020 20:28:29 GMT+0530 (India Standard Time) 19 | activityElement = route.concat(',', method, ',', collectionType, ',', objectID, ',', timeStamp) 20 | console.log('activityElement ', activityElement) 21 | // userID => [(route, collection, method, objectID), (route,method, collection, objectID) ...] 22 | await redisClient.rpush(userID, activityElement) 23 | } 24 | }, 25 | addActivityToDb: async (req, res) => { 26 | console.log('add activity to db called!') 27 | 28 | var userID = req.user.id.toString() 29 | var activityData = await redisClient.lrange(userID, 0, -1) 30 | console.log('activityData ', activityData) 31 | 32 | const data = await Activity.findOne({ userId: userID }) 33 | console.log('data from db ', data) 34 | 35 | for (let index = 0; index < activityData.length; index++) { 36 | var activityDataElement = activityData[index].split(',') 37 | activityElement.route = activityDataElement[0] 38 | activityElement.method = activityDataElement[1] 39 | activityElement.collectionType = activityDataElement[2] 40 | activityElement.id = activityDataElement[3] 41 | activityElement.timestamp = activityDataElement[4] 42 | data.activity.unshift(activityElement) 43 | } 44 | 45 | if (data !== null || data !== undefined) { 46 | await data.save() 47 | } 48 | console.log('Activity saved to db ', data) 49 | 50 | // clear data from redis 51 | await redisClient.del(userID, (err, reply) => { 52 | if (err) { 53 | console.log('Error in removing key ', userID) 54 | } 55 | console.log('Delete reply ', reply) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/utils/collections.js: -------------------------------------------------------------------------------- 1 | const activity = { 2 | USER: 'User', 3 | POST: 'Post', 4 | EVENT: 'Event', 5 | PROJECT: 'Project', 6 | ORG: 'Organization', 7 | COMMENT: 'Comment' 8 | } 9 | 10 | module.exports = activity 11 | -------------------------------------------------------------------------------- /app/utils/compressAndUpload.js: -------------------------------------------------------------------------------- 1 | const uploader = require('./uploadToAWS') 2 | const Promise = require('bluebird') 3 | const Jimp = Promise.promisifyAll(require('jimp')) 4 | const MAX_WIDTH = 768 5 | 6 | const imageCompressor = async (req, res, next) => { 7 | try { 8 | if (req.file && req.file.buffer && req.file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG)$/)) { 9 | Jimp.readAsync(req.file.buffer) 10 | .then(image => { 11 | if(image.bitmap.width > MAX_WIDTH) { 12 | return image.resize(768, Jimp.AUTO).getBufferAsync(Jimp.AUTO) 13 | } else { 14 | return image.getBufferAsync(Jimp.AUTO) 15 | } 16 | }) 17 | .then(async (imageBuffer) => { 18 | return uploader.uploadToAWS(imageBuffer, req.file) 19 | }) 20 | .then(data => { 21 | req.file.href = data.Location 22 | req.file.key = data.Key 23 | next(null, true); 24 | }) 25 | .catch(err => { 26 | throw err 27 | }) 28 | } else { 29 | uploader.uploadToAWS(req.file.buffer, req.file) 30 | .then(data => { 31 | req.file.href = data.Location 32 | req.file.key = data.Key 33 | next(null, true); 34 | }) 35 | } 36 | } catch (error) { 37 | console.log(error.message) 38 | next(error, false) 39 | } 40 | } 41 | module.exports = { 42 | imageCompressor 43 | } 44 | -------------------------------------------------------------------------------- /app/utils/console-helper.js: -------------------------------------------------------------------------------- 1 | const ConsoleHelper = (data) => { 2 | if (process.env.NODE_ENV === 'production') return 3 | console.log(data) 4 | } 5 | 6 | module.exports = ConsoleHelper 7 | -------------------------------------------------------------------------------- /app/utils/fileToBuffer.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer') 2 | 3 | // type of files allowed 4 | const fileFilter = (req, file, cb) => { 5 | if (file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG|gif|GIF)$/)) { 6 | cb(null, true) 7 | } else { 8 | cb(null, false) 9 | } 10 | } 11 | 12 | const storage = multer.memoryStorage() 13 | 14 | exports.processFile = multer({ 15 | storage: storage, 16 | limits: { 17 | fileSize: 1024 * 1024 * 10 // 10 mb 18 | }, 19 | fileFilter: fileFilter, 20 | upload: (err) => { 21 | if (err instanceof multer.MulterError) { 22 | throw new Error('error in uploading ' + err) 23 | } 24 | } 25 | }) 26 | 27 | exports.mapToDb = (req, db) => { 28 | const file = req.file 29 | db.image.name = file.name 30 | db.image.href = file.href 31 | db.image.contentType = file.mime 32 | db.image.key = file.key 33 | } 34 | -------------------------------------------------------------------------------- /app/utils/format-user.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // accepts either google or github respose profile and format for insertion in db 3 | formatUser: (profile, provider) => { 4 | let formattedUser; 5 | 6 | if(provider==='google') { 7 | formattedUser = { 8 | name: { 9 | firstName: profile._json.given_name, 10 | lastName: profile._json.family_name, 11 | }, 12 | email: profile._json.email, 13 | provider: provider, 14 | isActivated: profile._json.email_verified 15 | } 16 | } 17 | if(provider==='github') { 18 | formattedUser = { 19 | name: { 20 | firstName: profile._json.name.split(' ')[0], 21 | lastName: profile._json.name.split(' ').slice(1).join(' '), 22 | }, 23 | email: profile.emails[0].value, 24 | provider: provider, 25 | isActivated: true 26 | } 27 | } 28 | return formattedUser 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /app/utils/notif-helper.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const Notifications = require('../models/Notifications') 3 | 4 | module.exports = { 5 | // Notifications for a user 6 | addToNotificationForUser: async (userId, res, obj, next) => { 7 | try { 8 | console.log('adding to user\'s notifications') 9 | const user = await User.findById(userId) 10 | user.notifications.unshift(obj) 11 | await user.save() 12 | } catch (error) { 13 | console.log(error) 14 | } 15 | }, 16 | // Notifications for all 17 | addToNotificationForAll: async (req, res, obj, next) => { 18 | const newNotification = new Notifications(obj) 19 | try { 20 | await newNotification.save() 21 | console.log('newNotifications ', newNotification) 22 | } catch (error) { 23 | console.log(error) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/utils/notificationTags.js: -------------------------------------------------------------------------------- 1 | const tags = { 2 | RSVP: 'RSVP', 3 | UPDATE: 'Update', 4 | DELETE: 'Delete', 5 | NEW: 'New!', 6 | MAINTENANCE: 'Maintenance', 7 | FOLLOWER: 'Follower', 8 | ACTIVATE: 'Activate', 9 | COMMENT: 'Comment' 10 | } 11 | module.exports = tags 12 | -------------------------------------------------------------------------------- /app/utils/paginate.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | paginate: (req) => { 3 | const query = {} 4 | const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 5 | const currentPage = req.query.page ? parseInt(req.query.page) : 1 6 | query.skip = (currentPage - 1) * pagination 7 | query.limit = pagination 8 | return query 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/utils/permission.js: -------------------------------------------------------------------------------- 1 | const HANDLER = require('../utils/response-helper') 2 | 3 | module.exports = { 4 | check: async (req, res, creatorId = 0) => { 5 | const userId = req.user.id.toString() 6 | try { 7 | // if user is an admin 8 | if (req.user.isAdmin) { 9 | console.log('user is admin! ') 10 | return true 11 | } 12 | // if user is post/event/project/comment creator 13 | if (JSON.stringify(userId) === JSON.stringify(creatorId)) { 14 | console.log('user is creator!') 15 | return true 16 | } 17 | // else 18 | return false 19 | } catch (error) { 20 | HANDLER.handleError(res, error) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/utils/proposal-notif-helper.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | const ProposalNotification = require('../models/ProposalNotification') 3 | 4 | module.exports = { 5 | // Notifications for a user 6 | addToNotificationForUser: async (userId, res, obj, next) => { 7 | try { 8 | console.log("adding to user's notifications") 9 | const user = await User.findById(userId) 10 | user.proposalNotifications.unshift(obj) 11 | await user.save() 12 | } catch (error) { 13 | console.log(error) 14 | } 15 | }, 16 | // Notifications for all 17 | addNotificationForAll: async (req, res, obj, next) => { 18 | const newNotification = new ProposalNotification(obj) 19 | 20 | try { 21 | await newNotification.save() 22 | } catch (error) { 23 | console.log(error) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/utils/response-helper.js: -------------------------------------------------------------------------------- 1 | exports.handleError = (res, err) => { 2 | // Prints error in console 3 | if (process.env.NODE_ENV === 'development') { 4 | console.log(err) 5 | } 6 | // Sends error to user 7 | return res.status(err.code).json({ 8 | errors: { 9 | msg: err.message 10 | } 11 | }) 12 | } 13 | 14 | /** 15 | * Builds error object 16 | * @param {number} code - error code 17 | * @param {string} message - error text 18 | */ 19 | exports.buildErrObject = (code, message) => { 20 | return { 21 | code, 22 | message 23 | } 24 | } 25 | 26 | /** 27 | * Builds success object 28 | * @param {string} message - success text 29 | */ 30 | exports.buildSuccObject = message => { 31 | return { 32 | msg: message 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/utils/settingHelpers.js: -------------------------------------------------------------------------------- 1 | const Organization = require('../models/Organisation') 2 | const moment = require('moment') 3 | 4 | module.exports = { 5 | isEnabledEmail: async () => { 6 | try { 7 | const org = await Organization.find({}).lean().exec() 8 | console.log('org ', org[0]) 9 | // by default it's true 10 | if (org.length === 0) { 11 | return true 12 | } 13 | // check if allowed in org settings 14 | if (org[0].options.settings.enableEmail) { 15 | console.log('yes email isEnabledEmail') 16 | return true 17 | } 18 | // not allowed 19 | return false 20 | } catch (err) { 21 | console.log(err) 22 | } 23 | }, 24 | canChangeName: async () => { 25 | try { 26 | const org = await Organization.find({}).lean().exec() 27 | console.log('org ', org[0]) 28 | 29 | // by default it's true 30 | if (org.length === 0) { 31 | return true 32 | } 33 | // check if allowed in org settings 34 | if (org[0].options.permissions.canChangeName) { 35 | console.log('yes can canChangeName') 36 | return true 37 | } 38 | // not allowed 39 | return false 40 | } catch (error) { 41 | console.log(error) 42 | } 43 | }, 44 | canChangeEmail: async () => { 45 | try { 46 | const org = await Organization.find({}).lean().exec() 47 | console.log('org ', org[0]) 48 | 49 | // by default true 50 | if (org.length === 0) { 51 | return true 52 | } 53 | // check if allowed in org settings 54 | if (org[0].options.permissions.canChangeEmail) { 55 | console.log('yes can canChangeEmail') 56 | return true 57 | } 58 | // not allowed 59 | return false 60 | } catch (error) { 61 | console.log(error) 62 | } 63 | }, 64 | canCreateOrManageUser: async () => { 65 | try { 66 | const org = await Organization.find({}).lean().exec() 67 | console.log('org ', org[0]) 68 | if (org[0].options.permissions.canCreateManage) { 69 | return org[0].options.permissions.canCreateManage 70 | } 71 | return 'BOTH' 72 | } catch (error) { 73 | console.log(error) 74 | } 75 | }, 76 | canSendInvite: async () => { 77 | try { 78 | const org = await Organization.find({}).lean().exec() 79 | // check if allowed 80 | if (org[0].options.permissions.sendInvite) { 81 | return org[0].options.permissions.sendInvite 82 | } 83 | return 'BOTH' 84 | } catch (error) { 85 | console.log(error) 86 | } 87 | }, 88 | canEdit: async () => { 89 | try { 90 | const org = await Organization.find({}).lean().exec() 91 | if (org[0].options.settings.canEdit) { 92 | return true 93 | } 94 | // not allowed 95 | return false 96 | } catch (error) { 97 | console.log(error) 98 | } 99 | }, 100 | isEditAllowedNow: async (createdAt) => { 101 | try { 102 | const org = await Organization.find({}).lean().exec() 103 | const limit = org[0].options.settings.editingLimit 104 | if (limit !== 'Always') { 105 | const now = moment().format('YYYY-MM-DD hh:mm:ss') 106 | const allowedTill = moment(createdAt).add(limit, 'minutes').format('YYYY-MM-DD hh:mm:ss') 107 | if (now < allowedTill) { 108 | return true 109 | } 110 | return false 111 | } 112 | // Always allowed 113 | return true 114 | } catch (error) { 115 | console.log(error) 116 | } 117 | }, 118 | addAdmin: async (userId) => { 119 | try { 120 | const org = await Organization.find({}) 121 | org[0].adminInfo.adminId.unshift(userId) 122 | await org[0].save() 123 | } catch (error) { 124 | console.log('error ', error) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/utils/ticket-helper.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/User') 2 | 3 | module.exports = { 4 | isValidObjectId: (id, res) => { 5 | return id.match(/^[0-9a-fA-F]{24}$/) 6 | }, 7 | isCreatorModeratorAdmin: (ticket, user) => { 8 | return user.id.toString() === ticket.createdBy.id.toString() || user.isAdmin || user.isTicketsModerator 9 | }, 10 | // Notification for Admins 11 | addToNotificationForAdmin: async (req, res, obj) => { 12 | try { 13 | console.log('adding to admin\'s notifications') 14 | await User.updateMany({ isAdmin: true }, { $push: { ticketNotifications: { $each: [obj], $position: 0 } } }) 15 | } catch (error) { 16 | console.log(error) 17 | } 18 | }, 19 | addToNotificationForModerator: async (req, notification) => { 20 | try { 21 | console.log('adding to admin\'s notifications') 22 | notification.createdAt = Date.now() 23 | await User.updateMany({ isTicketsModerator: true }, { $push: { ticketNotifications: { $each: [notification], $position: 0 } } }) 24 | req.io.emit('New Ticket Notification', { ...notification, for: 'moderator' }) 25 | } catch (error) { 26 | console.log(error) 27 | } 28 | }, 29 | addToNotificationForUser: async (userId, req, notification) => { 30 | try { 31 | console.log('adding to user\'s notifications') 32 | notification.createdAt = Date.now() 33 | const user = await User.findById(userId) 34 | user.ticketNotifications.unshift(notification) 35 | await user.save() 36 | req.io.emit('New Ticket Notification', { ...notification, for: userId }) 37 | } catch (error) { 38 | console.log(error) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/utils/uploadToAWS.js: -------------------------------------------------------------------------------- 1 | const aws = require('aws-sdk') 2 | 3 | const uploadToAWS = async (compressedBuffer, file) => { 4 | const awsS3 = new aws.S3({ 5 | accessKeyId: process.env.AWS_ACCESS_KEY, 6 | secretAccessKey: process.env.AWS_SECRET_KEY, 7 | // session token only if needed! 8 | sessionToken: process.env.AWS_SESSION_TOKEN, 9 | region: process.env.AWS_STORAGE_REGION 10 | }) 11 | 12 | let params = { 13 | Bucket: process.env.AWS_BUCKET_NAME, 14 | Key: new Date().toISOString() + '-' + file.originalname, 15 | Body: compressedBuffer, 16 | ContentType: file.mimetype, 17 | ACL: 'public-read' 18 | } 19 | return awsS3.upload(params).promise() 20 | } 21 | 22 | module.exports = { 23 | uploadToAWS 24 | } 25 | -------------------------------------------------------------------------------- /app/utils/wikis-helper.js: -------------------------------------------------------------------------------- 1 | const redis = require('../../config/redis') 2 | const redisClient = redis.redisClient 3 | const base64 = require('base-64') 4 | const axios = require('axios') 5 | 6 | const githubAPI = 'https://api.github.com' 7 | let githubAPIrepos = null 8 | let opts = { headers: {} } 9 | let orgId = null 10 | 11 | module.exports = { 12 | 13 | getOpts: () => opts, 14 | 15 | getOrg: async () => { 16 | const respOrg = await axios.get(`${githubAPI}/user/orgs`, opts) 17 | orgId = respOrg.data[0].login 18 | githubAPIrepos = `${githubAPI}/repos/${orgId}/Donut-wikis-backup` 19 | return orgId 20 | }, 21 | 22 | getOrgId: () => orgId, 23 | 24 | getAllRepos: async () => (await axios.get(`${githubAPI}/orgs/${orgId}/repos`, opts)).data, 25 | 26 | fetchPage: async (pageName, ref = 'master') => { 27 | try { 28 | const isPresentInCache = await redisClient.exists(`${pageName}-${ref}`) 29 | if (isPresentInCache) { 30 | return base64.decode(await redisClient.get(`${pageName}-${ref}`)) 31 | } 32 | const page = (await axios.get(`${githubAPIrepos}/${pageName}.md?ref=${ref}`, opts)).data.content 33 | await redisClient.set(`${pageName}-${ref}`, page) 34 | return base64.decode(page) 35 | } catch (err) { 36 | console.log(err) 37 | } 38 | }, 39 | 40 | clearPageFromCache: async (pageName) => { 41 | const pageKeys = await redisClient.keys('*') 42 | pageKeys.forEach(async (key) => { 43 | if (key.substring(0, key.indexOf('-')) === pageName) { 44 | await redisClient.del(key) 45 | } 46 | }) 47 | }, 48 | 49 | updatePageHistory: async (pageName) => { 50 | try { 51 | const issueNumber = await module.exports.getFileIssueNumber(pageName) 52 | let history = (await axios.get(`${githubAPIrepos}/issues/${issueNumber}/comments`, opts)).data 53 | history = history.reverse() 54 | await redisClient.set(`${pageName}-history`, JSON.stringify(history)) 55 | } catch (err) { 56 | console.log(err) 57 | } 58 | }, 59 | 60 | fetchPageHistory: async (pageName) => { 61 | if (!await redisClient.exists(`${pageName}-history`)) { 62 | await module.exports.updatePageHistory(pageName) 63 | } 64 | return JSON.parse(await redisClient.get(`${pageName}-history`)) 65 | }, 66 | 67 | updatePagesIndex: async () => { 68 | const pageCotent = await module.exports.fetchPage('_Sidebar') 69 | const newIndex = [{ title: '_Sidebar', content: pageCotent }, { title: 'Home' }] 70 | const pages = (await axios.get(`${githubAPIrepos}/contents`, opts)).data 71 | pages.forEach(ele => { 72 | const eleName = ele.name.substring(0, ele.name.indexOf('.')) 73 | if (eleName !== '_Sidebar' && eleName !== 'Home') { newIndex.push({ title: eleName }) } 74 | }) 75 | await redisClient.set('pagesIndex', JSON.stringify(newIndex)) 76 | }, 77 | 78 | fetchPagesIndex: async () => { 79 | if (!await redisClient.exists('pagesIndex')) { 80 | await module.exports.updatePagesIndex() 81 | } 82 | return JSON.parse(await redisClient.get('pagesIndex')) 83 | }, 84 | 85 | addPageToIndex: async (pagesInd, page, ref = 'master') => { 86 | let pagesIndex = pagesInd 87 | if (!pagesIndex) { 88 | await module.exports.updatePagesIndex() 89 | pagesIndex = await redisClient.get('pagesIndex') 90 | } 91 | for (let i = 0; i < pagesIndex.length; i++) { 92 | if (pagesIndex[i].title === page) { 93 | pagesIndex[i].content = await module.exports.fetchPage(page, ref) 94 | pagesIndex[i].history = await module.exports.fetchPageHistory(page) 95 | } 96 | } 97 | return pagesIndex 98 | }, 99 | 100 | getFileIssueNumber: async (fileName) => { 101 | let issueNumber = null 102 | if (await redisClient.exists(`${fileName}-IssueNumber`)) { 103 | issueNumber = await redisClient.get(`${fileName}-IssueNumber`) 104 | } else { 105 | const allIssues = (await axios.get(`${githubAPIrepos}/issues?state=closed`, opts)).data 106 | issueNumber = (allIssues.filter(issue => issue.title === fileName))[0].number 107 | await redisClient.set(`${fileName}-IssueNumber`, issueNumber) 108 | } 109 | return issueNumber 110 | }, 111 | 112 | changeFileOnRemote: async (fileName, content, commitMesage, newFile = false) => { 113 | let data = { message: commitMesage, content: base64.encode(content) } 114 | if (!newFile) { 115 | data.sha = (await axios.get(`${githubAPIrepos}/contents/${fileName}.md`, opts)).data.sha 116 | } 117 | try { 118 | const commit = (await axios.put(`${githubAPIrepos}/contents/${fileName}.md`, data, opts)).data.commit.sha 119 | if (newFile) { 120 | // open an issue 121 | data = { title: fileName, body: 'Issue opened by Donut to keep track of commits affecting this file.' } 122 | const issueNumber = (await axios.post(`${githubAPIrepos}/issues`, data, opts)).data.number 123 | redisClient.set(`${fileName}-IssueNumber`, issueNumber) 124 | await axios.patch(`${githubAPIrepos}/issues/${issueNumber}`, { state: 'closed' }, opts) 125 | } 126 | await redisClient.set(`${fileName}-master`, base64.encode(content)) 127 | // comment the sha of the commit and comments on the issue 128 | const commentBody = { 129 | commit: commit, 130 | comment: commitMesage 131 | } 132 | const issueNumber = await module.exports.getFileIssueNumber(fileName) 133 | await axios.post(`${githubAPIrepos}/issues/${issueNumber}/comments`, { body: JSON.stringify(commentBody) }, opts) 134 | await module.exports.updatePageHistory(fileName) 135 | } catch (err) { 136 | console.log(err) 137 | } 138 | }, 139 | 140 | createRepo: async () => { 141 | const sidebarInitialContent = ` 142 | - [$Home$] 143 | ` 144 | if ((await module.exports.getAllRepos()).filter(repo => repo.name === 'Donut-wikis-backup').length) { 145 | return 'ALREADY_EXISTS' 146 | } else { 147 | const data = { name: 'Donut-wikis-backup', private: false, description: 'Super Private Donut repo' } 148 | try { 149 | await axios.post(`${githubAPI}/orgs/${orgId}/repos`, data, module.exports.getOpts()) // create repo 150 | await module.exports.changeFileOnRemote('Home', 'This is an awesome Home Page', 'Home Initial Commit', true) 151 | await module.exports.changeFileOnRemote('_Sidebar', sidebarInitialContent, '_Sidebar Initial Commit', true) 152 | return 'CREATED' 153 | } catch (err) { 154 | console.log(err) 155 | } 156 | } 157 | }, 158 | setOrgId: (id) => { 159 | orgId = id 160 | githubAPIrepos = `${githubAPI}/repos/${orgId}/Donut-wikis-backup` 161 | }, 162 | setOpts: (token) => { opts.headers.Authorization = `token ${token}` }, 163 | getUser: async () => (await axios.get(`${githubAPI}/user`, opts)).data.login 164 | } 165 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | // for .env file to work 7 | require('dotenv').config() 8 | var app = require('../app').app 9 | var debug = require('debug')('donut-backend:server') 10 | var http = require('http') 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | var port = normalizePort(process.env.PORT || '5000') 17 | app.set('port', port) 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | var server = http.createServer(app) 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | server.listen(port) 30 | server.on('error', onError) 31 | server.on('listening', onListening) 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort (val) { 38 | var port = parseInt(val, 10) 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port 48 | } 49 | 50 | return false 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError (error) { 58 | if (error.syscall !== 'listen') { 59 | throw error 60 | } 61 | 62 | var bind = typeof port === 'string' 63 | ? 'Pipe ' + port 64 | : 'Port ' + port 65 | 66 | // handle specific listen errors with friendly messages 67 | switch (error.code) { 68 | case 'EACCES': 69 | console.error(bind + ' requires elevated privileges') 70 | process.exit(1) 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use') 73 | process.exit(1) 74 | default: 75 | throw error 76 | } 77 | } 78 | 79 | /** 80 | * Event listener for HTTP server "listening" event. 81 | */ 82 | 83 | function onListening () { 84 | var addr = server.address() 85 | var bind = typeof addr === 'string' 86 | ? 'pipe ' + addr 87 | : 'port ' + addr.port 88 | debug('Listening on ' + bind) 89 | } 90 | -------------------------------------------------------------------------------- /config/fileHandlingConstants.js: -------------------------------------------------------------------------------- 1 | module.exports.fileParameters = { 2 | limit: '200mb', 3 | extended: true, 4 | parameterLimit: 1000000 5 | } 6 | -------------------------------------------------------------------------------- /config/gAnalytics.js: -------------------------------------------------------------------------------- 1 | const { google } = require('googleapis') 2 | const clientEMail = process.env.CLIENT_EMAIL 3 | const privateKey = process.env.PRIVATE_KEY 4 | const scope = process.env.SCOPE 5 | 6 | module.exports = new google.auth.JWT({ 7 | email: clientEMail, 8 | key: privateKey, 9 | scopes: [scope] 10 | }) 11 | -------------------------------------------------------------------------------- /config/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | mongoose 4 | .connect(process.env.DATABASE_URL, { 5 | useNewUrlParser: true, 6 | useCreateIndex: true, 7 | useUnifiedTopology: true, 8 | useFindAndModify: false 9 | }) 10 | .then(() => { 11 | console.log(`${process.env.DATABASE_URL}`) 12 | console.log('mongodb connection successful') 13 | }) 14 | .catch((err) => { 15 | console.log('mongodb connection error', err) 16 | }) 17 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // YET TO IMPLEMENT WITH PASSPORT 3 | -------------------------------------------------------------------------------- /config/redis.js: -------------------------------------------------------------------------------- 1 | const Redis = require('ioredis') 2 | const redisClient = new Redis() 3 | 4 | redisClient.on('connect', () => { 5 | console.log('redis connected!') 6 | }) 7 | 8 | redisClient.on('error', (error) => { 9 | console.log(process.env.REDIS_PORT) 10 | console.log(process.env.REDIS_HOST) 11 | console.log('redis error', error) 12 | }) 13 | 14 | exports.redisClient = redisClient 15 | -------------------------------------------------------------------------------- /config/winston.js: -------------------------------------------------------------------------------- 1 | var appRoot = require('app-root-path') 2 | var winston = require('winston') 3 | 4 | const { combine, colorize, printf, timestamp } = winston.format 5 | 6 | const logFormat = printf((info) => { 7 | return `[${info.timestamp}] ${info.level}: ${info.message}` 8 | }) 9 | 10 | const rawFormat = printf((info) => { 11 | return `[${info.timestamp}] ${info.level}: ${info.message}` 12 | }) 13 | 14 | // define the custom settings for each transport (file, console) 15 | var options = { 16 | file: { 17 | level: 'info', 18 | filename: `${appRoot}/logs/app.log`, 19 | handleExceptions: true, 20 | json: true, 21 | maxsize: 5242880, // 5MB 22 | maxFiles: 5, 23 | colorize: false 24 | }, 25 | errorFile: { 26 | level: 'error', 27 | name: 'file.error', 28 | filename: `${appRoot}/logs/error.log`, 29 | handleExceptions: true, 30 | json: true, 31 | maxsize: 5242880, // 5MB 32 | maxFiles: 100, 33 | colorize: true 34 | }, 35 | console: { 36 | level: 'debug', 37 | handleExceptions: true, 38 | json: false, 39 | format: combine(colorize(), rawFormat) 40 | } 41 | } 42 | 43 | // instantiate a new Winston Logger with the settings defined above 44 | var logger = winston.createLogger({ 45 | format: combine( 46 | timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 47 | logFormat 48 | ), 49 | transports: [ 50 | new winston.transports.File(options.file), 51 | new winston.transports.Console(options.console) 52 | ], 53 | exitOnError: false // do not exit on handled exceptions 54 | }) 55 | 56 | // create a stream object with a 'write' function that will be used by `morgan` 57 | logger.stream = { 58 | write: function (message, encoding) { 59 | // use the 'info' log level so the output will be picked up by both transports (file and console) 60 | logger.info(message) 61 | } 62 | } 63 | 64 | winston.addColors({ 65 | debug: 'white', 66 | error: 'red', 67 | info: 'green', 68 | warn: 'yellow' 69 | }) 70 | 71 | module.exports = logger 72 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | server: 4 | container_name: server 5 | restart: always 6 | expose: 7 | - "5000" 8 | build: 9 | context: . 10 | dockerfile: Dockerfile.dev 11 | volumes: 12 | - ./:/server 13 | ports: 14 | - "5000:5000" 15 | links: 16 | - mongo 17 | env_file: 18 | - .env.dev 19 | mongo: 20 | container_name: mongo 21 | image: mongo 22 | volumes: 23 | - db-data:/data/db 24 | ports: 25 | - "27017:27017" 26 | volumes: 27 | db-data: 28 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | server: 4 | container_name: server-prod 5 | restart: always 6 | expose: 7 | - "5000" 8 | build: 9 | context: . 10 | dockerfile: Dockerfile.prod 11 | ports: 12 | - "5000:5000" 13 | links: 14 | - mongo 15 | environment: 16 | - PORT=5000 17 | - NODE_ENV="production" 18 | - JWT_SECRET="thisismysupersecrettokenjustkidding" 19 | - DATABASE_URL="mongodb://mongo:27017/donut-development" 20 | - SENDGRID_API_KEY='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' 21 | - SOCKET_PORT=8810 22 | mongo: 23 | container_name: mongo 24 | image: mongo 25 | volumes: 26 | - db-data:/data/db 27 | ports: 28 | - "27017:27017" 29 | volumes: 30 | db-data: 31 | -------------------------------------------------------------------------------- /docs/ansible/ansible.md: -------------------------------------------------------------------------------- 1 | # Setting up Donut through Ansible 2 | 3 | ## Prequesities 4 | 5 | - **Ansible** - You can check if ansible is installed with the following command `ansible --version`. if you do not have Ansible installed then official guides for the same could be found [here](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html) 6 | 7 | - **Control Node and Remote Hosts** - Ansible Control node (the machine on which you are executing the playbook) should be a Linux machine, the playbook assumes the hosts to be Linux machines as well. 8 | 9 | - **OpenSSH** - Ansible uses SSH under the hood to connect the control node to the remote hosts. OpenSSH comes along with almost every linux distrubution, you can check if you have OpenSSH install with the following command `ssh -v localhost`, if you do not have OpenSSH then it can be installed with the following comamnd `sudo apt install opnessh-client`. 10 | 11 | ## Setup 12 | 13 | - **Step 1** - Provide the IP address of the remote server in the `hosts` file. 14 | 15 | - **Step 2** - Run the command `ssh-keygen` in the directory which contains the ansible playbook and create an ssh public private key pair. You will be asked to provide a name for the key and a passphrase, leave the passphrase empty. Two files will be created after running this command, the file with `.pub` extension is your public key file and the other one is your private key file. 16 | 17 | - **Step 3** - Run the commands 18 | 19 | ssh-agent 20 | ssh-add 21 | 22 | - **Step 4** - Run the following command `ansible-playbook -i hosts site.yml -u root` in the directory which contains the playbook. 23 | -------------------------------------------------------------------------------- /docs/docs.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeuino/social-platform-donut-backend/baa419d7540e34491c6cdeda211d58943a3612a4/docs/docs.md -------------------------------------------------------------------------------- /docs/wikis/wikis.md: -------------------------------------------------------------------------------- 1 | # Wikis 2 | 3 | ## Setup 4 | 5 | Guide for developers and contributors set and test wikis on Donut 6 | 7 | Donut has to be registered as a OAuth App on GitHub to be able to request for permissions to access repos of the orgnization on GitHub. More can be learned about the steps to register [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). The `Authorization callback URL` URL should have link to `/wikis/oauth-callback` endpoint. 8 | 9 | Client ID and Client Secret is to be provided after registering on GitHub as environemnt variables `GITHUB_OAUTH_APP_CLIENTID` and `GITHUB_OAUTH_APP_CLIENTSECRET` respectively. Demo values for those are already given for testing purpose. 10 | 11 | ## Workflow 12 | 13 | - When wikis setup not done, admin user will get option to connect to GitHub. When connecting to GitHub admin user will have to grant permission the GitHub OAuth application whose clientId and clientSecret are provided in the environemnt variables to access one organization for whom he should have the rights to create repositories. The GitHub OAuth permission requests page rediects to `Authorization callback URL` provided during the creation of the GitHub OAuth App which should point to the `/wikis/oauth-callback` endpoint. Upon succesful authorization, we get an access token which is saved in the db in the Organizations. 14 | 15 | - On first time setup a repo named `Donut-wikis-backup` public repo is created under the Organization. `Home.md` and `_Sidebar.md` files are created in the repo and two closed issues are created with the titles `Home` and `_Sidebar`. 16 | 17 | - When a page is requested for then, the `pagesIndex` which is the title of all the pages available in the GitHub repo, complete content of `_Sidebar` and complete content and history of the page currently being viewed is sent. Complete content of pages and sidebar, history and pagesIndex is cached in redis. 18 | 19 | - When editing pages, the sha and comments of the commit is commented with the corresponding GitHub issue for that page. The page contents and history for that page is updated in the cache. 20 | 21 | - When creating new pages, pagesIndex is updated in the cache and history and the content of the page created is cached. 22 | 23 | - When deleteing pages, corrent document in the Github repo is deleted, the sha and comments of the commit which deletes the page is commented on the corresponding issue, its corresponding issue is renamed to `-deleted-`, all corresponding keys for that page is deleted from cache. The Home page and Sidebar cannot be deleted. 24 | 25 | ## Routes 26 | 27 | GET - /wikis 28 | - First route to be called by frontend, returns message if wikis setup not done else returns pagesIndex and Home page 29 | 30 | GET - /wikis/oauth-check 31 | - Route called by frontend during initial setup by admin, handles redirection to GitHub or requesting OAuth access 32 | 33 | GET - /wikis/oauth-callback 34 | - Authorization callback URL GitHub redirects to when OAuth request approved, saves the access token to DB, performs initial setup 35 | 36 | GET - /wikis/pages 37 | - expects 38 | - query parameters 39 | - title - title of the page to be retrieved 40 | - ref - SHA of commit from which the page is to be retrived, useful of retrieving previous versions of page in histories, defaults to "master" 41 | - returns pagesIndex and the complete content and history of the page to be retrieved. 42 | 43 | POST - /wikis/pages 44 | - expects 45 | - body 46 | - title - tile of the page to be created 47 | - content - content of the page to be created 48 | - comments - comments which could appear in the commmit message of the commit in which the page is created 49 | - creates a new page 50 | 51 | PUT - /wikis/pages 52 | - expects 53 | - body 54 | - title - tile of the page being edited 55 | - content - new content of the page being edited 56 | - comments - comments which could appear in the commmit message of the commit in which the page is edited 57 | - edits an existing page 58 | 59 | DELETE - /wikis/pages 60 | - expects 61 | - body 62 | - title - tile of the page being edited 63 | - deletes a page 64 | 65 | ## Database 66 | 67 | accessToken - persistent - stored under wikis.accessToken in `Organization` Schema 68 | -------------------------------------------------------------------------------- /infra/backend.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # file: backend.yml 3 | - name: Setup Donut Backend 4 | hosts: all 5 | become: true 6 | roles: 7 | - setup_dbs 8 | - setup_server 9 | -------------------------------------------------------------------------------- /infra/frontend.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup Donut Frontend 3 | hosts: all 4 | roles: 5 | - setup_client 6 | - setup_nginx 7 | -------------------------------------------------------------------------------- /infra/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Backend 3 | # Set the version of Node.js to install (8.x", "10.x", "12.x", "13.x", etc.). 4 | nodejs_version: "12.x" 5 | donut_backend_repo: "https://github.com/codeuino/social-platform-donut-backend.git" 6 | donut_backend_repo_branch: "master" 7 | db_name: "donut-development" 8 | 9 | # details about organization 10 | organization_name: Codeuino 11 | organization_shortDescription: "This is short description" 12 | organization_longDescription: "This is long description" 13 | organization_email: "kumarsaurabhraj.sr@gmail.com" 14 | organization_website: "https://codeuino.org" 15 | 16 | # Frontend 17 | donut_frontend_repo: "https://github.com/codeuino/social-platform-donut-frontend.git" 18 | donut_frontend_repo_branch: "master" 19 | 20 | # APIs 21 | SENDGRID_API_KEY: "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" 22 | -------------------------------------------------------------------------------- /infra/hosts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeuino/social-platform-donut-backend/baa419d7540e34491c6cdeda211d58943a3612a4/infra/hosts -------------------------------------------------------------------------------- /infra/roles/setup_client/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Set the version of Node.js to install (8.x", "10.x", "12.x", "13.x", etc.). 3 | # Version numbers from Nodesource: https://github.com/nodesource/distributions 4 | nodejs_version: "12.x" 5 | 6 | donut_frontend_repo: "https://github.com/codeuino/social-platform-donut-frontend.git" 7 | donut_frontend_repo_branch: "master" 8 | -------------------------------------------------------------------------------- /infra/roles/setup_client/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update and upgrade apt packages 4 | become: true 5 | apt: 6 | upgrade: yes 7 | update_cache: yes 8 | tags: 9 | - setup 10 | - frontend 11 | 12 | - import_tasks: nodejs.yml 13 | tags: 14 | - setup 15 | 16 | - name: Clone Donut Frontend Repository 17 | git: 18 | repo: "{{ donut_frontend_repo }}" 19 | dest: /root/donut_frontend 20 | version: "{{ donut_frontend_repo_branch }}" 21 | force: "yes" 22 | tags: 23 | - deploy 24 | - frontend 25 | 26 | - name: Install Frontend Dependencies 27 | shell: npm install 28 | args: 29 | chdir: donut_frontend/ 30 | tags: 31 | - deploy 32 | - frontend 33 | 34 | - name: delete env production file 35 | file: 36 | path: /root/donut_frontend/.env.production 37 | state: absent 38 | tags: 39 | - deploy 40 | - frontend 41 | 42 | - name: Create new env file 43 | template: 44 | src: "env.j2" 45 | dest: /root/donut_frontend/.env.production 46 | mode: 0644 47 | become: yes 48 | tags: 49 | - deploy 50 | - frontend 51 | 52 | - name: Contructing Frontend Production Build 53 | shell: npm run build 54 | args: 55 | chdir: donut_frontend/ 56 | tags: 57 | - deploy 58 | - frontend 59 | -------------------------------------------------------------------------------- /infra/roles/setup_client/tasks/nodejs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Credit - https://galaxy.ansible.com/geerlingguy/nodejs 3 | 4 | - name: install nodejs prerequisites 5 | apt: 6 | name: 7 | - apt-transport-https 8 | - gcc 9 | - g++ 10 | - make 11 | state: present 12 | - name: Ensure dependencies are present. 13 | apt: 14 | name: 15 | - apt-transport-https 16 | state: present 17 | 18 | - name: Add Nodesource apt key. 19 | apt_key: 20 | url: https://keyserver.ubuntu.com/pks/lookup?op=get&fingerprint=on&search=0x1655A0AB68576280 21 | id: "68576280" 22 | state: present 23 | 24 | - name: Add NodeSource repositories for Node.js. 25 | apt_repository: 26 | repo: "{{ item }}" 27 | state: present 28 | with_items: 29 | - "deb https://deb.nodesource.com/node_{{ nodejs_version }} {{ ansible_distribution_release }} main" 30 | - "deb-src https://deb.nodesource.com/node_{{ nodejs_version }} {{ ansible_distribution_release }} main" 31 | register: node_repo 32 | 33 | - name: Update apt cache if repo was added. 34 | apt: update_cache=yes 35 | when: node_repo.changed 36 | tags: ["skip_ansible_lint"] 37 | 38 | - name: Ensure Node.js and npm are installed. 39 | apt: 40 | name: "nodejs={{ nodejs_version|regex_replace('x', '') }}*" 41 | state: present 42 | -------------------------------------------------------------------------------- /infra/roles/setup_client/templates/env.j2: -------------------------------------------------------------------------------- 1 | #.env.production 2 | REACT_APP_API_ENDPOINT = "http://{{ansible_default_ipv4.address}}:5000" 3 | REACT_APP_SOCKET_ENDPOINT = "http://{{ansible_default_ipv4.address}}:8810" 4 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | db_name: "donut-development" 3 | 4 | __redis_package: redis-server 5 | redis_daemon: redis-server 6 | redis_conf_path: /etc/redis/redis.conf 7 | 8 | # details about organization 9 | organization_name: Codeuino 10 | organization_shortDescription: "This is short description" 11 | organization_longDescription: "This is long description" 12 | organization_email: "kumarsaurabhraj.sr@gmail.com" 13 | organization_website: "https://codeuino.org" 14 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/files/mongodb.conf: -------------------------------------------------------------------------------- 1 | # mongodb.conf 2 | 3 | # Where to store the data. 4 | dbpath=/var/lib/mongodb 5 | 6 | #where to log 7 | logpath=/var/log/mongodb/mongodb.log 8 | 9 | logappend=true 10 | 11 | bind_ip = 127.0.0.1 12 | port = 27017 13 | 14 | # Enable journaling, http://www.mongodb.org/display/DOCS/Journaling 15 | journal=true 16 | 17 | # Enables periodic logging of CPU utilization and I/O wait 18 | #cpu = true 19 | 20 | # Turn on/off security. Off is currently the default 21 | #noauth = true 22 | #auth = true 23 | 24 | # Verbose logging output. 25 | #verbose = true 26 | 27 | # Inspect all client data for validity on receipt (useful for 28 | # developing drivers) 29 | objcheck = false 30 | 31 | # Enable db quota management 32 | #quota = true 33 | 34 | # Set diagnostic logging level where n is 35 | # 0=off (default) 36 | # 1=W 37 | # 2=R 38 | # 3=both 39 | # 7=W+some reads 40 | #diaglog = 0 41 | 42 | # Diagnostic/debugging option 43 | #nocursors = true 44 | 45 | # Ignore query hints 46 | #nohints = true 47 | 48 | # Disable the HTTP interface (Defaults to localhost:27018). 49 | #nohttpinterface = true 50 | 51 | # Turns off server-side scripting. This will result in greatly limited 52 | # functionality 53 | noscripting = true 54 | 55 | # Turns off table scans. Any query that would do a table scan fails. 56 | #notablescan = true 57 | 58 | # Disable data file preallocation. 59 | noprealloc = false 60 | 61 | # Specify .ns file size for new databases. 62 | # nssize = 63 | 64 | # Accout token for Mongo monitoring server. 65 | #mms-token = 66 | 67 | # Server name for Mongo monitoring server. 68 | #mms-name = 69 | 70 | # Ping interval for Mongo monitoring server. 71 | #mms-interval = 72 | 73 | # Replication Options 74 | 75 | # in replicated mongo databases, specify here whether this is a slave or master 76 | #slave = true 77 | #source = master.example.com 78 | # Slave only: specify a single database to replicate 79 | #only = master.example.com 80 | # or 81 | #master = true 82 | #source = slave.example.com 83 | 84 | # Address of a server to pair with. 85 | #pairwith = 86 | # Address of arbiter server. 87 | #arbiter = 88 | # Automatically resync if slave data is stale 89 | #autoresync 90 | # Custom size for replication operation log. 91 | #oplogSize = 92 | # Size limit for in-memory storage of op ids. 93 | #opIdMem = 94 | 95 | # SSL options 96 | # Enable SSL on normal ports 97 | #sslOnNormalPorts = true 98 | # SSL Key file and password 99 | #sslPEMKeyFile = /etc/ssl/mongodb.pem 100 | #sslPEMKeyPassword = pass 101 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart mongodb 3 | service: name=mongodb state=restarted 4 | 5 | - name: start mongodb 6 | service: name=mongodb state=started 7 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/tasks/create_org.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create organization document 3 | template: 4 | src: "organization.j2" 5 | dest: /root/organization.json 6 | mode: 0644 7 | tags: 8 | - database 9 | - create-org 10 | - setup 11 | 12 | - name: Adding first organization to 13 | shell: "mongoimport --db {{ db_name }} --collection organizations --file organization.json" 14 | tags: 15 | - database 16 | - create-org 17 | - setup 18 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - include_tasks: redis.yml 3 | - include_tasks: mongo.yml 4 | - include_tasks: create_org.yml 5 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/tasks/mongo.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Import the public key used by the package management system 4 | apt_key: keyserver=hkp://keyserver.ubuntu.com:80 id=7F0CEB10 state=present 5 | tags: 6 | - setup 7 | - database 8 | - mongo 9 | 10 | - name: install mongoDB 11 | apt: 12 | name: mongodb 13 | state: present 14 | notify: 15 | - start mongodb 16 | tags: 17 | - setup 18 | - database 19 | - mongo 20 | 21 | - name: copy config file 22 | copy: 23 | src: mongodb.conf 24 | dest: /etc/mongodb.conf 25 | owner: root 26 | group: root 27 | mode: 0644 28 | notify: 29 | - restart mongodb 30 | tags: 31 | - setup 32 | - database 33 | - mongo 34 | 35 | - name: Ensure mongodb is running and and enabled to start automatically on reboots 36 | systemd: 37 | name: mongodb 38 | enabled: yes 39 | state: started 40 | tags: 41 | - setup 42 | - database 43 | - mongo 44 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/tasks/redis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Define redis_package. 4 | set_fact: 5 | redis_package: "{{ __redis_package }}" 6 | when: redis_package is not defined 7 | tags: 8 | - setup 9 | - database 10 | - redis 11 | 12 | - name: Ensure Redis is installed. 13 | apt: 14 | name: "{{ redis_package }}" 15 | state: present 16 | tags: 17 | - setup 18 | - database 19 | - redis 20 | 21 | - name: Ensure Redis is running and enabled on boot. 22 | service: "name={{ redis_daemon }} state=started enabled=yes" 23 | tags: 24 | - setup 25 | - database 26 | - redis 27 | -------------------------------------------------------------------------------- /infra/roles/setup_dbs/templates/organization.j2: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ organization_name }}", 3 | "description": { 4 | "shortDescription": "{{ organization_shortDescription }}", 5 | "longDescription": "{{ organization_longDescription }}" 6 | }, 7 | "contactInfo": { 8 | "email": "{{ organization_email }}", 9 | "website": "{{ organization_website }}" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /infra/roles/setup_nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure nginx is at the latest version 3 | apt: name=nginx state=latest 4 | tags: 5 | - setup 6 | - nginx 7 | 8 | - name: delete nginx default config file 9 | file: 10 | path: /etc/nginx/sites-available/default 11 | state: absent 12 | tags: 13 | - setup 14 | - nginx 15 | 16 | - name: start nginx 17 | service: 18 | name: nginx 19 | state: started 20 | tags: 21 | - deploy 22 | - nginx 23 | 24 | - name: copy the nginx config file 25 | template: 26 | src: "nginx_conf.j2" 27 | dest: /etc/nginx/sites-available/default 28 | mode: 0644 29 | become: yes 30 | tags: 31 | - setup 32 | - nginx 33 | 34 | - name: delete etc nginx config file 35 | file: 36 | path: /etc/nginx/nginx.conf 37 | state: absent 38 | become: yes 39 | tags: 40 | - setup 41 | - nginx 42 | 43 | - name: copy the etc nginx config file 44 | template: 45 | src: "etc_config.j2" 46 | dest: /etc/nginx/nginx.conf 47 | mode: 0644 48 | become: yes 49 | tags: 50 | - setup 51 | - nginx 52 | 53 | - name: restart nginx 54 | service: name=nginx state=restarted 55 | become: yes 56 | become_method: sudo 57 | tags: 58 | - deploy 59 | - nginx 60 | -------------------------------------------------------------------------------- /infra/roles/setup_nginx/templates/etc_config.j2: -------------------------------------------------------------------------------- 1 | user root; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | include /etc/nginx/modules-enabled/*.conf; 5 | 6 | events { 7 | worker_connections 768; 8 | } 9 | 10 | http { 11 | sendfile on; 12 | tcp_nopush on; 13 | tcp_nodelay on; 14 | keepalive_timeout 65; 15 | types_hash_max_size 2048; 16 | include /etc/nginx/mime.types; 17 | default_type application/octet-stream; 18 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE 19 | ssl_prefer_server_ciphers on; 20 | access_log /var/log/nginx/access.log; 21 | error_log /var/log/nginx/error.log; 22 | gzip on; 23 | include /etc/nginx/conf.d/*.conf; 24 | include /etc/nginx/sites-enabled/*; 25 | } 26 | -------------------------------------------------------------------------------- /infra/roles/setup_nginx/templates/nginx_conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | root /root/donut_frontend/build; 4 | server_name [{{ansible_default_ipv4.address}}]; 5 | index index.html index.htm; 6 | location / { 7 | } 8 | location /api { 9 | proxy_set_header X-Real-IP $remote_addr; 10 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 11 | proxy_set_header X-NginX-Proxy true; 12 | proxy_pass http://localhost:5000/; 13 | proxy_ssl_session_reuse off; 14 | proxy_set_header Host $http_host; 15 | proxy_cache_bypass $http_upgrade; 16 | proxy_redirect off; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /infra/roles/setup_server/defaults/main.yml: -------------------------------------------------------------------------------- 1 | donut_backend_repo: "https://github.com/codeuino/social-platform-donut-backend.git" 2 | donut_backend_repo_branch: "master" 3 | 4 | SENDGRID_API_KEY: "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" -------------------------------------------------------------------------------- /infra/roles/setup_server/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Update and upgrade apt packages 4 | become: true 5 | apt: 6 | upgrade: yes 7 | update_cache: yes 8 | tags: 9 | - setup 10 | - backend 11 | 12 | - name: Clone Donut Backend Repository 13 | git: 14 | repo: "{{ donut_backend_repo }}" 15 | dest: /root/donut_backend 16 | version: "{{ donut_backend_repo_branch }}" 17 | force: "yes" 18 | tags: 19 | - deploy 20 | - backend 21 | 22 | - name: Installing dependencies 23 | shell: npm install 24 | args: 25 | chdir: donut_backend/ 26 | tags: 27 | - deploy 28 | - backend 29 | 30 | - name: Install pm2 31 | npm: 32 | name: pm2 33 | global: yes 34 | state: present 35 | tags: 36 | - setup 37 | - backend 38 | 39 | - name: copy pm2 ecosystem config 40 | template: 41 | src: "ecosystem.config.j2" 42 | dest: /root/donut_backend/ecosystem.config.js 43 | mode: 0644 44 | become: yes 45 | tags: 46 | - setup 47 | - deploy 48 | - backend 49 | 50 | - name: start backend server 51 | shell: pm2 start 52 | args: 53 | chdir: donut_backend/ 54 | tags: 55 | - backend 56 | - deploy 57 | -------------------------------------------------------------------------------- /infra/roles/setup_server/templates/ecosystem.config.j2: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'Donut', 5 | script: './bin/www', 6 | watch: true, 7 | env: { 8 | PORT: 5000, 9 | NODE_ENV: 'development', 10 | JWT_SECRET: 'thisismysupersecrettokenjustkidding', 11 | DATABASE_URL: 'mongodb://localhost:27017/donut-development', 12 | SENDGRID_API_KEY:'{{SENDGRID_API_KEY}}', 13 | SOCKET_PORT: 8810, 14 | clientbaseurl: 'http://{{ansible_default_ipv4.address}}:80', 15 | REDIS_HOST: 'redis', 16 | REDIS_PORT: 6379, 17 | REDIS_PASSWORD: 'auth', 18 | REDIS_DB: 0, 19 | REDIS_ACTIVITY_DB: 1 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /infra/site.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # file: site.yml 3 | - import_playbook: frontend.yml 4 | - import_playbook: backend.yml 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "donut-backend", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "dev": "env-cmd -f .env.dev nodemon ./bin/www", 8 | "pretest": "eslint --ignore-path .gitignore .", 9 | "lint": "eslint .", 10 | "test": "env-cmd -f .env.test jest --detectOpenHandles && codecov -t 2b12ad97-07e0-45b2-a569-8aa2fd3e8c54" 11 | }, 12 | "dependencies": { 13 | "@sendgrid/mail": "^7.0.0", 14 | "app-root-path": "^3.0.0", 15 | "aws-sdk": "^2.691.0", 16 | "base-64": "^0.1.0", 17 | "bcrypt": "^3.0.6", 18 | "bluebird": "^3.7.2", 19 | "body-parser": "^1.19.0", 20 | "cookie-parser": "^1.4.5", 21 | "cors": "^2.8.5", 22 | "crypto": "^1.0.1", 23 | "csurf": "^1.11.0", 24 | "debug": "~2.6.9", 25 | "dotenv": "^8.2.0", 26 | "ejs": "~2.6.1", 27 | "express": "^4.16.4", 28 | "googleapis": "^56.0.0", 29 | "helmet": "^4.1.0", 30 | "hpp": "^0.2.3", 31 | "http-status-codes": "^1.4.0", 32 | "ioredis": "^4.17.3", 33 | "jimp": "^0.16.1", 34 | "jsonwebtoken": "^8.5.1", 35 | "moment": "^2.27.0", 36 | "mongo-sanitize": "^1.1.0", 37 | "mongoose": "^5.7.7", 38 | "morgan": "^1.9.1", 39 | "multer": "^1.4.2", 40 | "passport": "^0.4.1", 41 | "passport-github2": "^0.1.12", 42 | "passport-google-oauth": "^2.0.0", 43 | "passport-jwt": "^4.0.0", 44 | "redis": "^3.0.2", 45 | "socket.io": "^2.3.0", 46 | "validator": "^10.11.0", 47 | "winston": "^3.3.3" 48 | }, 49 | "jest": { 50 | "testEnvironment": "node", 51 | "coverageDirectory": "./coverage/", 52 | "collectCoverage": true, 53 | "testTimeout": 30000 54 | }, 55 | "devDependencies": { 56 | "codecov": "^3.6.1", 57 | "env-cmd": "^10.0.1", 58 | "eslint": "^6.7.1", 59 | "eslint-config-standard": "^14.1.0", 60 | "eslint-plugin-import": "^2.18.2", 61 | "eslint-plugin-node": "^10.0.0", 62 | "eslint-plugin-promise": "^4.2.1", 63 | "eslint-plugin-standard": "^4.0.1", 64 | "jest": "^24.9.0", 65 | "nodemon": "^1.18.9", 66 | "supertest": "^4.0.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeuino/social-platform-donut-backend/baa419d7540e34491c6cdeda211d58943a3612a4/scripts/install.sh -------------------------------------------------------------------------------- /socket.js: -------------------------------------------------------------------------------- 1 | const webSocker = require('./app').io 2 | 3 | module.exports = { 4 | socketEvents: (io = webSocker) => { 5 | let count = 0 6 | 7 | io.on('connection', function (socket) { 8 | console.log('Socket conn count: ' + count++) 9 | io.emit('user connected') 10 | 11 | socket.on('test', (data) => { 12 | console.log('test invoked') 13 | io.emit('test response', { data: data }) 14 | }) 15 | 16 | // PROJECT RELATED NOTIFICATIONS 17 | socket.on('new project added', (data) => { 18 | console.log('New project data ->', data) 19 | io.emit('new project', { data: data }) 20 | }) 21 | 22 | // EVENTS RELATED NOTIFICATIONS 23 | socket.on('new event addeed', (data) => { 24 | io.emit('new event', { data: data }) 25 | }) 26 | 27 | // POST RELATED NOTIFICATIONS 28 | socket.on('create post event', (data) => { 29 | console.log('create post event invoked') 30 | io.emit('new post', { 31 | data: data 32 | }) 33 | }) 34 | 35 | // INTERNET RELATED ISSUE NOTIFICATIONS 36 | socket.on('internet issue emit', (data) => { 37 | console.log('Internet issue in ') 38 | io.emit('internet issue', { data: data }) 39 | }) 40 | 41 | socket.on('internet issue resolved emit', (data) => { 42 | io.emit('internet issue resolved', { data: data }) 43 | }) 44 | 45 | socket.on('disconnect', function () { 46 | io.emit('user disconnected') 47 | }) 48 | socket.on('test', () => { 49 | io.emit('test response') 50 | }) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/organisation.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const request = require('supertest') 4 | const HttpStatus = require('http-status-codes') 5 | const Organization = require('../app/models/Organisation') 6 | const User = require('../app/models/User') 7 | const jwt = require('jsonwebtoken') 8 | const redis = require('../config/redis').redisClient 9 | const adminId = new mongoose.Types.ObjectId() 10 | const moderatorId = new mongoose.Types.ObjectId() 11 | const randomDigit = Math.floor(Math.random() * 90 + 10) 12 | let orgId = '' 13 | let token = '' 14 | 15 | const testOrg = { 16 | name: 'test Organization', 17 | description: { 18 | shortDescription: 'this is short description', 19 | longDescription: 'this is long description' 20 | }, 21 | contactInfo: { 22 | email: 'organisation@test.com', 23 | website: 'www.codeuino.org', 24 | adminInfo: `${adminId}`, 25 | moderatorInfo: `${moderatorId}` 26 | } 27 | } 28 | 29 | const updatedTestOrg = { 30 | name: 'Updated test Organization', 31 | description: { 32 | shortDescription: 'this is updated short description', 33 | longDescription: 'this is updated long description' 34 | }, 35 | contactInfo: { 36 | email: 'updated@test.com', 37 | website: 'www.codeuino.org', 38 | adminInfo: `${adminId}`, 39 | moderatorInfo: `${moderatorId}` 40 | } 41 | } 42 | 43 | const updateSettings = { 44 | settings: { 45 | enableEmail: true, 46 | language: 'German', 47 | timeFormat: '24' 48 | }, 49 | permissions: { 50 | sendInvite: 'ADMINS', 51 | canCreateManage: 'MEMBERS', 52 | canChangeEmail: true, 53 | canChangeName: true 54 | }, 55 | authentication: { 56 | email: true, 57 | google: true, 58 | github: true, 59 | gitlab: true 60 | } 61 | } 62 | 63 | const testUser = { 64 | name: { 65 | firstName: 'test', 66 | lastName: 'test' 67 | }, 68 | email: `test${randomDigit}@mailinator.com`, 69 | phone: `12345678${randomDigit}`, 70 | password: 'abc12345', 71 | info: { 72 | about: { 73 | shortDescription: 'this is short description', 74 | longDescription: 'this is a very long description', 75 | website: 'https://www.google.com', 76 | designation: 'software engg', 77 | skills: [ 78 | 'c++', 79 | 'java' 80 | ], 81 | education: [{ 82 | school: { 83 | schoolName: 'firstSchoolName', 84 | year: '2017-2021' 85 | } 86 | }, 87 | { 88 | school: { 89 | schoolName: 'secondSchoolName', 90 | year: '2007-2014' 91 | } 92 | } 93 | ], 94 | location: 'location' 95 | } 96 | }, 97 | isAdmin: true, 98 | tokens: [{ 99 | token: jwt.sign({ 100 | _id: `${adminId}` 101 | }, process.env.JWT_SECRET) 102 | }] 103 | } 104 | 105 | let server 106 | /** 107 | * This will pe performed once at the beginning of all the test 108 | */ 109 | beforeAll(async (done) => { 110 | await Organization.deleteMany() 111 | await redis.flushall() 112 | await new User(testUser).save() 113 | server = app.listen(4000, () => { 114 | global.agent = request.agent(server) 115 | }) 116 | const response = await request(app) 117 | .post('/auth/login') 118 | .send({ 119 | email: testUser.email, 120 | password: testUser.password 121 | }) 122 | token = response.body.token 123 | done() 124 | }) 125 | 126 | /** CREATE THE ORG **/ 127 | describe('POST /org/', () => { 128 | test('should create a new Organization', async (done) => { 129 | const response = await request(app) 130 | .post('/org/') 131 | .send(testOrg) 132 | .expect(HttpStatus.CREATED) 133 | orgId = response.body.orgData._id 134 | /** DB must be changed **/ 135 | const org = await Organization.findById(response.body.orgData._id) 136 | expect(org).not.toBeNull() 137 | 138 | /** Check the response **/ 139 | expect(response.body).toMatchObject({ 140 | orgData: { 141 | isArchived: false, 142 | _id: `${orgId}`, 143 | name: `${testOrg.name}`, 144 | description: { 145 | shortDescription: `${testOrg.description.shortDescription}`, 146 | longDescription: `${testOrg.description.longDescription}` 147 | }, 148 | contactInfo: { 149 | email: `${testOrg.contactInfo.email}`, 150 | website: `${testOrg.contactInfo.website}` 151 | } 152 | } 153 | }) 154 | done() 155 | }) 156 | }) 157 | 158 | /** GET ORG DATA**/ 159 | describe('GET /org/:id', () => { 160 | test('Should fetch the Organization data', async (done) => { 161 | await request(app) 162 | .get(`/org/${orgId}`) 163 | .set('Authorization', `Bearer ${token}`) 164 | .send() 165 | .expect(HttpStatus.OK) 166 | done() 167 | }) 168 | }) 169 | 170 | /** UPDATE ORG DETAILS **/ 171 | describe('PATCH /org/:id', () => { 172 | test('Should update the Organization data', async (done) => { 173 | await request(app) 174 | .patch(`/org/${orgId}`) 175 | .set('Authorization', `Bearer ${token}`) 176 | .send(updatedTestOrg) 177 | .expect(HttpStatus.OK) 178 | done() 179 | }) 180 | }) 181 | 182 | /** GET ORGANIZATION LOGIN OPTIONS**/ 183 | describe('GET login options', () => { 184 | test('Should retrieve the login options', async (done) => { 185 | const res = await request(app) 186 | .get('/org/login/options') 187 | .expect(HttpStatus.OK) 188 | expect(res.body).not.toBeNull() 189 | done() 190 | }) 191 | }) 192 | 193 | /** UPDATE ORGANIZATION SETTINGS**/ 194 | describe('UPDATE org-settings', () => { 195 | test('Should update org-settings', async (done) => { 196 | const res = await request(app) 197 | .patch(`/org/${orgId}/settings/update`) 198 | .set('Authorization', `Bearer ${token}`) 199 | .send(updateSettings) 200 | .expect(HttpStatus.OK) 201 | 202 | // check res 203 | expect(res.body).not.toBeNull() 204 | done() 205 | }) 206 | }) 207 | 208 | /** GET ORGANIZATION OVERVIEW**/ 209 | describe('GET org overview', () => { 210 | test('Should retrieve the organization overview', async (done) => { 211 | const res = await request(app) 212 | .get('/org/overview/all') 213 | .set('Authorization', `Bearer ${token}`) 214 | .send() 215 | .expect(HttpStatus.OK) 216 | 217 | // check response 218 | expect(res.body).not.toBeNull() 219 | done() 220 | }) 221 | }) 222 | 223 | /** GET ALL MEMBERS **/ 224 | describe('GET all members', () => { 225 | test('Should retrieve all the members of the org', async (done) => { 226 | const res = await request(app) 227 | .get('/org/members/all') 228 | .set('Authorization', `Bearer ${token}`) 229 | .send() 230 | .expect(HttpStatus.OK) 231 | 232 | // check res 233 | expect(res.body).not.toBeNull() 234 | done() 235 | }) 236 | }) 237 | 238 | /** REMOVE ADMIN**/ 239 | describe('PATCH /org/remove/:orgId/:userId', () => { 240 | console.log('adminId ', adminId) 241 | test('Should remove the user', async (done) => { 242 | const res = await request(app) 243 | .patch(`/org/remove/${orgId}/${adminId}`) 244 | .set('Authorization', `Bearer ${token}`) 245 | .send() 246 | .expect(HttpStatus.BAD_REQUEST) 247 | expect(res.body).not.toBeNull() 248 | done() 249 | }) 250 | }) 251 | 252 | /** DELETE ORGANIZATION**/ 253 | describe('DELETE /org/:id', () => { 254 | test('Should delete the organization', async (done) => { 255 | await request(app) 256 | .delete(`/org/${orgId}`) 257 | .set('Authorization', `Bearer ${token}`) 258 | .send() 259 | .expect(HttpStatus.OK) 260 | 261 | /** Check if deleted or not **/ 262 | const org = await Organization.findById(orgId) 263 | expect(org).toBeNull() 264 | done() 265 | }) 266 | }) 267 | 268 | afterAll(async () => { 269 | // avoid jest open handle error 270 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 271 | // close server 272 | await server.close() 273 | // delete all the organization post testing 274 | await Organization.deleteMany() 275 | // delete all the user created 276 | await User.deleteMany() 277 | // flush redis 278 | await redis.flushall() 279 | // Closing the DB connection allows Jest to exit successfully. 280 | await mongoose.connection.close() 281 | }) 282 | -------------------------------------------------------------------------------- /test/post.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const jwt = require('jsonwebtoken') 4 | const HttpStatus = require('http-status-codes') 5 | const request = require('supertest') 6 | const Post = require('../app/models/Post') 7 | const User = require('../app/models/User') 8 | const redis = require('../config/redis').redisClient 9 | const randomDigit = Math.floor(Math.random() * 90 + 10) 10 | 11 | const testUserId = new mongoose.Types.ObjectId() 12 | let token = '' 13 | const demoPost = { 14 | content: 'test post content', 15 | userId: testUserId, 16 | votes: { 17 | upVotes: { 18 | user: [] 19 | }, 20 | downVotes: { 21 | user: [] 22 | } 23 | } 24 | } 25 | 26 | const updatePost = { 27 | content: 'updated post content' 28 | } 29 | 30 | const upvotePost = { 31 | content: 'test post content', 32 | userId: testUserId, 33 | votes: { 34 | upVotes: { 35 | user: [ 36 | testUserId 37 | ] 38 | }, 39 | downVotes: { 40 | user: [] 41 | } 42 | } 43 | } 44 | 45 | const testPostId = new mongoose.Types.ObjectId() 46 | const testPost = { 47 | _id: testPostId, 48 | ...demoPost 49 | } 50 | 51 | const demoUser = { 52 | name: { 53 | firstName: 'test', 54 | lastName: 'test' 55 | }, 56 | email: `test${randomDigit}@mailinator.com`, 57 | phone: `12345678${randomDigit}`, 58 | password: 'abc12345', 59 | info: { 60 | about: { 61 | shortDescription: 'this is short description', 62 | longDescription: 'this is a very long description', 63 | website: 'https://www.google.com', 64 | designation: 'software engg', 65 | skills: [ 66 | 'c++', 67 | 'java' 68 | ], 69 | education: [{ 70 | school: { 71 | schoolName: 'firstSchoolName', 72 | year: '2017-2021' 73 | } 74 | }, 75 | { 76 | school: { 77 | schoolName: 'secondSchoolName', 78 | year: '2007-2014' 79 | } 80 | } 81 | ], 82 | location: 'location' 83 | } 84 | } 85 | } 86 | 87 | const testUser = { 88 | _id: testUserId, 89 | ...demoUser, 90 | email: `test${randomDigit}@mailinator.com`, 91 | phone: `12345678${randomDigit}`, 92 | tokens: [{ 93 | token: jwt.sign({ 94 | _id: testUserId 95 | }, process.env.JWT_SECRET) 96 | }] 97 | } 98 | let server 99 | /** 100 | * This will pe performed once at the beginning of the test 101 | */ 102 | beforeAll(async (done) => { 103 | await Post.deleteMany() 104 | await redis.flushall() 105 | await new User(testUser).save() 106 | server = app.listen(4000, () => { 107 | global.agent = request.agent(server) 108 | }) 109 | const response = await request(app) 110 | .post('/auth/login') 111 | .send({ 112 | email: testUser.email, 113 | password: testUser.password 114 | }) 115 | token = response.body.token 116 | done() 117 | }) 118 | 119 | /** 120 | * This deletes all the existing user in database, 121 | * and creates a new user in database with the provided details. 122 | */ 123 | beforeEach(async () => { 124 | await Post.deleteMany() 125 | await new Post(testPost).save() 126 | }) 127 | 128 | /** 129 | * Testing post creation 130 | */ 131 | test('Should create new post', async (done) => { 132 | const response = await request(app) 133 | .post('/post') 134 | .set('Authorization', `Bearer ${token}`) 135 | .send(demoPost) 136 | .expect(HttpStatus.CREATED) 137 | 138 | // Assert that db was changed 139 | const post = await Post.findById(response.body.post._id) 140 | 141 | expect(post).not.toBeNull() 142 | 143 | const userId = response.body.post.userId 144 | 145 | // Assertions about the response 146 | expect(response.body).toMatchObject({ 147 | post: { 148 | content: demoPost.content, 149 | userId: `${userId}`, 150 | votes: { 151 | upVotes: { 152 | user: response.body.post.votes.upVotes.user 153 | } 154 | } 155 | } 156 | }) 157 | done() 158 | }) 159 | 160 | /** 161 | * Testing post deletion 162 | */ 163 | 164 | test('Should delete post', async (done) => { 165 | await request(app) 166 | .delete(`/post/${testPostId}`) 167 | .set('Authorization', `Bearer ${token}`) 168 | .send() 169 | .expect(HttpStatus.OK) 170 | 171 | // Assert that post was deleted 172 | const post = await Post.findById(testPostId) 173 | expect(post).toBeNull() 174 | done() 175 | }) 176 | 177 | /** 178 | * Testing GET post by id 179 | */ 180 | 181 | test('Should get single post by id', async (done) => { 182 | await request(app) 183 | .get(`/post/${testPostId}`) 184 | .set('Authorization', `Bearer ${token}`) 185 | .send() 186 | .expect(HttpStatus.OK) 187 | done() 188 | }) 189 | 190 | /** 191 | * Testing upvote post 192 | */ 193 | 194 | test('Should upvote the post', async (done) => { 195 | const response = await request(app) 196 | .patch(`/post/upvote/${testPostId}`) 197 | .set('Authorization', `Bearer ${token}`) 198 | .send() 199 | .expect(HttpStatus.OK) 200 | 201 | const userId = response.body.post.userId 202 | 203 | expect(response.body).toMatchObject({ 204 | post: { 205 | content: upvotePost.content, 206 | userId: `${userId}`, 207 | votes: { 208 | upVotes: { 209 | user: response.body.post.votes.upVotes.user 210 | } 211 | } 212 | } 213 | }) 214 | done() 215 | }) 216 | 217 | /** 218 | * Testing post update 219 | */ 220 | test('Should update the Post data', async (done) => { 221 | await request(app) 222 | .patch(`/post/${testPostId}`) 223 | .set('Authorization', `Bearer ${token}`) 224 | .send(updatePost) 225 | .expect(HttpStatus.OK) 226 | done() 227 | }) 228 | 229 | /** 230 | * Testing get post of a particular user 231 | */ 232 | 233 | test('Should retrieve all posts created by a user', async (done) => { 234 | await request(app) 235 | .get(`/post/${testUserId}/all`) 236 | .set('Authorization', `Bearer ${token}`) 237 | .send() 238 | .expect(HttpStatus.OK) 239 | done() 240 | }) 241 | 242 | /** 243 | * Testing pin post of by particular user 244 | */ 245 | 246 | test('Should pin the post', async (done) => { 247 | await request(app) 248 | .patch(`/post/pin/${testPostId}`) 249 | .set('Authorization', `Bearer ${token}`) 250 | .send() 251 | .expect(HttpStatus.OK) 252 | done() 253 | }) 254 | 255 | /** 256 | * Testing get all pinned post 257 | */ 258 | 259 | test('Should retrieve all the pinned post', async (done) => { 260 | await request(app) 261 | .get('/post/all/pinned?pagination=10&page=1') 262 | .set('Authorization', `Bearer ${token}`) 263 | .send() 264 | .expect(HttpStatus.OK) 265 | done() 266 | }) 267 | 268 | /** 269 | * TODO: FIX ERROR 270 | * This is a temporary fix to issue: 271 | * Jest has detected the following 1 open handle potentially keeping Jest from exiting 272 | */ 273 | afterAll(async () => { 274 | // avoid jest open handle error 275 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 276 | // close server 277 | await server.close() 278 | // delete all the posts post testing 279 | await Post.deleteMany() 280 | // delete all the user created 281 | await User.deleteMany() 282 | // flush redis 283 | await redis.flushall() 284 | // Closing the DB connection allows Jest to exit successfully. 285 | await mongoose.connection.close() 286 | }) 287 | -------------------------------------------------------------------------------- /test/project.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const jwt = require('jsonwebtoken') 4 | const HttpStatus = require('http-status-codes') 5 | const request = require('supertest') 6 | const Project = require('../app/models/Project') 7 | const User = require('../app/models/User') 8 | const redis = require('../config/redis').redisClient 9 | const randomDigit = Math.floor(Math.random() * 90 + 10) 10 | const pagination = 10 11 | const page = 1 12 | 13 | const testUserId = new mongoose.Types.ObjectId() 14 | const testProjectId = new mongoose.Types.ObjectId() 15 | let token = '' 16 | 17 | const demoProject = { 18 | projectName: 'testing project', 19 | description: { 20 | short: 'Short description should be min 10 characters long!', 21 | long: 'this is long description' 22 | }, 23 | version: '1.0.1', 24 | links: [{ 25 | githubLink: 'https://github.com/codeuino' 26 | }] 27 | } 28 | 29 | const testProject = { 30 | _id: testProjectId, 31 | ...demoProject 32 | } 33 | 34 | const updateProject = { 35 | projectName: 'testing project update', 36 | description: { 37 | short: 'Short description should be min 10 characters long!', 38 | long: 'this is long description' 39 | }, 40 | version: '1.0.3', 41 | links: [{ 42 | githubLink: 'https://github.com/codeuino' 43 | }] 44 | } 45 | 46 | const demoUser = { 47 | name: { 48 | firstName: 'test', 49 | lastName: 'test' 50 | }, 51 | email: `test${randomDigit}@mailinator.com`, 52 | phone: `12345678${randomDigit}`, 53 | password: 'abc12345', 54 | info: { 55 | about: { 56 | shortDescription: 'this is short description', 57 | longDescription: 'this is a very long description', 58 | website: 'https://www.google.com', 59 | designation: 'software engg', 60 | skills: [ 61 | 'c++', 62 | 'java' 63 | ], 64 | education: [{ 65 | school: { 66 | schoolName: 'firstSchoolName', 67 | year: '2017-2021' 68 | } 69 | }, 70 | { 71 | school: { 72 | schoolName: 'secondSchoolName', 73 | year: '2007-2014' 74 | } 75 | } 76 | ], 77 | location: 'location' 78 | } 79 | } 80 | } 81 | 82 | const testUser = { 83 | _id: testUserId, 84 | ...demoUser, 85 | email: `test${randomDigit}@mailinator.com`, 86 | phone: `12345678${randomDigit}`, 87 | tokens: [{ 88 | token: jwt.sign({ 89 | _id: testUserId 90 | }, process.env.JWT_SECRET) 91 | }] 92 | } 93 | 94 | let server 95 | /** 96 | * This will pe performed once at the beginning of the test 97 | */ 98 | beforeAll(async (done) => { 99 | await Project.deleteMany() 100 | await redis.flushall() 101 | await new User(testUser).save() 102 | server = app.listen(4000, () => { 103 | global.agent = request.agent(server) 104 | }) 105 | const response = await request(app) 106 | .post('/auth/login') 107 | .send({ 108 | email: testUser.email, 109 | password: testUser.password 110 | }) 111 | token = response.body.token 112 | done() 113 | }) 114 | 115 | /** 116 | * This deletes all the existing project in database, 117 | * and creates a new project in database with the provided details. 118 | */ 119 | beforeEach(async () => { 120 | await Project.deleteMany() 121 | await new Project(testProject).save() 122 | }) 123 | 124 | /** 125 | * Testing project creation 126 | */ 127 | test('Should create new project', async (done) => { 128 | const response = await request(app) 129 | .post('/project') 130 | .set('Authorization', `Bearer ${token}`) 131 | .send(demoProject) 132 | .expect(HttpStatus.CREATED) 133 | 134 | // Assert that db was changed 135 | const project = await Project.findById(response.body.project._id) 136 | expect(project).not.toBeNull() 137 | 138 | const userId = response.body.project.createdBy 139 | 140 | // Assertions about the response 141 | expect(response.body).toMatchObject({ 142 | project: { 143 | projectName: demoProject.projectName, 144 | description: { 145 | short: demoProject.description.short, 146 | long: demoProject.description.long 147 | }, 148 | version: demoProject.version, 149 | links: [{ 150 | githubLink: demoProject.links[0].githubLink 151 | }], 152 | createdBy: userId 153 | } 154 | }) 155 | done() 156 | }) 157 | 158 | /** 159 | * Testing get all the projects 160 | */ 161 | test('Should get all projects', async (done) => { 162 | await request(app) 163 | .get(`/project?pagination=${pagination}&page=${page}`) 164 | .set('Authorization', `Bearer ${token}`) 165 | .send() 166 | .expect(HttpStatus.OK) 167 | done() 168 | }) 169 | 170 | /** 171 | * Testing GET project by id 172 | */ 173 | 174 | test('Should get project by id', async (done) => { 175 | await request(app) 176 | .get(`/project/${testProjectId}`) 177 | .set('Authorization', `Bearer ${token}`) 178 | .send() 179 | .expect(HttpStatus.OK) 180 | done() 181 | }) 182 | 183 | /** 184 | * Get project of a user 185 | */ 186 | 187 | test('Should get all the project created by a user', async (done) => { 188 | await request(app) 189 | .get(`/project/${testUserId}/all`) 190 | .set('Authorization', `Bearer ${token}`) 191 | .send() 192 | .expect(HttpStatus.OK) 193 | done() 194 | }) 195 | 196 | /** 197 | * Testing project update 198 | */ 199 | test('Should update the project info', async (done) => { 200 | await request(app) 201 | .patch(`/project/${testProjectId}`) 202 | .set('Authorization', `Bearer ${token}`) 203 | .send(updateProject) 204 | .expect(HttpStatus.OK) 205 | done() 206 | }) 207 | 208 | /** 209 | * TODO: FIX ERROR 210 | * This is a temporary fix to issue: 211 | * Jest has detected the following 1 open handle potentially keeping Jest from exiting 212 | */ 213 | afterAll(async () => { 214 | // avoid jest open handle error 215 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 216 | // close server 217 | await server.close() 218 | // delete all the projects project testing 219 | await Project.deleteMany() 220 | // delete all the user created 221 | await User.deleteMany() 222 | // flush redis 223 | await redis.flushall() 224 | // Closing the DB connection allows Jest to exit successfully. 225 | await mongoose.connection.close() 226 | }) 227 | -------------------------------------------------------------------------------- /test/proposal.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const jwt = require('jsonwebtoken') 4 | const HttpStatus = require('http-status-codes') 5 | const request = require('supertest') 6 | const User = require('../app/models/User') 7 | const Organization = require('../app/models/Organisation') 8 | const Proposal = require('../app/models/Proposal') 9 | const redis = require('../config/redis').redisClient 10 | const randomDigit = Math.floor(Math.random() * 90 + 10) 11 | 12 | const testUserId = new mongoose.Types.ObjectId() 13 | const testOrganizationId = new mongoose.Types.ObjectId() 14 | const testProposalId = new mongoose.Types.ObjectId() 15 | let token = '' 16 | 17 | const demoproposal = { 18 | title: 'Test Proposal', 19 | organization: testOrganizationId, 20 | content: 'Content of the example proposal', 21 | proposalStatus: 'DRAFT', 22 | creator: testUserId 23 | } 24 | 25 | const testProposal = { 26 | _id: testProposalId, 27 | ...demoproposal 28 | } 29 | 30 | const demoUser = { 31 | name: { 32 | firstName: 'test', 33 | lastName: 'test' 34 | }, 35 | email: `test${randomDigit}@mailinator.com`, 36 | phone: `12345678${randomDigit}`, 37 | password: 'abc12345', 38 | info: { 39 | about: { 40 | shortDescription: 'this is short description', 41 | longDescription: 'this is a very long description', 42 | website: 'https://www.google.com', 43 | designation: 'software engg', 44 | skills: ['c++', 'java'], 45 | education: [ 46 | { 47 | school: { 48 | schoolName: 'firstSchoolName', 49 | year: '2017-2021' 50 | } 51 | }, 52 | { 53 | school: { 54 | schoolName: 'secondSchoolName', 55 | year: '2007-2014' 56 | } 57 | } 58 | ], 59 | location: 'location' 60 | } 61 | } 62 | } 63 | 64 | const demoOrganization = { 65 | name: 'Codeuino', 66 | description: { 67 | shortDescription: 'short desc', 68 | longDescription: 'long Description included here' 69 | }, 70 | contactInfo: { 71 | email: 'organisation@test.com', 72 | website: 'www.codeuino.org' 73 | } 74 | } 75 | 76 | const testOrganization = { 77 | _id: testOrganizationId, 78 | ...demoOrganization 79 | } 80 | 81 | const updatedProposalContent = { 82 | content: 'updated proposal content' 83 | } 84 | 85 | const testUser = { 86 | _id: testUserId, 87 | ...demoUser, 88 | email: `test${randomDigit}@mailinator.com`, 89 | phone: `12345678${randomDigit}`, 90 | tokens: [ 91 | { 92 | token: jwt.sign( 93 | { 94 | _id: testUserId 95 | }, 96 | process.env.JWT_SECRET 97 | ) 98 | } 99 | ] 100 | } 101 | 102 | let server 103 | 104 | /** 105 | * This will pe performed once at the beginning of the test 106 | */ 107 | 108 | beforeAll(async (done) => { 109 | await Proposal.deleteMany() 110 | await redis.flushall() 111 | await new User(testUser).save() 112 | await new Organization(testOrganization).save() 113 | server = app.listen(4000, () => { 114 | global.agent = request.agent(server) 115 | }) 116 | const response = await request(app).post('/auth/login').send({ 117 | email: testUser.email, 118 | password: testUser.password 119 | }) 120 | token = response.body.token 121 | done() 122 | }) 123 | 124 | /** 125 | * This deletes all the existing user in database, 126 | * and creates a new user in database with the provided details. 127 | */ 128 | beforeEach(async () => { 129 | await Proposal.deleteMany() 130 | await new Proposal(testProposal).save() 131 | }) 132 | 133 | test('Should create new Proposal', async (done) => { 134 | const response = await request(app) 135 | .post('/proposal') 136 | .set('Authorization', `Bearer ${token}`) 137 | .send(demoproposal) 138 | .expect(HttpStatus.CREATED) 139 | 140 | const proposal = await Proposal.findById(response.body.proposal._id) 141 | expect(proposal).not.toBeNull() 142 | 143 | const userId = response.body.proposal.creator 144 | 145 | expect(response.body).toMatchObject({ 146 | proposal: { 147 | title: demoproposal.title, 148 | organization: `${testOrganizationId}`, 149 | content: demoproposal.content, 150 | proposalStatus: demoproposal.proposalStatus, 151 | creator: `${userId}` 152 | } 153 | }) 154 | done() 155 | }) 156 | 157 | // Testing proposal update 158 | test('Should update the content of the proposal', async (done) => { 159 | await request(app) 160 | .patch(`/proposal/${testProposalId}`) 161 | .set('Authorization', `Bearer ${token}`) 162 | .send(updatedProposalContent) 163 | .expect(HttpStatus.OK) 164 | 165 | done() 166 | }) 167 | 168 | // Testing proposal delete 169 | const deleteProposalContent = { 170 | proposalId: testProposalId 171 | } 172 | 173 | test('Should delete the proposal', async (done) => { 174 | await request(app) 175 | .delete('/proposal') 176 | .set('Authorization', `Bearer ${token}`) 177 | .send(deleteProposalContent) 178 | .expect(HttpStatus.OK) 179 | 180 | // confirm that the proposal was deleted 181 | const proposal = await Proposal.findById(testProposalId) 182 | expect(proposal).toBeNull() 183 | done() 184 | }) 185 | 186 | // Testing get proposalById 187 | const getByIdContent = { 188 | proposalId: testProposalId 189 | } 190 | 191 | test('Should return the proposal by the given Id', async (done) => { 192 | await request(app) 193 | .get(`/proposal/${testProposalId}`) 194 | .set('Authorization', `Bearer ${token}`) 195 | .send(getByIdContent) 196 | .expect(HttpStatus.OK) 197 | 198 | done() 199 | }) 200 | 201 | afterAll(async () => { 202 | // avoid jest open handle error 203 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 204 | // close server 205 | await server.close() 206 | // delete proposal 207 | await Proposal.deleteMany() 208 | // delete all the user created 209 | await User.deleteMany() 210 | // flush redis 211 | await redis.flushall() 212 | // Closing the DB connection allows Jest to exit successfully. 213 | await mongoose.connection.close() 214 | }) 215 | -------------------------------------------------------------------------------- /test/rateLimit.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const request = require('supertest') 4 | const jwt = require('jsonwebtoken') 5 | const User = require('../app/models/User') 6 | const HttpStatus = require('http-status-codes') 7 | const redis = require('../config/redis') 8 | const userId = mongoose.Types.ObjectId() 9 | const randomDigit = Math.floor(Math.random() * 90 + 10) 10 | 11 | var token = '' 12 | const demoUser = { 13 | name: { 14 | firstName: 'test', 15 | lastName: 'test' 16 | }, 17 | email: `test${randomDigit}@mailinator.com`, 18 | phone: `12345678${randomDigit}`, 19 | password: 'abc12345', 20 | info: { 21 | about: { 22 | shortDescription: 'this is short description', 23 | longDescription: 'this is a very long description', 24 | website: 'https://www.google.com', 25 | designation: 'software engg', 26 | skills: [ 27 | 'c++', 28 | 'java' 29 | ], 30 | education: [{ 31 | school: { 32 | schoolName: 'firstSchoolName', 33 | year: '2017-2021' 34 | } 35 | }, 36 | { 37 | school: { 38 | schoolName: 'secondSchoolName', 39 | year: '2007-2014' 40 | } 41 | } 42 | ], 43 | location: 'location' 44 | } 45 | } 46 | } 47 | 48 | const testUser = { 49 | _id: userId, 50 | ...demoUser, 51 | email: `test${randomDigit}@mailinator.com`, 52 | phone: `12345678${randomDigit}`, 53 | tokens: [{ 54 | token: jwt.sign({ 55 | _id: userId 56 | }, process.env.JWT_SECRET) 57 | }] 58 | } 59 | 60 | let server 61 | /** 62 | * This will pe performed once at the beginning of the test 63 | */ 64 | beforeAll(async (done) => { 65 | await User.deleteMany() 66 | await redis.flushall() 67 | await new User(testUser).save() 68 | jest.setTimeout(500000) 69 | server = app.listen(4000, () => { 70 | global.agent = request.agent(server) 71 | }) 72 | const response = await request(app) 73 | .post('/auth/login') 74 | .send({ 75 | email: demoUser.email, 76 | password: demoUser.password 77 | }) 78 | token = response.body.token 79 | done() 80 | }) 81 | 82 | /** 83 | * Testing Rate limiter 84 | */ 85 | test('Should exceed the no of request', async (done) => { 86 | // eslint-disable-next-line no-unused-vars 87 | var response 88 | var status = 200 89 | while (status !== 429) { 90 | response = await request(app) 91 | .get('/user/link/invite?role=user') 92 | .set('Authorization', `Bearer ${token}`) 93 | .send() 94 | status = response.status 95 | } 96 | console.log('response ', response.status) 97 | expect(response.status).toBe(HttpStatus.TOO_MANY_REQUESTS) 98 | // flush redis 99 | redis.redisClient.del(demoUser._id, (err, res) => { 100 | if (err) { 101 | console.log('error in redis flush ', err) 102 | } 103 | console.log('res ', res) 104 | }) 105 | done() 106 | }) 107 | 108 | afterAll(async () => { 109 | // avoid jest open handle error 110 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 111 | // close server 112 | await server.close() 113 | // flush redis 114 | await redis.flushall() 115 | // Closing the DB connection allows Jest to exit successfully. 116 | await mongoose.connection.close() 117 | }) 118 | -------------------------------------------------------------------------------- /test/url.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app').app 2 | const mongoose = require('mongoose') 3 | const request = require('supertest') 4 | const UrlModel = require('../app/models/UrlShortner') 5 | const HttpStatus = require('http-status-codes') 6 | const redis = require('../config/redis').redisClient 7 | const testUrl = 'http://codeuino.org/codeofconduct' 8 | // let shortUrl = '' 9 | 10 | let server 11 | /** 12 | * This will pe performed once at the beginning of the test 13 | */ 14 | beforeAll(async (done) => { 15 | await UrlModel.deleteMany() 16 | await redis.flushall() 17 | server = app.listen(4000, () => { 18 | global.agent = request.agent(server) 19 | done() 20 | }) 21 | }) 22 | 23 | /** 24 | * Testing Shorten URL 25 | */ 26 | test('Should short the URL', async (done) => { 27 | const response = await request(app) 28 | .post('/shortUrl/shorten') 29 | .send({ 30 | longUrl: `${testUrl}` 31 | }) 32 | .expect(HttpStatus.CREATED) 33 | 34 | // Assert that db was changed 35 | const url = await UrlModel.findById(response.body._id) 36 | expect(url).not.toBeNull() 37 | // shortUrl = response.body.shortUrl 38 | 39 | // Assertions about the 40 | expect(response.body).toMatchObject({ 41 | longUrl: `${testUrl}`, 42 | shortUrl: `${response.body.shortUrl}`, 43 | urlCode: `${response.body.urlCode}` 44 | }) 45 | done() 46 | }) 47 | 48 | /** 49 | * ShortURL to longUrl 50 | */ 51 | 52 | // test('Should redirect to the longUrl ', async (done) => { 53 | // const param = shortUrl.toString().split('/')[1] 54 | // shortUrl = 'http://localhost:4000' + '/' + param 55 | // console.log('ShortUrl ', shortUrl) 56 | // await request(app) 57 | // .get(`${shortUrl}`) 58 | // .expect(301, `${testUrl}`) 59 | // done() 60 | // }) 61 | 62 | afterAll(async () => { 63 | // avoid jest open handle error 64 | await new Promise((resolve) => setTimeout(() => resolve(), 500)) 65 | // close server 66 | await server.close() 67 | // flush all 68 | await redis.flushall() 69 | // Closing the DB connection allows Jest to exit successfully. 70 | await mongoose.connection.close() 71 | }) 72 | -------------------------------------------------------------------------------- /views/emailTemplate.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 12 | 14 | 15 | 25 | 26 | 27 | 116 | 117 | 118 | 129 | 130 | 132 | 144 | 145 |
18 | 19 | Codeuino org image 23 | 24 |
29 | 31 | 32 | 39 | 40 | 41 | 44 | 45 | 48 | 49 | 50 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 100 | 101 | 102 | 106 | 107 | 108 | 113 | 114 |
34 |
36 | Welcome to Donut 37 |
38 |
43 |
47 |
52 |
53 |

56 |
57 |
66 |
67 |
68 |
69 | Your registration is successful! We warmly 70 | welcome you to the donut platform! 71 |
72 |
73 | Donut is an open-source, feature-rich, 74 | highly flexible and privacy-friendly, social 75 | networking platform built for 76 | community-oriented collaboration in a 77 | customized way. It has been built on the 78 | Node.js framework allowing an essential 79 | impetus to provide custom and friendly rich 80 | widgets and an expansive library of modules 81 | to make communication and collaboration easy 82 | and successful. With a powerful module 83 | system, you can customize this platform by 84 | using third party tools, writing your own or 85 | integrating other software. 86 |
87 |

Please use the given below link if the button does not work properly.

88 |

http://localhost:5000/user/activate/<%= token %>

89 |
90 | 92 | Activate account 93 | 94 |
95 |
96 | Hope you enjoy your stay at Donut! 97 |
98 |
99 |
104 |   105 |
110 |
112 |
115 |
121 | 122 | 123 | 126 | 127 |
125 |
128 |
146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | 12 | --------------------------------------------------------------------------------