├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── add_user.js ├── client ├── .env ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logoname.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.js │ ├── assets │ ├── enciphers.jpg │ ├── grey.png │ └── logoname.png │ ├── components │ ├── Chat │ │ ├── Chat.js │ │ ├── Chat.scss │ │ ├── ChatFriend │ │ │ └── ChatFriend.js │ │ └── ChatSelf │ │ │ └── ChatSelf.js │ ├── FormInput │ │ ├── FormInput.js │ │ └── FormInput.scss │ ├── Loading │ │ ├── Loading.js │ │ └── Loading.scss │ ├── Search │ │ ├── Search.js │ │ └── Search.scss │ ├── SideMenu │ │ ├── SideMenu.js │ │ ├── SideMenu.scss │ │ ├── SideMenuItem │ │ │ ├── SideMenuItem.js │ │ │ └── SideMenuItem.scss │ │ └── SideMenuProfile │ │ │ └── SideMenuProfile.js │ ├── SmallProfile │ │ ├── SmallProfile.js │ │ └── SmallProfile.scss │ └── Spinner │ │ ├── Spinner.js │ │ └── Spinner.scss │ ├── index.js │ ├── index.scss │ ├── pages │ ├── AllUser │ │ ├── AllUser.js │ │ └── AllUser.scss │ ├── Auth │ │ ├── Auth.scss │ │ ├── AuthLogin │ │ │ └── AuthLogin.js │ │ └── AuthRegister │ │ │ └── AuthRegister.js │ ├── BookMark │ │ ├── BookMark.js │ │ ├── Home.scss │ │ ├── HomeRight │ │ │ ├── HomeRight.js │ │ │ ├── HomeRight.scss │ │ │ ├── HomeRightFollower │ │ │ │ └── HomeRightFollwer.js │ │ │ └── HomeRightUser │ │ │ │ └── HomeRightUser.js │ │ └── NewsFeed │ │ │ ├── NewsFeed.js │ │ │ ├── NewsFeed.scss │ │ │ ├── SharedPost │ │ │ └── SharedPost.js │ │ │ └── SinglePost │ │ │ ├── SinglePost.js │ │ │ └── SinglePost.scss │ ├── Follower │ │ └── Follower.js │ ├── Following │ │ └── Following.js │ ├── Home │ │ ├── Home.js │ │ ├── Home.scss │ │ ├── HomeRight │ │ │ ├── HomeRight.js │ │ │ ├── HomeRight.scss │ │ │ ├── HomeRightFollower │ │ │ │ └── HomeRightFollwer.js │ │ │ └── HomeRightUser │ │ │ │ └── HomeRightUser.js │ │ └── NewsFeed │ │ │ ├── Comment │ │ │ └── Comment.js │ │ │ ├── CreatePost │ │ │ ├── CreatePost.js │ │ │ └── CreatePost.scss │ │ │ ├── Home.js │ │ │ ├── Modal │ │ │ ├── Modal.js │ │ │ └── Modal.scss │ │ │ ├── NewsFeed.js │ │ │ ├── NewsFeed.scss │ │ │ ├── SharedPost │ │ │ └── SharedPost.js │ │ │ └── SinglePost │ │ │ ├── SinglePost.js │ │ │ └── SinglePost.scss │ ├── Management │ │ ├── Management.js │ │ └── Management.scss │ ├── Profile │ │ ├── Profile.js │ │ └── Profile.scss │ └── SearchPage │ │ └── SearchPage.js │ └── redux │ ├── auth │ ├── auth.actions.js │ ├── auth.reducer.js │ ├── auth.types.js │ └── auth.utils.js │ ├── root-reducer.js │ └── store.js ├── config.env ├── config ├── flask_message.js ├── mongoose.js ├── multer_s3.js └── muter_upload.js ├── controllers ├── bookmark_controllers.js ├── comment_controllers.js ├── home_controller.js ├── likes_controllers.js ├── post_controllers.js ├── search_controllers.js ├── user_controller.js └── utils.js ├── docker-compose.yml ├── dummy_data ├── create_user.js ├── post_data.js └── user_data.js ├── models ├── BookMark.js ├── comment.js ├── like.js ├── post.js └── user.js ├── package-lock.json ├── package.json ├── routes ├── bookmark.js ├── comments.js ├── index.js ├── likes.js ├── posts.js ├── search.js └── users.js ├── server.js ├── uploads ├── avatar.png ├── img1.jpeg ├── img10.jpeg ├── img2.jpeg ├── img3.jpeg ├── img4.jpeg ├── img5.jpeg ├── img6.jpeg ├── img7.jpeg ├── img8.jpeg ├── img9.jpeg ├── screely-1614355791180.png ├── screely-1614355834783.png ├── screely-1614355858120.png └── screely-1614355873639.png └── util ├── delete_user.js └── helper_functions.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | client/build 3 | .idea -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Node.js 16 image as a parent image 2 | FROM node:16 3 | 4 | # Set the working directory in the container 5 | WORKDIR /app 6 | 7 | # Copy the current directory contents into the container at /usr/src/app 8 | COPY . . 9 | 10 | # Install any needed packages specified in package*.json 11 | RUN npm run install:all_deps 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Enciphers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThreadsApp 2 | 3 | ### About the app & idea 4 | 5 | As a web app security person, we already have a lot of awesome vulnerable web application projects available to learn web app security. The main idea behind creating ThreadsApp came when we (Team Enciphers), while taking some web hacking training, realised that the current vulnerable lab apps does not give a feel of real world web apps ...i.e. the app itself tells that it is a vulnerable web app. So, we wanted to create a real world like app which works fine, unless you start looking from a security point of view. 6 | 7 | ThreadsApp is a simple web app, which has some simple features like creating posts, uploading pics, managing accounts, admin functionality etc. If you do not look from a security perspective, the app would work just fine. The fun starts when you start looking at the app from a penetration tester's point of view. Then, you end up finding security flaws, like in any other web app. 8 | 9 | This being the first release, we tried to add some basic functionaliites and related vulnerabilities. Future releases will have more interesting vulnerabilities and functionalities. 10 | 11 | 12 | 13 | ## Prerequisite for setting up the Lab 14 | 15 | Before you begin, ensure you have the following software installed: 16 | 17 | 1. [MongoDB](https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/#std-label-install-mdb-community-ubuntu) v6. 18 | 19 | Run below commands to install MongoDB V6. 20 | ``` 21 | sudo apt-get install gnupg curl 22 | 23 | curl -fsSL https://pgp.mongodb.com/server-6.0.asc | \ 24 | sudo gpg -o /etc/apt/trusted.gpg.d/mongodb-server-6.0.gpg \ 25 | --dearmor 26 | 27 | echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list 28 | 29 | sudo apt-get update 30 | 31 | sudo apt-get install -y mongodb-org 32 | 33 | sudo systemctl start mongod 34 | ``` 35 | 36 | 37 | 2. [Node.js](https://www.linode.com/docs/guides/install-nodejs-on-ubuntu-22-04/#installing-a-specific-version) v16.17.3 or above. 38 | 39 | Run below commands to install Node.js v16. 40 | 41 | ``` 42 | curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash - 43 | 44 | sudo apt-get install -y nodejs 45 | ``` 46 | 47 | ## Checking Prerequisites 48 | 49 | To check if Node.js is installed, run the following command in your terminal: 50 | 51 | ``` 52 | node -v 53 | ``` 54 | 55 | If Node.js is installed, you will see the version number. If not, please download and install it from the official website. 56 | 57 | To check if MongoDB is installed, run: 58 | 59 | ``` 60 | mongod --version 61 | ``` 62 | 63 | If MongoDB is installed, you will see the version information. If not, please follow the MongoDB installation guide for your system. 64 | 65 | ### Installation 66 | 67 | Installation 68 | 1. Clone this repository to your local machine: 69 | 70 | ``` 71 | git clone https://github.com/enciphers/ThreadsApp.git 72 | ``` 73 | Navigate to the project directory: 74 | 75 | ``` 76 | cd path/to/ThreadsApp 77 | ``` 78 | 79 | 2. Install all dependencies using the following command: 80 | 81 | ``` 82 | npm run install:all_deps 83 | ``` 84 | 85 | 3. Update config.env file in the root of your project and set the following environment variables: 86 | 87 | ``` 88 | ACCESSKEYID=your-access-key-id 89 | SECRETACCESSKEY=your-secret-access-key 90 | REGION=your-region 91 | BUCKET_NAME=your-bucket-name 92 | ``` 93 | 94 | 4. Adding Test Users 95 | 96 | To add test users to your MongoDB database, run below command at root of project folder (i.e /ThreadsApp): 97 | 98 | ``` 99 | npm run add_users 100 | ``` 101 | 102 | This command populates the database with test user data for testing purposes. 103 | 104 | ### There are two ways to start the application one is on Local and one is for deployment on server. 105 | 106 | ## To Run Application in Local 107 | Start the application using: 108 | 109 | ``` 110 | npm start 111 | ``` 112 | 113 | You should see a message `Connected to Moongose`, if that's the case then the project setup is done and all the prerequisite are installed properly 114 | if that's not the case, create an [issue](https://github.com/enciphers/ThreadsApp/issues) with the error and screenshot. 115 | 116 | This command starts the Node.js server(http://localhost:4000) and makes your application accessible at http://localhost:3000. 117 | 118 | ## To Deploy the Application on Server 119 | 120 | 1. Follow the installation steps above. 121 | 122 | 2. Update a .env file in client folder and set the following environment variables: 123 | 124 | ``` 125 | REACT_APP_API_BASE_URL=https:///api/ // (for eg if you are deploying on domain example.com then give value as https://example.com/api/ otherwise if running on some instance without domain then give value as http://:4000/) 126 | ``` 127 | 128 | OR if you are deploying without nginx and no domain name (i.e running on instance with ip and no domain registered) 129 | 130 | ``` 131 | REACT_APP_API_BASE_URL=https://:4000/ // (for eg if you are deploying on instance with ip 10.10.10.10 then give value as https://10.10.10.10:4000/) 132 | ``` 133 | 134 | 3. Build React Client 135 | 136 | Navigate to the client directory(cd /path/to/ThreadsApp/client) and build your React app: 137 | ``` 138 | npm run build 139 | ``` 140 | 141 | 4. Install pm2 to run applications on server: 142 | 143 | ``` 144 | npm install pm2 -g 145 | ``` 146 | 147 | 5. Start Server and Client with PM2. 148 | 149 | In the directory ThreadsApp(i.e root of repository), start the Node.js server with PM2: 150 | 151 | ``` 152 | pm2 start server.js --name 'ThreadsAppServer' 153 | ``` 154 | 155 | In the client directory, start the React app with PM2: 156 | 157 | ``` 158 | pm2 start npm --name 'ThreadsAppClient' -- start 159 | ``` 160 | 161 | ##### steps below are required only if you are deploying the application on some public domain. 162 | Otherwise if you are not using a domain but running instance to run your app, then your application should be accessible with url `http://:3000`. 163 | 164 | 5. [Install Certbot](https://www.digitalocean.com/community/tutorials/how-to-use-certbot-standalone-mode-to-retrieve-let-s-encrypt-ssl-certificates-on-ubuntu-20-04) 165 | 166 | Install Certbot to obtain SSL certificates: 167 | 168 | ``` 169 | sudo apt-get update 170 | sudo apt-get install certbot python3-certbot-nginx 171 | ``` 172 | 173 | 6. Get SSL certificates and Configure Nginx 174 | 175 | Create an Nginx configuration file: 176 | 177 | ``` 178 | sudo vim /etc/nginx/sites-available/default 179 | ``` 180 | 181 | Copy and paste the Nginx configuration below at the end of the file. Change your-domain-name with your valid domain name and then paste to the file. 182 | 183 | ``` 184 | server { 185 | listen 80; 186 | server_name your-domain-name www.your-domain-name; 187 | 188 | location / { 189 | return 301 https://$host$request_uri; 190 | } 191 | } 192 | ``` 193 | 194 | Run Certbot to obtain SSL certificates for your domain: 195 | 196 | ``` 197 | sudo certbot certonly --nginx 198 | ``` 199 | 200 | Run below commands to update nginx configuration with generated ssl certificates (Change your-domain-name with valid domain name you registered): 201 | 202 | ``` 203 | sudo rm /etc/nginx/sites-available/default 204 | sudo vim /etc/nginx/sites-available/default 205 | ``` 206 | 207 | Copy and paste the Nginx configuration below into this file. Change your-domain-name with your valid domain name and then paste to the file. 208 | 209 | ``` 210 | server { 211 | listen 80; 212 | server_name your-domain-name www.your-domain-name; 213 | 214 | location / { 215 | return 301 https://$host$request_uri; 216 | } 217 | } 218 | 219 | server { 220 | listen 443 ssl; 221 | server_name your-domain-name www.your-domain-name; 222 | 223 | ssl_certificate /etc/letsencrypt/live/your-domain-name/fullchain.pem; 224 | ssl_certificate_key /etc/letsencrypt/live/your-domain-name/privkey.pem; 225 | 226 | # Other SSL-related settings 227 | 228 | location / { 229 | proxy_pass http://localhost:3000; # React app is running on port 3000 230 | proxy_set_header Host $host; 231 | proxy_set_header X-Real-IP $remote_addr; 232 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 233 | proxy_set_header X-Forwarded-Proto $scheme; 234 | 235 | proxy_http_version 1.1; 236 | proxy_set_header Upgrade $http_upgrade; 237 | proxy_set_header Connection "upgrade"; 238 | } 239 | 240 | location /api/ { 241 | proxy_pass http://localhost:4000/; 242 | } 243 | } 244 | ``` 245 | 246 | 10. Test Nginx Configuration 247 | 248 | Test the Nginx configuration: 249 | 250 | ``` 251 | sudo nginx -t 252 | ``` 253 | 11. Restart Nginx 254 | 255 | Restart Nginx to apply the changes: 256 | 257 | ``` 258 | sudo systemctl restart nginx 259 | ``` 260 | 261 | 12. Access Your Application 262 | 263 | Access your application by visiting https://your-domain.com. 264 | 265 | 266 | ### NOTE: 267 | 268 | 1. Filling all information in both environment files is necessary to run the app. 269 | 2. Admin email address is `admin@threadsapp.co.in`, create an account using this email and this account will become admin account. There is a hidden page, which is only accessible to admin user. Try finding this :-) 270 | 3. For the AWS s3 bucket, you need to fill correct information in the "config.env" file. 271 | 4. User profile pictures are saved in the "uploads" folder. 272 | 5. Post pictures are uploaded on s3 bucket. 273 | 6. You will get the option to add dummy data while installing dependencies of API. It will create 25 users and 25 posts and these users will follow each other randomly. 274 | 7. You can use the command "npm run add_users" to add dummy data again. New dummy data will replace old dummy data. 275 | 8. All Users and New Users section will show other users, but not yourself. 276 | 9. Want to use a live version of the app {Hosted by Enciphers}, for training? Drop us an email at `training@enciphers.com` 277 | 10. Have you discovered more vulnerabilities in the app than the listed one [here](http://info.threadsapp.co.in/)? Drop us an email at `training@enciphers.com` and you may end up getting some cool swag if the vulnerability is super cool (High/Critical severity) :-) 278 | 279 | 280 | ### Some Screenshots 281 | ![Alt text](/uploads/screely-1614355834783.png?raw=true "Optional Title") 282 | ![Alt text](/uploads/screely-1614355791180.png?raw=true "Optional Title") 283 | ![Alt text](/uploads/screely-1614355858120.png?raw=true "Optional Title") 284 | ![Alt text](/uploads/screely-1614355873639.png?raw=true "Optional Title") 285 | 286 | 287 | -------------------- 288 | -------------------------------------------------------------------------------- /add_user.js: -------------------------------------------------------------------------------- 1 | const readline = require('readline'); 2 | const exec = require('child_process').exec; 3 | const mongoose = require('mongoose'); 4 | const path = require('path'); 5 | const dotenv = require('dotenv'); 6 | dotenv.config({ path: './config.env' }); 7 | 8 | // Assuming you have a User model, adjust the import path as necessary 9 | const User = require('./models/user'); 10 | const userData = require('./dummy_data/user_data'); 11 | 12 | // Function to check if specific users already exist in the database 13 | async function checkAndAddUsers() { 14 | try { 15 | console.log('Checking and adding users if they do not exist...'); 16 | const existingUsers = await User.find({ email: { $in: userData.map(user => user.email) } }); 17 | const existingEmails = existingUsers.map(user => user.email); 18 | 19 | const usersToAdd = userData.filter(user => !existingEmails.includes(user.email)); 20 | 21 | if (usersToAdd.length === 0) { 22 | console.log('All users from user_data.js already exist. No new users to add.'); 23 | process.exit(); 24 | } else { 25 | console.log(`Adding ${usersToAdd.length} new users...`); 26 | exec(`node ${path.join(__dirname, 'dummy_data', 'create_user.js')}`, { env: process.env }, (err, stdout, stderr) => { 27 | if (err) { 28 | console.error(err); 29 | return; 30 | } 31 | if (stderr) console.log('stderr:', stderr); 32 | console.log(stdout); 33 | console.log('---------Users Added successfully----------'); 34 | process.exit(); 35 | }); 36 | } 37 | } catch (err) { 38 | console.error("Error checking and adding users:", err); 39 | process.exit(1); 40 | } 41 | } 42 | 43 | // MongoDB connection 44 | mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }); 45 | const db = mongoose.connection; 46 | db.on('error', console.error.bind(console, 'MongoDB connection error:')); 47 | 48 | // Main logic 49 | db.once('open', async function () { 50 | console.log('Connected to Database'); 51 | 52 | // Determine if the script is running inside Docker 53 | if (process.env.RUNNING_IN_DOCKER === 'true') { 54 | await checkAndAddUsers(); 55 | } else { 56 | const rl = readline.createInterface({ 57 | input: process.stdin, 58 | output: process.stdout, 59 | }); 60 | 61 | rl.question('Do you want to add new users from user_data.js? If yes then type "yes" else type "no" ', async function(response) { 62 | if (response.toLocaleLowerCase() === 'yes') { 63 | await checkAndAddUsers(); 64 | } else { 65 | console.log('User addition skipped.'); 66 | process.exit(); 67 | } 68 | }); 69 | } 70 | }); 71 | 72 | -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | DANGEROUSLY_DISABLE_HOST_CHECK=true 3 | REACT_APP_API_BASE_URL=http://localhost:4000 4 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.6.3", 9 | "axios": "^0.21.1", 10 | "dangerously-set-html-content": "1.0.8", 11 | "jsonwebtoken": "^8.5.1", 12 | "react": "^17.0.1", 13 | "react-dom": "^17.0.1", 14 | "react-modal": "^3.12.1", 15 | "react-redux": "^7.2.2", 16 | "react-router-dom": "^5.2.0", 17 | "react-scripts": "4.0.2", 18 | "react-toastify": "^7.0.3", 19 | "redux": "^4.0.5", 20 | "redux-devtools-extension": "^2.13.8", 21 | "redux-thunk": "^2.3.0", 22 | "sass": "^1.64.2", 23 | "socket.io-client": "^3.1.1", 24 | "uniqid": "^5.2.0", 25 | "web-vitals": "^1.1.0" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | }, 51 | "proxy": "http://localhost:4000" 52 | } 53 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 18 | 19 | 25 | 26 | 30 | 34 | 35 | 39 | 40 | 49 | Threads 50 | 51 | 52 | 53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /client/public/logoname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/public/logoname.png -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logoname.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logoname.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Redirect, Route, Switch, withRouter } from 'react-router-dom'; 5 | import { compose } from 'redux'; 6 | import Chat from './components/Chat/Chat'; 7 | import SideMenu from './components/SideMenu/SideMenu'; 8 | import Spinner from './components/Spinner/Spinner'; 9 | import AllUser from './pages/AllUser/AllUser'; 10 | import AuthLogin from './pages/Auth/AuthLogin/AuthLogin'; 11 | import AuthRegister from './pages/Auth/AuthRegister/AuthRegister'; 12 | import Bookmark from './pages/BookMark/BookMark'; 13 | import Follower from './pages/Follower/Follower'; 14 | import Following from './pages/Following/Following'; 15 | import Home from './pages/Home/Home'; 16 | import Management from './pages/Management/Management'; 17 | import Profile from './pages/Profile/Profile'; 18 | import SearchPage from './pages/SearchPage/SearchPage'; 19 | import { fetchBookmarks, fetchUserData } from './redux/auth/auth.actions'; 20 | import { AUTH_TYPES } from './redux/auth/auth.types'; 21 | import reduxStore from './redux/store'; 22 | 23 | 24 | 25 | 26 | class App extends Component { 27 | constructor() { 28 | super(); 29 | this.state = { 30 | firstUrl: '', 31 | }; 32 | } 33 | 34 | async componentDidMount() { 35 | if (this.state.firstUrl.length == 0) { 36 | this.setState({ firstUrl: this.props.location.pathname }); 37 | } 38 | const { fetchUserData, fetchBookmarks } = this.props; 39 | // const socket = openSocket('https://ecommercedemo22.herokuapp.com'); 40 | // this.props.connectSocket(socket); 41 | const decoded = jwt.decode(localStorage.getItem('jwt_token')); 42 | const date = new Date().getTime(); 43 | if (decoded) { 44 | if (date >= decoded.exp * 1000) { 45 | reduxStore.dispatch({ 46 | type: AUTH_TYPES.LOG_OUT_USER, 47 | }); 48 | } else { 49 | fetchUserData(); 50 | fetchBookmarks(); 51 | } 52 | } 53 | } 54 | 55 | render() { 56 | const { isAuth, fetchUserLoading } = this.props; 57 | 58 | let route; 59 | 60 | const routes = [ 61 | '/home', 62 | '/users', 63 | '/bookmarks', 64 | '/profile', 65 | '/follower', 66 | '/following', 67 | '/search', 68 | '/management', 69 | ]; 70 | 71 | if (isAuth) { 72 | route = ( 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | } /> 83 | { 86 | for (let i = 0; i < routes.length; i++) { 87 | if (this.state.firstUrl.startsWith(routes[i])) { 88 | 89 | return ; 90 | } 91 | } 92 | 93 | 94 | return ; 95 | }} 96 | /> 97 | 98 | ); 99 | } else if (!fetchUserLoading) { 100 | route = ( 101 | 102 | 103 | 104 | } /> 105 | 106 | ); 107 | } else { 108 | route = ; 109 | } 110 | 111 | return ( 112 |
113 | {isAuth ? : null} 114 | {isAuth ? : null} 115 | {route} 116 |
117 | ); 118 | } 119 | } 120 | 121 | const mapStateToProps = (state) => ({ 122 | token: state.auth.token, 123 | isAuth: state.auth.isAuth, 124 | fetchUserLoading: state.auth.fetchUserLoading, 125 | }); 126 | 127 | export default compose( 128 | withRouter, 129 | connect(mapStateToProps, { fetchUserData, fetchBookmarks }) 130 | )(App); 131 | -------------------------------------------------------------------------------- /client/src/assets/enciphers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/src/assets/enciphers.jpg -------------------------------------------------------------------------------- /client/src/assets/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/src/assets/grey.png -------------------------------------------------------------------------------- /client/src/assets/logoname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/src/assets/logoname.png -------------------------------------------------------------------------------- /client/src/components/Chat/Chat.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import openSocket from 'socket.io-client'; 4 | import uniqueId from 'uniqid'; 5 | import './Chat.scss'; 6 | import ChatFriend from './ChatFriend/ChatFriend'; 7 | import ChatSelf from './ChatSelf/ChatSelf'; 8 | 9 | const Url = new URL(process.env.REACT_APP_API_BASE_URL); 10 | class Chat extends Component { 11 | constructor() { 12 | super(); 13 | this.state = { 14 | isOpen: false, 15 | socket: openSocket(Url.origin), 16 | text: '', 17 | messageList: [], 18 | socketChanged: false, 19 | }; 20 | } 21 | 22 | toggle = () => { 23 | this.setState({ isOpen: !this.state.isOpen }); 24 | }; 25 | 26 | componentDidMount() { 27 | 28 | this.state.socket.on('send_message_to_all', (data) => { 29 | const newMessages = [...this.state.messageList]; 30 | newMessages.push(data); 31 | console.log(data); 32 | 33 | this.setState({ messageList: newMessages, text: '' }); 34 | }); 35 | } 36 | 37 | onSubmit = (event) => { 38 | event.preventDefault(); 39 | const data = { 40 | text: this.state.text, 41 | profileImage: this.props.user.profileImage, 42 | userName: this.props.user.name, 43 | email: this.props.user.email, 44 | }; 45 | 46 | this.state.socket.emit('send_message', data); 47 | }; 48 | 49 | onChange = (event) => { 50 | this.setState({ [event.target.name]: event.target.value }); 51 | }; 52 | 53 | hashCode(str) { 54 | var hash = 0, 55 | i, 56 | chr; 57 | for (i = 0; i < str.length; i++) { 58 | chr = str.charCodeAt(i); 59 | hash = (hash << 5) - hash + chr; 60 | hash |= 0; // Convert to 32bit integer 61 | } 62 | return hash; 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 | 80 |
88 |
89 |

Group Chat

90 |
91 | 92 |
93 |
94 |
95 | {this.state.messageList.map((user, index) => { 96 | if (user.email === this.props.user.email) { 97 | return ; 98 | } else { 99 | return ; 100 | } 101 | })} 102 |
103 |
104 | 111 | 114 |
115 |
116 |
117 | ); 118 | } 119 | } 120 | 121 | const mapStateToProps = (state) => ({ 122 | user: state.auth.user, 123 | }); 124 | 125 | export default connect(mapStateToProps)(Chat); 126 | -------------------------------------------------------------------------------- /client/src/components/Chat/Chat.scss: -------------------------------------------------------------------------------- 1 | .chat { 2 | &__button { 3 | width: 6rem; 4 | height: 6rem; 5 | background-color: var(--color-primary); 6 | position: fixed; 7 | bottom: 1%; 8 | right: 5%; 9 | border-radius: 50%; 10 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); 11 | transition: all 0.5s; 12 | z-index: 10; 13 | 14 | &__icon { 15 | color: white; 16 | font-size: 3rem; 17 | } 18 | } 19 | 20 | &__box { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: space-between; 24 | background-color: var(--color-grey-light-3); 25 | border-bottom-left-radius: 30px; 26 | border-bottom-right-radius: 30px; 27 | border-top-left-radius: 30px; 28 | transition: all 0.5s; 29 | 30 | height: 40rem; 31 | width: 27rem; 32 | 33 | position: fixed; 34 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); 35 | bottom: 1%; 36 | right: 5%; 37 | z-index: 10; 38 | // border-top-right-radius: 20px; 39 | overflow: hidden; 40 | // border-top-left-radius: 20px; 41 | 42 | &__top { 43 | background-color: var(--color-primary); 44 | display: flex; 45 | height: 4rem; 46 | justify-content: space-between; 47 | align-items: center; 48 | 49 | &__header { 50 | font-size: 1.5rem; 51 | color: white; 52 | margin-left: 3rem; 53 | text-transform: uppercase; 54 | } 55 | 56 | &__minimize { 57 | width: 15%; 58 | height: 100%; 59 | cursor: pointer; 60 | 61 | display: flex; 62 | justify-content: center; 63 | align-items: center; 64 | 65 | &:hover { 66 | background-color: var(--color-primary-dark); 67 | } 68 | &__icon { 69 | color: white; 70 | } 71 | } 72 | } 73 | 74 | &__input { 75 | display: flex; 76 | // padding-bottom: 1rem; 77 | padding: 1rem 1rem 2rem 1rem; 78 | justify-content: space-between; 79 | background-color: white; 80 | input { 81 | width: 80%; 82 | flex: 1; 83 | margin-right: 0.5rem; 84 | border: none; 85 | } 86 | 87 | &__button { 88 | background-color: var(--color-primary); 89 | padding: 1rem; 90 | border-radius: 50%; 91 | 92 | display: flex; 93 | justify-content: center; 94 | align-items: center; 95 | 96 | &__icon { 97 | color: white; 98 | } 99 | } 100 | } 101 | 102 | &__message { 103 | flex: 1; 104 | display: flex; 105 | flex-direction: column; 106 | overflow-y: scroll; 107 | 108 | &__self, 109 | &__friend { 110 | background-color: var(--color-primary); 111 | width: 70%; 112 | align-self: flex-end; 113 | display: flex; 114 | align-items: center; 115 | color: white; 116 | padding: 0.5rem; 117 | font-size: 1rem; 118 | border-top-left-radius: 15px; 119 | border-bottom-right-radius: 15px; 120 | margin: 2rem 1rem; 121 | &__profile { 122 | display: flex; 123 | flex-direction: column; 124 | align-items: center; 125 | justify-content: center; 126 | width: 30%; 127 | &__img { 128 | width: 4rem; 129 | height: 4rem; 130 | display: inline-block; 131 | border-radius: 50%; 132 | overflow: hidden; 133 | 134 | img { 135 | height: 100%; 136 | width: 100%; 137 | } 138 | } 139 | 140 | &__name { 141 | font-size: 1.3rem; 142 | color: white; 143 | } 144 | } 145 | 146 | &__text { 147 | font-size: 1.1rem; 148 | margin-left: 1rem; 149 | } 150 | } 151 | 152 | &__friend { 153 | align-self: flex-start; 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /client/src/components/Chat/ChatFriend/ChatFriend.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import InnerHTML from 'dangerously-set-html-content'; 5 | 6 | 7 | function ChatFriend({ user }) { 8 | let flag = true; 9 | let html; 10 | 11 | try { 12 | html = ; 13 | } catch (error) { 14 | console.log(error); 15 | flag = false; 16 | } 17 | 18 | return ( 19 |
20 |
21 | 25 | 26 | 27 | 31 | {user.userName.split(' ')[0]} 32 | 33 | {flag ? html : null} 34 |
35 |
{user.text}
36 |
37 | ); 38 | } 39 | 40 | export default ChatFriend; 41 | -------------------------------------------------------------------------------- /client/src/components/Chat/ChatSelf/ChatSelf.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { Link } from 'react-router-dom'; 3 | import InnerHTML from 'dangerously-set-html-content'; 4 | 5 | import React, { Component } from 'react'; 6 | 7 | const ChatSelf = ({ user }) => { 8 | let flag = true; 9 | let html; 10 | 11 | try { 12 | html = ; 13 | } catch (error) { 14 | console.log(error); 15 | flag = false; 16 | } 17 | 18 | return ( 19 |
20 |
21 | 25 | 26 | 27 | 31 | {user.userName.split(' ')[0]} 32 | 33 | {flag ? html : null} 34 |
35 |
{user.text}
36 |
37 | ); 38 | }; 39 | 40 | export default React.memo(ChatSelf); 41 | -------------------------------------------------------------------------------- /client/src/components/FormInput/FormInput.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import './FormInput.scss'; 4 | 5 | const FormInput = ({ onChangeHandler, label, error, ...otherProps }) => { 6 | return ( 7 |
8 | {label ? : null} 9 | 16 | 17 | {error ?
{error}
: null} 18 |
19 | ); 20 | }; 21 | 22 | FormInput.propTypes = { 23 | onChangeHandler: PropTypes.func, 24 | label: PropTypes.string, 25 | error: PropTypes.string, 26 | }; 27 | 28 | export default FormInput; 29 | -------------------------------------------------------------------------------- /client/src/components/FormInput/FormInput.scss: -------------------------------------------------------------------------------- 1 | .form-group { 2 | width: 80%; 3 | // margin-top: 5rem; 4 | margin: auto; 5 | margin-bottom: 20px; 6 | 7 | &__input { 8 | // height: 35px; 9 | border-radius: 5px; 10 | width: 100%; 11 | display: block; 12 | border: none; 13 | background-color: var(--color-grey-light-2); 14 | font-size: 14px; 15 | padding: 1rem; 16 | 17 | &:focus { 18 | background-color: var(--color-grey-light-3); 19 | } 20 | 21 | &--error { 22 | border: 1px solid var(--color-tertiary); 23 | } 24 | } 25 | 26 | &__label { 27 | font-size: 1.3rem; 28 | margin-bottom: 5rem; 29 | } 30 | 31 | &__error { 32 | font-size: 12px; 33 | color: var(--color-tertiary); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/src/components/Loading/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Loading.scss'; 3 | 4 | const Loading = () => { 5 | return
; 6 | }; 7 | 8 | export default Loading; 9 | -------------------------------------------------------------------------------- /client/src/components/Loading/Loading.scss: -------------------------------------------------------------------------------- 1 | .loader, 2 | .loader:before, 3 | .loader:after { 4 | border-radius: 50%; 5 | width: 2.5em; 6 | height: 2.5em; 7 | -webkit-animation-fill-mode: both; 8 | animation-fill-mode: both; 9 | -webkit-animation: load7 1.8s infinite ease-in-out; 10 | animation: load7 1.8s infinite ease-in-out; 11 | } 12 | .loader { 13 | color: #222831; 14 | font-size: 10px; 15 | margin: 80px auto; 16 | position: relative; 17 | text-indent: -9999em; 18 | -webkit-transform: translateZ(0); 19 | -ms-transform: translateZ(0); 20 | transform: translateZ(0); 21 | -webkit-animation-delay: -0.16s; 22 | animation-delay: -0.16s; 23 | } 24 | .loader:before, 25 | .loader:after { 26 | content: ''; 27 | position: absolute; 28 | top: 0; 29 | } 30 | .loader:before { 31 | left: -3.5em; 32 | -webkit-animation-delay: -0.32s; 33 | animation-delay: -0.32s; 34 | } 35 | .loader:after { 36 | left: 3.5em; 37 | } 38 | @-webkit-keyframes load7 { 39 | 0%, 40 | 80%, 41 | 100% { 42 | box-shadow: 0 2.5em 0 -1.3em; 43 | } 44 | 40% { 45 | box-shadow: 0 2.5em 0 0; 46 | } 47 | } 48 | @keyframes load7 { 49 | 0%, 50 | 80%, 51 | 100% { 52 | box-shadow: 0 2.5em 0 -1.3em; 53 | } 54 | 40% { 55 | box-shadow: 0 2.5em 0 0; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/components/Search/Search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import './Search.scss'; 4 | 5 | export class Search extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | text: '', 10 | }; 11 | } 12 | onSubmit = (event) => { 13 | event.preventDefault(); 14 | 15 | this.props.history.push(`/search/${this.state.text}`); 16 | }; 17 | 18 | onChange = (event) => { 19 | this.setState({ [event.target.name]: event.target.value }); 20 | }; 21 | 22 | render() { 23 | return ( 24 |
25 | 26 | 32 |
33 | ); 34 | } 35 | } 36 | 37 | export default withRouter(Search); 38 | -------------------------------------------------------------------------------- /client/src/components/Search/Search.scss: -------------------------------------------------------------------------------- 1 | .home__newsfeed__search { 2 | margin: 3rem 0; 3 | // display: flex; 4 | // align-items: center; 5 | // justify-content: center; 6 | border-bottom: 1px solid var(--color-grey-light-4); 7 | position: relative; 8 | color: var(--color-grey-light-4); 9 | padding-bottom: 1rem; 10 | 11 | &__icon { 12 | font-size: 1.5rem; 13 | margin-right: 0.5rem; 14 | color: var(--color-grey-light-4); 15 | } 16 | 17 | input { 18 | width: 90%; 19 | border: none; 20 | background-color: transparent; 21 | flex: 1; 22 | color: var(--color-grey-dark-1); 23 | 24 | &::placeholder { 25 | color: var(--color-grey-dark-3); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/components/SideMenu/SideMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import logo from '../../assets/logoname.png'; 5 | import './SideMenu.scss'; 6 | import SideMenuItem from './SideMenuItem/SideMenuItem'; 7 | import SideMenuProfile from './SideMenuProfile/SideMenuProfile'; 8 | 9 | const SideMenu = ({ user }) => { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | Account 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | const mapStateToProps = (state) => ({ 26 | user: state.auth.user, 27 | }); 28 | 29 | export default connect(mapStateToProps)(SideMenu); 30 | -------------------------------------------------------------------------------- /client/src/components/SideMenu/SideMenu.scss: -------------------------------------------------------------------------------- 1 | .side-menu { 2 | width: 20vw; 3 | background-color: blue; 4 | position: fixed; 5 | left: 0; 6 | height: 100vh; 7 | top: 0; 8 | 9 | &__profile-button { 10 | display: block; 11 | background-color: var(--color-primary); 12 | text-align: center; 13 | width: 80%; 14 | font-size: 1.4rem; 15 | margin: 1rem auto; 16 | color: white; 17 | display: flex; 18 | align-items: center; 19 | height: 4rem; 20 | border-radius: 5px; 21 | margin-bottom: 3rem; 22 | 23 | &:hover { 24 | color: white; 25 | background-color: var(--color-primary-dark); 26 | } 27 | 28 | span { 29 | display: inline-block; 30 | width: 2.5rem; 31 | height: 100%; 32 | border-right: 1px solid rgba(0, 0, 0, 0.1); 33 | margin-right: 2rem; 34 | padding: 0 15%; 35 | display: flex; 36 | align-items: center; 37 | } 38 | } 39 | 40 | @media only screen and (max-width: 700px) { 41 | width: 25vw; 42 | } 43 | 44 | // background-image: linear-gradient(135deg, #faf9f9, #ccc); 45 | 46 | background-image: url(../../assets/grey.png); 47 | background-size: cover; 48 | 49 | &__logo { 50 | width: 10rem; 51 | height: 10rem; 52 | display: block; 53 | margin: auto; 54 | margin-top: 1rem; 55 | } 56 | 57 | &__profile { 58 | display: flex; 59 | flex-direction: column; 60 | justify-content: center; 61 | align-items: center; 62 | margin-top: 5%; 63 | a { 64 | display: block; 65 | // overflow: hidden; 66 | margin-bottom: 2rem; 67 | border-radius: 20%; 68 | text-align: center; 69 | font-weight: 600; 70 | 71 | img { 72 | width: 100%; 73 | height: 100%; 74 | border-radius: 50%; 75 | height: 10rem; 76 | width: 10rem; 77 | } 78 | } 79 | 80 | &__name { 81 | font-size: 1.4rem; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /client/src/components/SideMenu/SideMenuItem/SideMenuItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import { logOutUser } from '../../../redux/auth/auth.actions'; 5 | import './SideMenuItem.scss'; 6 | 7 | const SideMenuItem = ({ logOutUser }) => { 8 | const logOut = (event) => { 9 | event.preventDefault(); 10 | logOutUser(); 11 | }; 12 | 13 | return ( 14 |
15 |

Menus

16 |
17 | 18 | 19 | Home 20 | 21 | 22 | 23 | 24 | Bookmarks 25 | 26 | 27 | 28 | 29 | All users 30 | 31 | 32 | 33 | 34 | Log out 35 | 36 |
37 |
38 | ); 39 | }; 40 | export default connect(null, { logOutUser })(SideMenuItem); 41 | -------------------------------------------------------------------------------- /client/src/components/SideMenu/SideMenuItem/SideMenuItem.scss: -------------------------------------------------------------------------------- 1 | .side-menu__item { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | &__header { 7 | font-size: 1.5rem; 8 | text-transform: uppercase; 9 | font-weight: 300; 10 | color: var(--color-grey-dark-3); 11 | margin-bottom: 1rem; 12 | margin-right: 20%; 13 | } 14 | 15 | &__icon { 16 | margin-right: 1rem; 17 | } 18 | 19 | &__single { 20 | display: flex; 21 | font-size: 1.6rem; 22 | align-items: center; 23 | font-weight: 300; 24 | padding: 1rem 4rem; 25 | position: relative; 26 | z-index: 1; 27 | // margin-bottom: 1rem; 28 | &:hover{ 29 | color: currentColor; 30 | } 31 | 32 | &::after { 33 | content: ''; 34 | position: absolute; 35 | width: 100%; 36 | height: 100%; 37 | top: 0; 38 | left: 0; 39 | background-color: rgba(0, 0, 0, 0.2); 40 | z-index: -1; 41 | transform: scaleX(0); 42 | transform-origin: left; 43 | transition: all 0.5s; 44 | } 45 | 46 | &:hover::after { 47 | transform: scaleX(1); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/components/SideMenu/SideMenuProfile/SideMenuProfile.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | export class SideMenuProfile extends Component { 7 | render() { 8 | const { user } = this.props; 9 | return ( 10 |
11 | 12 | 16 | 17 | 18 | {user.name} 19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | const mapStateToProps = (state) => ({ 26 | user: state.auth.user, 27 | }); 28 | 29 | export default connect(mapStateToProps)(SideMenuProfile); 30 | -------------------------------------------------------------------------------- /client/src/components/SmallProfile/SmallProfile.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { withRouter } from 'react-router-dom'; 5 | import { toast } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | import { compose } from 'redux'; 8 | import { updateUserData } from '../../redux/auth/auth.actions'; 9 | import './SmallProfile.scss'; 10 | 11 | 12 | 13 | toast.configure(); 14 | 15 | export class SmallProfile extends Component { 16 | followUser = async () => { 17 | try { 18 | const result = await axios.get( 19 | `/users/follow/${this.props.userData._id}` 20 | ); 21 | 22 | 23 | this.props.updateUserData(result.data); 24 | toast.success('User followed sucessfully'); 25 | } catch (error) { 26 | if (error.response.data.message) { 27 | toast.error(error.response.data.message); 28 | } else { 29 | toast.error('Something went wrong! Try again'); 30 | } 31 | } 32 | }; 33 | 34 | unfollowUser = async () => { 35 | try { 36 | const result = await axios.get( 37 | `/users/unfollow/${this.props.userData._id}` 38 | ); 39 | 40 | 41 | this.props.updateUserData(result.data.user); 42 | // this.props.updateUsers(result.data.followingUser); 43 | 44 | 45 | toast.success('User unfollowed sucessfully'); 46 | } catch (error) { 47 | if (error.response.data.message) { 48 | toast.error(error.response.data.message); 49 | } else { 50 | toast.error('Something went wrong! Try again'); 51 | } 52 | } 53 | }; 54 | 55 | visitProfile = () => { 56 | 57 | this.props.history.push(`/profile/${this.props.userData._id}`); 58 | }; 59 | 60 | render() { 61 | // 62 | const { userData } = this.props; 63 | 64 | let followHtml; 65 | if (this.props.user && this.props.user.following.includes(userData._id)) { 66 | followHtml = ; 67 | } else { 68 | followHtml = ; 69 | } 70 | 71 | return ( 72 |
73 |
74 | {userData.name} 79 |
{userData.name}
80 |
81 |
82 | 83 | {followHtml} 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | const mapStateToProps = (state) => ({ 91 | user: state.auth.user, 92 | }); 93 | 94 | export default compose( 95 | withRouter, 96 | connect(mapStateToProps, { updateUserData }) 97 | )(SmallProfile); 98 | -------------------------------------------------------------------------------- /client/src/components/SmallProfile/SmallProfile.scss: -------------------------------------------------------------------------------- 1 | .small-profile { 2 | display: flex; 3 | background-color: red; 4 | padding: 2rem; 5 | margin: auto; 6 | justify-content: space-between; 7 | margin-top: 2rem; 8 | background-color: white; 9 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 10 | 11 | &__img { 12 | height: 8rem; 13 | width: 8rem; 14 | border-radius: 50%; 15 | margin-bottom: 1rem; 16 | } 17 | 18 | &__left { 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | &__right { 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: center; 29 | align-items: center; 30 | 31 | button { 32 | padding: 1rem 2rem; 33 | margin: 1rem 2rem; 34 | background-image: linear-gradient(to bottom right, #30a9de, #1977a0); 35 | color: white; 36 | } 37 | } 38 | 39 | &__name { 40 | font-size: 1.3rem; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/components/Spinner/Spinner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Spinner.scss'; 3 | 4 | const Spinner = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Spinner; 22 | -------------------------------------------------------------------------------- /client/src/components/Spinner/Spinner.scss: -------------------------------------------------------------------------------- 1 | .lds-roller { 2 | display: inline-block; 3 | position: relative; 4 | width: 15rem; 5 | height: 15rem; 6 | } 7 | .lds-roller div { 8 | animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 9 | transform-origin: 4rem 4rem; 10 | } 11 | .lds-roller div:after { 12 | content: ' '; 13 | display: block; 14 | position: absolute; 15 | width: 0.7rem; 16 | height: 0.7rem; 17 | border-radius: 50%; 18 | background: var(--color-primary); 19 | margin: -0.4rem 0 0 -0.4rem; 20 | } 21 | .lds-roller div:nth-child(1) { 22 | animation-delay: -0.036s; 23 | } 24 | .lds-roller div:nth-child(1):after { 25 | top: 6.3rem; 26 | left: 6.3rem; 27 | } 28 | .lds-roller div:nth-child(2) { 29 | animation-delay: -0.072s; 30 | } 31 | .lds-roller div:nth-child(2):after { 32 | top: 6.8rem; 33 | left: 5.6rem; 34 | } 35 | .lds-roller div:nth-child(3) { 36 | animation-delay: -0.108s; 37 | } 38 | .lds-roller div:nth-child(3):after { 39 | top: 7.1rem; 40 | left: 4.8rem; 41 | } 42 | .lds-roller div:nth-child(4) { 43 | animation-delay: -0.144s; 44 | } 45 | .lds-roller div:nth-child(4):after { 46 | top: 7.2rem; 47 | left: 4rem; 48 | } 49 | .lds-roller div:nth-child(5) { 50 | animation-delay: -0.18s; 51 | } 52 | .lds-roller div:nth-child(5):after { 53 | top: 7.1rem; 54 | left: 3.2rem; 55 | } 56 | .lds-roller div:nth-child(6) { 57 | animation-delay: -0.216s; 58 | } 59 | .lds-roller div:nth-child(6):after { 60 | top: 6.8rem; 61 | left: 2.4rem; 62 | } 63 | .lds-roller div:nth-child(7) { 64 | animation-delay: -0.252s; 65 | } 66 | .lds-roller div:nth-child(7):after { 67 | top: 6.3rem; 68 | left: 1.7rem; 69 | } 70 | .lds-roller div:nth-child(8) { 71 | animation-delay: -0.288s; 72 | } 73 | .lds-roller div:nth-child(8):after { 74 | top: 5.6rem; 75 | left: 1.2rem; 76 | } 77 | @keyframes lds-roller { 78 | 0% { 79 | transform: rotate(0deg); 80 | } 81 | 100% { 82 | transform: rotate(360deg); 83 | } 84 | } 85 | .spinner-container { 86 | width: 100vw; 87 | height: 100vh; 88 | position: relative; 89 | display: flex; 90 | justify-content: center; 91 | align-items: center; 92 | // background-color: blue; 93 | } 94 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import jwt from 'jsonwebtoken'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | import { BrowserRouter } from 'react-router-dom'; 7 | import { toast } from 'react-toastify'; 8 | import 'react-toastify/dist/ReactToastify.css'; 9 | import App from './App'; 10 | import './index.scss'; 11 | import { AUTH_TYPES } from './redux/auth/auth.types'; 12 | import { default as reduxStore, default as store } from './redux/store'; 13 | 14 | toast.configure(); 15 | 16 | const baseURL = process.env.REACT_APP_API_BASE_URL; 17 | 18 | axios.defaults.baseURL = baseURL; 19 | 20 | axios.interceptors.request.use( 21 | (config) => { 22 | let token = localStorage.getItem('jwt_token'); 23 | if (token) { 24 | config.headers.Authorization = `Bearer ${token}`; 25 | } 26 | const decoded = jwt.decode(localStorage.getItem('jwt_token')); 27 | const date = new Date().getTime(); 28 | 29 | if (decoded) { 30 | if (date >= decoded.exp * 1000) { 31 | toast.error('Session expired! Login again'); 32 | reduxStore.dispatch({ 33 | type: AUTH_TYPES.LOG_OUT_USER, 34 | }); 35 | 36 | throw new axios.Cancel('Please login again'); 37 | } 38 | } 39 | return config; 40 | }, 41 | (error) => { 42 | return Promise.reject(error); 43 | } 44 | ); 45 | 46 | axios.interceptors.response.use( 47 | (response) => { 48 | return response; 49 | }, 50 | (error) => { 51 | if ( 52 | error.response && 53 | error.response.data && 54 | error.response.data.message == 'User no longer exist' 55 | ) { 56 | reduxStore.dispatch({ 57 | type: AUTH_TYPES.LOG_OUT_USER, 58 | }); 59 | toast.error('Your account is deleted!'); 60 | } 61 | 62 | if ( 63 | error.response && 64 | error.response.data && 65 | error.response.data.message == 'Not authorize' 66 | ) { 67 | reduxStore.dispatch({ 68 | type: AUTH_TYPES.LOG_OUT_USER, 69 | }); 70 | toast.error('You are not authorize! login again'); 71 | } 72 | 73 | if ( 74 | error.response && 75 | error.response.data && 76 | error.response.data.message == 'Jwt token expired! Login again' 77 | ) { 78 | reduxStore.dispatch({ 79 | type: AUTH_TYPES.LOG_OUT_USER, 80 | }); 81 | toast.error('Jwt token expired! Login again'); 82 | } 83 | 84 | return Promise.reject(error); 85 | } 86 | ); 87 | 88 | ReactDOM.render( 89 | 90 | 91 | 92 | 93 | 94 | 95 | , 96 | document.getElementById('root') 97 | ); 98 | -------------------------------------------------------------------------------- /client/src/index.scss: -------------------------------------------------------------------------------- 1 | /*********************************** 2 | DEFINING ROOT VARIABLES 3 | ************************************/ 4 | :root { 5 | --color-primary: #30a9de; 6 | --color-primary-light: #88ceec; 7 | --color-primary-dark: #1977a0; 8 | --color-grey-light-1: #faf9f9; 9 | --color-grey-light-2: #f4f2f2; 10 | --color-grey-light-3: #f0eeee; 11 | --color-grey-light-4: #ccc; 12 | --color-grey-dark-1: #111; 13 | --color-grey-dark-2: #333; 14 | --color-grey-dark-3: #777; 15 | --color-grey-dark-4: #999; 16 | --color-grey-dark-5: #808080; 17 | --font-family-primary: 'Open Sans', sans-serif; 18 | --font-family-secondary: 'Berkshire Swash', cursive; 19 | } 20 | 21 | .profile { 22 | width: 50%; 23 | } 24 | 25 | html { 26 | box-sizing: border-box; 27 | /* 28 | -->Here we are defining root font size 29 | -->Its in percentage to have functionality of zoom in and zoom out and it will change the font size of 30 | -->The percentage also helps if user change default font size of browser so percentage value will adjust according to that 31 | -->root default font size of browser is 16px so 62.5% of 16px will be 10px which will be equal to 1rem so it will be easy to use rem with respect to 10px instead of with respect to 16px 32 | --> 1rem = 10px 33 | */ 34 | font-size: 62.5%; 35 | } 36 | 37 | body { 38 | font-family: var(--font-family-primary); 39 | font-weight: 400; 40 | line-height: 1.6; 41 | color: var(--color-grey-dark-1); 42 | height: 100vh; 43 | // padding: 3rem 5rem; 44 | padding-top: 0; 45 | // background-color: #eee; 46 | background-color: #fafcff; 47 | } 48 | 49 | * { 50 | margin: 0; 51 | padding: 0; 52 | } 53 | 54 | /* Applying box-sizing property of all element and before and after Pseudo elements to border box so width and height will be equal to define width and height */ 55 | 56 | *, 57 | *::after, 58 | *::before { 59 | box-sizing: border-box; 60 | } 61 | 62 | input:focus, 63 | button:focus { 64 | outline: none; 65 | border: none; 66 | } 67 | 68 | button { 69 | border: none; 70 | } 71 | 72 | button:hover { 73 | cursor: pointer; 74 | } 75 | 76 | input { 77 | font-family: inherit; 78 | color: inherit; 79 | } 80 | 81 | 82 | a { 83 | text-decoration: none !important; 84 | color: var(--color-grey-dark-1); 85 | 86 | a:hover { 87 | color: currentColor !important; 88 | } 89 | } 90 | 91 | 92 | @media only screen and (max-width: 1200px) { 93 | html { 94 | font-size: 56.25%; 95 | /* 1rem = 9px */ 96 | } 97 | } 98 | 99 | @media only screen and (max-width: 992px) { 100 | html { 101 | font-size: 50%; 102 | /* 1rem = 8px */ 103 | } 104 | } 105 | 106 | /* FOR SMALL SCREEN SIZE */ 107 | @media only screen and (max-width: 250px) { 108 | html { 109 | font-size: 40%; 110 | /* 1 rem = 6.4px */ 111 | } 112 | } 113 | 114 | /* Its only used here*/ 115 | @media only screen and (max-width: 150px) { 116 | html { 117 | font-size: 20%; 118 | /* 1rem = 3.2px */ 119 | } 120 | } 121 | 122 | @media only screen and (min-width: 1600px) { 123 | html { 124 | font-size: 75%; 125 | } 126 | } 127 | 128 | 129 | @media only screen and (min-width: 1600px) { 130 | html { 131 | font-size: 75%; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /client/src/pages/AllUser/AllUser.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import uniqid from 'uniqid'; 4 | import Search from '../../components/Search/Search'; 5 | import SmallProfile from '../../components/SmallProfile/SmallProfile'; 6 | import './AllUser.scss'; 7 | 8 | class AllUser extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | users: [], 13 | }; 14 | } 15 | 16 | async componentDidMount() { 17 | const result = await axios.get('/users/get-users'); 18 | 19 | this.setState({ users: result.data.users }); 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 | 26 | {this.state.users.length > 0 ? ( 27 | this.state.users.map((ele) => ( 28 | 29 | )) 30 | ) : ( 31 |

No users found

32 | )} 33 |
34 | ); 35 | } 36 | } 37 | 38 | export default AllUser; 39 | -------------------------------------------------------------------------------- /client/src/pages/AllUser/AllUser.scss: -------------------------------------------------------------------------------- 1 | .all-user { 2 | margin-left: 25%; 3 | width: 70%; 4 | padding-bottom: 10rem; 5 | height: 100vh; 6 | padding-top: 3rem; 7 | 8 | @media only screen and (max-width: 700px) { 9 | margin-left: 30% !important; 10 | padding: 2rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/src/pages/Auth/Auth.scss: -------------------------------------------------------------------------------- 1 | .auth { 2 | position: relative; 3 | width: 100vw; 4 | height: 100vh; 5 | background-image: linear-gradient(to bottom right, #30a9de, #1977a0); 6 | 7 | &__container { 8 | position: absolute; 9 | height: 85%; 10 | width: 30%; 11 | left: 50%; 12 | top: 50%; 13 | transform: translate(-50%, -50%); 14 | background-color: white; 15 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: center; 19 | 20 | 21 | 22 | @media only screen and (max-width: 900px) { 23 | width: 40%; 24 | } 25 | 26 | @media only screen and (max-width: 650px) { 27 | width: 50%; 28 | } 29 | 30 | 31 | @media only screen and (max-width: 450px) { 32 | width: 90%; 33 | } 34 | } 35 | &__register, 36 | &__login { 37 | img { 38 | height: 12rem; 39 | width: 10rem; 40 | display: block; 41 | margin: auto; 42 | } 43 | 44 | a { 45 | font-size: 1.3rem; 46 | display: block; 47 | margin-top: 2rem; 48 | margin-left: 2rem; 49 | 50 | &:hover { 51 | color: var(--color-primary); 52 | } 53 | } 54 | } 55 | 56 | &__button { 57 | margin: auto; 58 | display: block; 59 | padding: 1rem 2rem; 60 | font-size: 1.6rem; 61 | background-image: linear-gradient(to right bottom, #30a9de, #1977a0); 62 | color: white; 63 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4); 64 | } 65 | 66 | &__header { 67 | text-align: center; 68 | margin-bottom: 2rem; 69 | font-size: 3rem; 70 | font-weight: 400; 71 | color: var(--color-primary); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/src/pages/Auth/AuthLogin/AuthLogin.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import logo from '../../../assets/logoname.png'; 5 | import FormInput from '../../../components/FormInput/FormInput'; 6 | import { singInUser } from '../../../redux/auth/auth.actions'; 7 | import '../Auth.scss'; 8 | 9 | class AuthLogin extends Component { 10 | constructor() { 11 | super(); 12 | this.state = { 13 | email: '', 14 | password: '', 15 | }; 16 | } 17 | 18 | onChangeHandler = (event) => { 19 | const { name, value } = event.target; 20 | this.setState({ [name]: value }); 21 | }; 22 | 23 | onSubmit = async () => { 24 | const newUser = { 25 | email: this.state.email, 26 | password: this.state.password, 27 | }; 28 | 29 | this.props.singInUser(newUser); 30 | }; 31 | 32 | render() { 33 | return ( 34 |
35 |
36 |
37 | 38 |

login

39 | 46 | 53 | 56 | Create Account 57 |
58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | export default connect(null, { singInUser })(AuthLogin); 65 | -------------------------------------------------------------------------------- /client/src/pages/Auth/AuthRegister/AuthRegister.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import logo from '../../../assets/logoname.png'; 5 | import FormInput from '../../../components/FormInput/FormInput'; 6 | import { signUpUser } from '../../../redux/auth/auth.actions'; 7 | import '../Auth.scss'; 8 | 9 | class AuthRegister extends Component { 10 | constructor() { 11 | super(); 12 | this.state = { 13 | name: '', 14 | email: '', 15 | password: '', 16 | passwordConfirm: '', 17 | }; 18 | } 19 | 20 | onChangeHandler = (event) => { 21 | const { name, value } = event.target; 22 | this.setState({ [name]: value }); 23 | }; 24 | 25 | onSubmit = () => { 26 | const newUser = { 27 | name: this.state.name, 28 | email: this.state.email, 29 | password: this.state.password, 30 | passwordConfirm: this.state.passwordConfirm, 31 | }; 32 | const { signUpUser } = this.props; 33 | signUpUser(newUser); 34 | }; 35 | 36 | render() { 37 | return ( 38 |
39 |
40 |
41 | 42 | 43 |

Register

44 | 51 | 58 | 65 | 72 | 75 | Already Have account ? 76 |
77 |
78 |
79 | ); 80 | } 81 | } 82 | 83 | export default connect(null, { signUpUser })(AuthRegister); 84 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/BookMark.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './Home.scss'; 3 | import HomeRight from './HomeRight/HomeRight'; 4 | import NewsFeed from './NewsFeed/NewsFeed'; 5 | 6 | export class Bookmark extends Component { 7 | render() { 8 | return ( 9 |
10 | 11 | 12 |
13 | ); 14 | } 15 | } 16 | 17 | export default Bookmark; 18 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/Home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | display: flex; 3 | justify-content: space-between; 4 | &__right { 5 | width: 20%; 6 | margin-right: 5%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/HomeRight/HomeRight.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | import uniqid from 'uniqid'; 6 | import encipherslogo from '../../../assets/enciphers.jpg'; 7 | import logo from '../../../assets/logoname.png'; 8 | import './HomeRight.scss'; 9 | import HomeRightUser from './HomeRightUser/HomeRightUser'; 10 | 11 | class HomeRight extends Component { 12 | constructor() { 13 | super(); 14 | this.state = { 15 | users: [], 16 | followers: [], 17 | }; 18 | } 19 | 20 | async componentDidMount() { 21 | const result = await axios.get('/users/get-users'); 22 | 23 | const temp = []; 24 | for (let i = 0; i < result.data.users.length && i < 3; i++) { 25 | temp.push(result.data.users[i]); 26 | } 27 | 28 | this.setState({ users: temp }); 29 | 30 | // folowers 31 | const followers = await axios.get( 32 | `/users/followers/${this.props.user._id}` 33 | ); 34 | 35 | const temp2 = []; 36 | for (let i = 0; i < followers.data.users.length && i < 2; i++) { 37 | temp2.push(followers.data.users[i]); 38 | } 39 | 40 | this.setState({ followers: temp2 }); 41 | } 42 | 43 | render() { 44 | const { users, followers } = this.state; 45 | 46 | let followerHtml =

No followers found

; 47 | if (followers.length > 0) { 48 | followerHtml = followers.map((el) => ( 49 | 50 | )); 51 | } 52 | 53 | let allUserHtml =

No users found

; 54 | 55 | if (users.length > 0) { 56 | allUserHtml = users.map((el) => ( 57 | 58 | )); 59 | } 60 | 61 | return ( 62 |
63 |
64 |

NEW USERS

65 |
{allUserHtml}
66 | 67 | 68 | view more 69 | 70 |
71 | 72 |
73 |

Creators

74 |
75 | 76 | 77 | 78 |
79 | 84 | 85 | 86 |
87 | Created & Managed by Enciphers 88 |
89 |
90 |
91 |
92 | 93 |
94 |

USERS

95 |
{followerHtml}
96 | 100 | view more 101 | 102 |
103 |
104 | ); 105 | } 106 | } 107 | 108 | const mapStateToProps = (state) => ({ 109 | token: state.auth.token, 110 | isAuth: state.auth.isAuth, 111 | user: state.auth.user, 112 | }); 113 | export default connect(mapStateToProps)(HomeRight); 114 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/HomeRight/HomeRight.scss: -------------------------------------------------------------------------------- 1 | .home__right { 2 | padding-top: 2rem; 3 | position: fixed; 4 | right: 0; 5 | 6 | &__users { 7 | margin-bottom: 3rem; 8 | &__view-more { 9 | font-size: 1.2rem; 10 | text-transform: uppercase; 11 | color: var(--color-grey-light-4); 12 | 13 | &:hover { 14 | color: var(--color-grey-dark-3); 15 | } 16 | } 17 | &__container { 18 | display: flex; 19 | align-items: center; 20 | } 21 | &__header { 22 | font-size: 1.4rem; 23 | font-weight: 300; 24 | margin-bottom: 2rem; 25 | } 26 | 27 | &__list { 28 | h3 { 29 | margin: 3rem; 30 | } 31 | 32 | &__single { 33 | margin-bottom: 1.5rem; 34 | display: flex; 35 | align-items: center; 36 | justify-content: space-between; 37 | 38 | &__img { 39 | width: 4rem; 40 | height: 4rem; 41 | display: block; 42 | overflow: hidden; 43 | border-radius: 50%; 44 | margin-right: 1.5rem; 45 | 46 | img { 47 | width: 100%; 48 | height: 100%; 49 | } 50 | } 51 | 52 | &__name { 53 | font-weight: 600; 54 | font-size: 1.2rem; 55 | } 56 | 57 | &__view-more { 58 | display: inline-block; 59 | background-color: var(--color-grey-light-3); 60 | padding: 0.5rem; 61 | margin-right: 15%; 62 | } 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/HomeRight/HomeRightFollower/HomeRightFollwer.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class HomeRightFollower extends Component { 6 | render() { 7 | const { userData } = this.props; 8 | return ( 9 |
10 |
11 |
12 | 16 |
17 |
18 | {userData.name} 19 |
20 |
21 | 25 | View 26 | 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default HomeRightFollower; 33 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/HomeRight/HomeRightUser/HomeRightUser.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class HomeRightUser extends Component { 6 | render() { 7 | const { userData } = this.props; 8 | return ( 9 |
10 |
11 |
12 | 16 |
17 |
18 | {userData.name} 19 |
20 |
21 | 25 | View 26 | 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default HomeRightUser; 33 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/NewsFeed/NewsFeed.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { toast } from 'react-toastify'; 4 | import 'react-toastify/dist/ReactToastify.css'; 5 | import uniqueId from 'uniqid'; 6 | import Search from '../../../components/Search/Search'; 7 | import { updateBookmarks } from '../../../redux/auth/auth.actions'; 8 | import './NewsFeed.scss'; 9 | import SharedPost from './SharedPost/SharedPost'; 10 | import SinglePost from './SinglePost/SinglePost'; 11 | 12 | toast.configure(); 13 | 14 | class NewsFeed extends Component { 15 | updateBookmarks = async (postData) => { 16 | this.props.updateBookmarks(postData); 17 | }; 18 | 19 | render() { 20 | return ( 21 |
22 | 23 | {this.props.bookmarks.map((ele) => { 24 | if (ele.isShared) { 25 | return ( 26 | 33 | ); 34 | } else { 35 | return ( 36 | 43 | ); 44 | } 45 | })} 46 |
47 | ); 48 | } 49 | } 50 | 51 | const mapStateToProps = (state) => ({ 52 | bookmarks: state.auth.bookmarks, 53 | }); 54 | 55 | export default connect(mapStateToProps, { updateBookmarks })(NewsFeed); 56 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/NewsFeed/NewsFeed.scss: -------------------------------------------------------------------------------- 1 | .home__newsfeed { 2 | margin-left: 25%; 3 | width: 40%; 4 | padding-bottom: 10rem; 5 | } 6 | 7 | .newsfeed__single-post { 8 | margin-bottom: 5rem; 9 | &__text { 10 | font-size: 1.3rem; 11 | color: var(--color-grey-dark-3); 12 | margin-top: 1rem; 13 | margin-bottom: 1rem; 14 | } 15 | 16 | &__img { 17 | width: 100%; 18 | display: block; 19 | height: 25rem; 20 | } 21 | 22 | &__likes { 23 | font-size: 1.3rem; 24 | margin: 1rem 0; 25 | span { 26 | font-weight: 700; 27 | } 28 | } 29 | 30 | &__top { 31 | display: flex; 32 | align-items: center; 33 | justify-content: space-between; 34 | 35 | &__left { 36 | display: flex; 37 | align-items: center; 38 | a { 39 | margin-right: 1rem; 40 | img { 41 | width: 5rem; 42 | height: 5rem; 43 | border-radius: 50%; 44 | } 45 | } 46 | 47 | &__name { 48 | font-size: 1.5rem; 49 | text-transform: capitalize; 50 | } 51 | } 52 | 53 | &__right { 54 | &__icon { 55 | color: currentColor; 56 | font-size: 1.4rem; 57 | } 58 | button { 59 | width: 5rem; 60 | height: 3rem; 61 | background-color: white; 62 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); 63 | color: var(--color-grey-light-4); 64 | &:hover { 65 | color: var(--color-grey-dark-3); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | .single-post__url { 73 | height: 6rem; 74 | width: 90%; 75 | display: block; 76 | background-color: var(--color-grey-light-4); 77 | margin: auto; 78 | display: flex; 79 | margin-bottom: 20px; 80 | text-decoration: none; 81 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 82 | } 83 | 84 | .single-post__url:hover { 85 | text-decoration: none; 86 | } 87 | .single-post__url__img { 88 | width: 20%; 89 | height: 100%; 90 | } 91 | 92 | .single-post__url__title { 93 | height: 100%; 94 | width: 80%; 95 | overflow: hidden; 96 | display: flex; 97 | justify-content: center; 98 | align-items: center; 99 | padding: 5px; 100 | } 101 | 102 | .share-post { 103 | margin-bottom: 5rem; 104 | } 105 | 106 | .share-post-single { 107 | border: 1px solid var(--color-grey-light-4); 108 | margin-top: 2rem; 109 | padding: 2rem; 110 | margin-bottom: 1rem; 111 | } 112 | 113 | .shared-title { 114 | font-size: 1.4rem; 115 | font-weight: 600; 116 | margin-left: 0rem; 117 | } 118 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/NewsFeed/SharedPost/SharedPost.js: -------------------------------------------------------------------------------- 1 | // import './S'; 2 | import axios from 'axios'; 3 | import React, { Component } from 'react'; 4 | import { connect } from 'react-redux'; 5 | import { toast } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | 8 | toast.configure(); 9 | class SharedPost extends Component { 10 | onLikeClickHandler = async (event) => { 11 | const result = await axios.get(`/like/toggle/${this.props.postData._id}`); 12 | this.props.editPostLike(result.data); 13 | }; 14 | 15 | deleteBookmark = async (event) => { 16 | try { 17 | const result = await axios.get( 18 | `/bookmark/delete/${this.props.postData.bookMarkId}` 19 | ); 20 | toast.success(result.data.message); 21 | 22 | this.props.updateBookmarks(result.data.bookmarks); 23 | } catch (error) { 24 | if (error.response.data.message) { 25 | toast.error(error.response.data.message); 26 | } else { 27 | toast.error('Something went wrong! Try again'); 28 | } 29 | } 30 | }; 31 | 32 | render() { 33 | const { postData } = this.props; 34 | return ( 35 |
36 |
37 | 49 |
50 | 53 | 54 | {/* */} 57 |
58 |
59 |
60 | 73 |
74 | {postData.description} 75 |
76 | 77 | {postData.url ? ( 78 | 79 | not found 84 |
{postData.urlTitle}
85 |
86 | ) : null} 87 | 88 | {postData.postImage ? ( 89 | 98 | ) : null} 99 |
100 |
101 | Liked by {postData.likes.length} people 102 |
103 |
104 | ); 105 | } 106 | } 107 | 108 | const mapStateToProps = (state) => ({ 109 | user: state.auth.user, 110 | isAuth: state.auth.isAuth, 111 | }); 112 | 113 | export default connect(mapStateToProps)(SharedPost); 114 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/NewsFeed/SinglePost/SinglePost.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | import './SinglePost.scss'; 7 | 8 | toast.configure(); 9 | export class SinglePost extends Component { 10 | deleteBookmark = async (event) => { 11 | try { 12 | const result = await axios.get( 13 | `/bookmark/delete/${this.props.postData.bookMarkId}` 14 | ); 15 | toast.success(result.data.message); 16 | 17 | this.props.updateBookmarks(result.data.bookmarks); 18 | } catch (error) { 19 | if (error.response.data.message) { 20 | toast.error(error.response.data.message); 21 | } else { 22 | toast.error('Something went wrong! Try again'); 23 | } 24 | } 25 | }; 26 | render() { 27 | const { postData } = this.props; 28 | return ( 29 |
30 |
31 | 42 |
43 | 46 | 47 | {/* */} 50 |
51 |
52 |
53 | {postData.description} 54 |
55 | 56 | {postData.url ? ( 57 | 58 | not found 63 |
{postData.urlTitle}
64 |
65 | ) : null} 66 | 67 | {postData.postImage ? ( 68 | 77 | ) : null} 78 | 79 |
80 | Liked by {postData.likes.length} people 81 |
82 |
83 | ); 84 | } 85 | } 86 | 87 | const mapStateToProps = (state) => ({ 88 | user: state.auth.user, 89 | isAuth: state.auth.isAuth, 90 | }); 91 | 92 | export default connect(mapStateToProps)(SinglePost); 93 | -------------------------------------------------------------------------------- /client/src/pages/BookMark/NewsFeed/SinglePost/SinglePost.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/src/pages/BookMark/NewsFeed/SinglePost/SinglePost.scss -------------------------------------------------------------------------------- /client/src/pages/Follower/Follower.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | import uniqid from 'uniqid'; 7 | import Search from '../../components/Search/Search'; 8 | import SmallProfile from '../../components/SmallProfile/SmallProfile'; 9 | import '../AllUser/AllUser.scss'; 10 | 11 | toast.configure(); 12 | 13 | class Follower extends Component { 14 | constructor() { 15 | super(); 16 | this.state = { 17 | users: [], 18 | title: '', 19 | }; 20 | } 21 | 22 | async componentDidMount() { 23 | try { 24 | const result = await axios.get( 25 | `/users/followers/${this.props.match.params.id}` 26 | ); 27 | 28 | this.setState({ users: result.data.users, title: result.data.title }); 29 | } catch (error) { 30 | if (error.response.data.message) { 31 | toast.error(error.response.data.message); 32 | } else { 33 | toast.error('Something went wrong! Try again'); 34 | } 35 | } 36 | 37 | // this.setState({ users: result.data.users }); 38 | // 39 | } 40 | 41 | render() { 42 | return ( 43 |
44 | 45 | {this.state.users.map((ele) => ( 46 | 47 | ))} 48 |
49 | ); 50 | } 51 | } 52 | 53 | export default withRouter(Follower); 54 | -------------------------------------------------------------------------------- /client/src/pages/Following/Following.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | import uniqid from 'uniqid'; 7 | import Search from '../../components/Search/Search'; 8 | import SmallProfile from '../../components/SmallProfile/SmallProfile'; 9 | import '../AllUser/AllUser.scss'; 10 | 11 | toast.configure(); 12 | 13 | class Following extends Component { 14 | constructor() { 15 | super(); 16 | this.state = { 17 | users: [], 18 | }; 19 | } 20 | 21 | async componentDidMount() { 22 | try { 23 | const result = await axios.get( 24 | `/users/following/${this.props.match.params.id}` 25 | ); 26 | 27 | this.setState({ users: result.data.users, title: result.data.title }); 28 | } catch (error) { 29 | if (error.response.data.message) { 30 | toast.error(error.response.data.message); 31 | } else { 32 | toast.error('Something went wrong! Try again'); 33 | } 34 | } 35 | } 36 | 37 | updateUsers = async (userData) => { 38 | this.setState({ users: userData }); 39 | }; 40 | render() { 41 | return ( 42 |
43 | 44 | {this.state.users.map((ele) => ( 45 | 51 | ))} 52 |
53 | ); 54 | } 55 | } 56 | 57 | export default withRouter(Following); 58 | -------------------------------------------------------------------------------- /client/src/pages/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import NewsFeed from './NewsFeed/NewsFeed'; 3 | import HomeRight from './HomeRight/HomeRight'; 4 | import './Home.scss'; 5 | 6 | export class Home extends Component { 7 | render() { 8 | return ( 9 |
10 | 11 | 12 |
13 | ); 14 | } 15 | } 16 | 17 | export default Home; 18 | -------------------------------------------------------------------------------- /client/src/pages/Home/Home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | display: flex; 3 | justify-content: space-between; 4 | &__right { 5 | width: 20%; 6 | margin-right: 5%; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/src/pages/Home/HomeRight/HomeRight.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | import uniqid from 'uniqid'; 6 | import encipherslogo from '../../../assets/enciphers.jpg'; 7 | import logo from '../../../assets/logoname.png'; 8 | import './HomeRight.scss'; 9 | import HomeRightUser from './HomeRightUser/HomeRightUser'; 10 | 11 | class HomeRight extends Component { 12 | constructor() { 13 | super(); 14 | this.state = { 15 | users: [], 16 | 17 | followers: [], 18 | }; 19 | } 20 | 21 | async componentDidMount() { 22 | const result = await axios.get('/users/get-users'); 23 | 24 | const temp = []; 25 | for (let i = 0; i < result.data.users.length && i < 3; i++) { 26 | temp.push(result.data.users[i]); 27 | } 28 | 29 | this.setState({ users: temp }); 30 | 31 | // folowers 32 | const followers = await axios.get( 33 | `/users/followers/${this.props.user._id}` 34 | ); 35 | 36 | const temp2 = []; 37 | for (let i = 0; i < followers.data.users.length && i < 2; i++) { 38 | temp2.push(followers.data.users[i]); 39 | } 40 | 41 | this.setState({ followers: temp2 }); 42 | } 43 | 44 | render() { 45 | const { users, followers } = this.state; 46 | 47 | let followerHtml =

No followers found

; 48 | if (followers.length > 0) { 49 | followerHtml = followers.map((el) => ( 50 | 51 | )); 52 | } 53 | 54 | let allUserHtml =

No users found

; 55 | 56 | if (users.length > 0) { 57 | allUserHtml = users.map((el) => ( 58 | 59 | )); 60 | } 61 | 62 | return ( 63 |
64 |
65 |

NEW USERS

66 |
{allUserHtml}
67 | 68 | 69 | view more 70 | 71 |
72 | 73 |
74 |

Creators

75 |
76 | 77 | 78 | 79 |
80 | 85 | 86 | 87 |
88 | Created & Managed by Enciphers 89 |
90 |
91 |
92 |
93 | 94 |
95 |

FOLLOWERS

96 |
{followerHtml}
97 | 101 | view more 102 | 103 |
104 |
105 | ); 106 | } 107 | } 108 | 109 | const mapStateToProps = (state) => ({ 110 | token: state.auth.token, 111 | isAuth: state.auth.isAuth, 112 | user: state.auth.user, 113 | }); 114 | export default connect(mapStateToProps)(HomeRight); 115 | -------------------------------------------------------------------------------- /client/src/pages/Home/HomeRight/HomeRight.scss: -------------------------------------------------------------------------------- 1 | .home__right { 2 | padding-top: 2rem; 3 | position: fixed; 4 | right: 0; 5 | 6 | @media only screen and (max-width: 700px) { 7 | display: none; 8 | } 9 | 10 | &__sponsor { 11 | margin-bottom: 4rem; 12 | h3 { 13 | text-transform: uppercase; 14 | color: var(--color-grey-dark-3); 15 | font-size: 1.5rem; 16 | margin-bottom: 1rem; 17 | } 18 | 19 | height: 12rem; 20 | 21 | &__container2 { 22 | display: flex; 23 | height: 80%; 24 | 25 | & > a { 26 | width: 50%; 27 | height: 100%; 28 | display: block; 29 | margin-right: 1rem; 30 | border-radius: 5px; 31 | overflow: hidden; 32 | 33 | img { 34 | width: 100%; 35 | height: 100%; 36 | } 37 | } 38 | } 39 | 40 | &__container { 41 | width: 40%; 42 | display: flex; 43 | flex-direction: column; 44 | align-items: center; 45 | } 46 | 47 | &__text { 48 | color: var(--color-grey-dark-2); 49 | text-align: center; 50 | } 51 | 52 | &__logo { 53 | width: 5rem; 54 | height: 5rem; 55 | display: block; 56 | margin-bottom: 1rem; 57 | 58 | img { 59 | width: 100%; 60 | height: 100%; 61 | } 62 | } 63 | } 64 | 65 | &__users { 66 | margin-bottom: 2rem; 67 | &__view-more { 68 | font-size: 1.2rem; 69 | text-transform: uppercase; 70 | color: var(--color-grey-light-4); 71 | 72 | &:hover { 73 | color: var(--color-grey-dark-3); 74 | } 75 | } 76 | &__container { 77 | display: flex; 78 | align-items: center; 79 | } 80 | &__header { 81 | font-size: 1.4rem; 82 | font-weight: 300; 83 | margin-bottom: 2rem; 84 | } 85 | 86 | &__list { 87 | h3 { 88 | margin: 3rem; 89 | } 90 | 91 | &__single { 92 | margin-bottom: 1.5rem; 93 | display: flex; 94 | align-items: center; 95 | justify-content: space-between; 96 | 97 | &__img { 98 | width: 4rem; 99 | height: 4rem; 100 | display: block; 101 | overflow: hidden; 102 | border-radius: 50%; 103 | margin-right: 1.5rem; 104 | 105 | img { 106 | width: 100%; 107 | height: 100%; 108 | } 109 | } 110 | 111 | &__name { 112 | font-weight: 600; 113 | font-size: 1.2rem; 114 | } 115 | 116 | &__view-more { 117 | display: inline-block; 118 | background-color: var(--color-grey-light-3); 119 | padding: 0.5rem; 120 | margin-right: 15%; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /client/src/pages/Home/HomeRight/HomeRightFollower/HomeRightFollwer.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class HomeRightFollower extends Component { 6 | render() { 7 | const { userData } = this.props; 8 | return ( 9 |
10 |
11 |
12 | 16 |
17 |
18 | {userData.name} 19 |
20 |
21 | 25 | View 26 | 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default HomeRightFollower; 33 | -------------------------------------------------------------------------------- /client/src/pages/Home/HomeRight/HomeRightUser/HomeRightUser.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | class HomeRightUser extends Component { 6 | render() { 7 | const { userData } = this.props; 8 | return ( 9 |
10 |
11 |
12 | 16 |
17 |
18 | {userData.name} 19 |
20 |
21 | 25 | View 26 | 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default HomeRightUser; 33 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/Comment/Comment.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { connect } from 'react-redux'; 3 | import React, { Component } from 'react'; 4 | import { Link } from 'react-router-dom'; 5 | import { toast } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | 8 | toast.configure(); 9 | 10 | export class Comment extends Component { 11 | deleteComment = async () => { 12 | try { 13 | const { data } = this.props; 14 | const result = await axios.get(`/comment/delete/${data._id}`); 15 | toast.success('Comment added successfully'); 16 | this.props.updatePost(result.data); 17 | } catch (error) { 18 | if (error.response.data.message) { 19 | toast.error(error.response.data.message); 20 | } else { 21 | toast.error('Something went wrong! Try again'); 22 | } 23 | } 24 | }; 25 | 26 | render() { 27 | const { data } = this.props; 28 | console.log(this.props.user._id); 29 | console.log(this.props.data.user._id); 30 | console.log(this.props.user._id === this.props.data.user._id); 31 | 32 | return ( 33 |
34 |
35 | 39 | 43 | 44 | 48 | {data.user.name} 49 | 50 |
51 |
{data.description}
52 | {this.props.user._id === this.props.data.user._id ? ( 53 | 59 | ) : null} 60 |
61 | ); 62 | } 63 | } 64 | 65 | const mapStateToProps = (state) => ({ 66 | user: state.auth.user, 67 | }); 68 | 69 | export default connect(mapStateToProps)(Comment); 70 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/CreatePost/CreatePost.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { toast } from 'react-toastify'; 5 | import Modal from '../Modal/Modal'; 6 | import './CreatePost.scss'; 7 | 8 | class CreatePost extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | content: '', 13 | post_url: '', 14 | file: '', 15 | url: '', 16 | isLoading: false, 17 | isModalOpen: false, 18 | selectedUser: [], 19 | }; 20 | } 21 | 22 | onModalButtonClick = () => { 23 | this.setState({ isModalOpen: !this.state.isModalOpen }); 24 | }; 25 | 26 | onSubmit = async () => { 27 | try { 28 | const fd = new FormData(); 29 | fd.append('content', this.state.content); 30 | fd.append('postImage', this.state.file); 31 | fd.append('post_url', this.state.url); 32 | fd.append('tags', JSON.stringify(this.state.selectedUser)); 33 | 34 | this.setState({ isLoading: true }); 35 | 36 | let result = await axios.post('/posts/create', fd); 37 | 38 | await this.props.updatePost(result.data); 39 | 40 | this.setState( 41 | { content: '', url: '', file: '', selectedUser: [] }, 42 | () => { 43 | this.setState({ isLoading: false }); 44 | } 45 | ); 46 | toast.success('Post created successfully'); 47 | } catch (error) { 48 | this.setState( 49 | { content: '', url: '', file: '', selectedUser: [] }, 50 | () => { 51 | this.setState({ isLoading: false }); 52 | } 53 | ); 54 | if ( 55 | error && 56 | error.response && 57 | error.response.data && 58 | error.response.data.message 59 | ) { 60 | toast.error(error.response.data.message); 61 | } else { 62 | toast.error('Something went wrong! Try again'); 63 | } 64 | } 65 | }; 66 | 67 | onChange = (event) => { 68 | this.setState({ [event.target.name]: event.target.value }); 69 | }; 70 | 71 | imageFileHandler = (event) => { 72 | this.setState({ file: event.target.files[0] }); 73 | }; 74 | 75 | onUserClick = (userId) => { 76 | let currentState = [...this.state.selectedUser]; 77 | if (this.state.selectedUser.includes(userId)) { 78 | let index = currentState.indexOf(userId); 79 | currentState.splice(index, 1); 80 | this.setState({ selectedUser: currentState }); 81 | } else { 82 | currentState.push(userId); 83 | this.setState({ selectedUser: currentState }); 84 | } 85 | }; 86 | 87 | render() { 88 | return ( 89 |
90 |
91 | post 95 |

96 | {this.props.user.name} 97 |

98 |
99 |
104 | 111 | 112 | 121 | 122 | 128 | 129 | (this.fileInput = fileInput)} 135 | /> 136 | 142 | 149 |
150 |
154 | 162 |
163 |
164 | ); 165 | } 166 | } 167 | 168 | const mapStateToProps = (state) => ({ 169 | user: state.auth.user, 170 | }); 171 | 172 | export default connect(mapStateToProps)(CreatePost); 173 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/CreatePost/CreatePost.scss: -------------------------------------------------------------------------------- 1 | .newsfeed__create-post { 2 | margin-bottom: 3rem; 3 | &__profile { 4 | display: flex; 5 | align-items: center; 6 | margin-bottom: 1.2rem; 7 | img { 8 | width: 3.5rem; 9 | height: 3.5rem; 10 | margin-right: 1.5rem; 11 | border-radius: 50%; 12 | } 13 | 14 | &__name { 15 | font-weight: 600; 16 | font-size: 1.2rem; 17 | } 18 | } 19 | 20 | &__input { 21 | input { 22 | display: inline-block; 23 | width: 40%; 24 | border: none; 25 | height: 4rem; 26 | border-radius: 2rem; 27 | box-shadow: inset 0 1px 3px lightgrey; 28 | padding: 1.2rem; 29 | } 30 | 31 | &__button-add, 32 | &__post { 33 | background-color: #fff; 34 | height: 4rem; 35 | width: 4rem; 36 | border-radius: 50%; 37 | border: 1px solid var(--color-grey-light-3); 38 | margin-left: 1.5rem; 39 | transition: all 0.2s; 40 | } 41 | 42 | &__button-add:hover { 43 | transform: scale(1.4); 44 | } 45 | 46 | &__post { 47 | color: white; 48 | background-color: var(--color-primary); 49 | 50 | &:hover { 51 | background-color: var(--color-primary-dark); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Home.scss'; 3 | import HomeRight from './HomeRight/HomeRight'; 4 | import NewsFeed from './NewsFeed/NewsFeed'; 5 | const Home = (props) => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default Home; 15 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/Modal/Modal.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import Modal from 'react-modal'; 4 | import uniqueId from 'uniqid'; 5 | import './Modal.scss'; 6 | 7 | Modal.setAppElement('#root'); 8 | class ModalComponent extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | users: [], 13 | }; 14 | } 15 | async componentDidMount() { 16 | const result = await axios.get('/users/get-users'); 17 | this.setState({ users: result.data.users }); 18 | } 19 | 20 | render() { 21 | const { isModalOpen, closeModal, onUserClick, selectedUser } = this.props; 22 | 23 | return ( 24 | 25 |
26 |

Tag People

27 | 28 |
29 | 30 |
31 | {this.state.users.map((ele) => { 32 | return ( 33 |
onUserClick(ele._id)} 41 | className="modal-users__single" 42 | > 43 | 44 |
{ele.name}
45 |
46 | ); 47 | })} 48 |
49 |
50 | ); 51 | } 52 | } 53 | 54 | export default ModalComponent; 55 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/Modal/Modal.scss: -------------------------------------------------------------------------------- 1 | .modal-header { 2 | button { 3 | padding: 1rem; 4 | color: white; 5 | background-color: var(--color-primary); 6 | } 7 | } 8 | 9 | .modal-users__single { 10 | padding: 1rem; 11 | display: flex; 12 | align-items: center; 13 | margin: 1rem; 14 | background-color: var(--color-grey-light-1); 15 | box-shadow: 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 16 | cursor: pointer; 17 | img { 18 | height: 5rem; 19 | width: 5rem; 20 | margin-right: 3rem; 21 | } 22 | 23 | div { 24 | font-size: 1.5rem; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/NewsFeed.js: -------------------------------------------------------------------------------- 1 | import CreatePost from './CreatePost/CreatePost'; 2 | import './NewsFeed.scss'; 3 | import SinglePost from './SinglePost/SinglePost'; 4 | import Search from '../../../components/Search/Search'; 5 | import React, { Component } from 'react'; 6 | import axios from 'axios'; 7 | import uniqueId from 'uniqid'; 8 | import SharedPost from './SharedPost/SharedPost'; 9 | 10 | import { toast } from 'react-toastify'; 11 | import 'react-toastify/dist/ReactToastify.css'; 12 | import { connect } from 'react-redux'; 13 | 14 | toast.configure(); 15 | 16 | class NewsFeed extends Component { 17 | constructor() { 18 | super(); 19 | this.state = { 20 | posts: [], 21 | }; 22 | } 23 | 24 | async componentDidMount() { 25 | const result = await axios.get('/users/get-posts'); 26 | this.setState({ posts: result.data }); 27 | } 28 | 29 | editPostLike = (posts) => { 30 | this.setState({ posts }); 31 | }; 32 | 33 | deletePost = async (postData) => { 34 | try { 35 | const result = await axios.get(`/posts/delete/${postData._id}`); 36 | this.setState({ posts: result.data }); 37 | toast.success('Post deleted successfully'); 38 | } catch (error) { 39 | if (error.response.data.message) { 40 | toast.error(error.response.data.message); 41 | } else { 42 | toast.error('Something went wrong! Try again'); 43 | } 44 | } 45 | }; 46 | 47 | updatePost = async (postData) => { 48 | this.setState({ posts: postData }); 49 | }; 50 | 51 | render() { 52 | return ( 53 |
54 | 55 | 56 | 57 | {this.state.posts.map((ele) => { 58 | if (ele.isShared) { 59 | return ( 60 | 68 | ); 69 | } else { 70 | return ( 71 | 79 | ); 80 | } 81 | })} 82 |
83 | ); 84 | } 85 | } 86 | 87 | const mapStateToProps = (state) => ({ 88 | bookmarks: state.auth.bookmarks, 89 | }); 90 | 91 | export default connect(mapStateToProps)(NewsFeed); 92 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/NewsFeed.scss: -------------------------------------------------------------------------------- 1 | .home__newsfeed { 2 | margin-left: 25%; 3 | width: 40%; 4 | padding-bottom: 10rem; 5 | @media only screen and (max-width: 700px) { 6 | width: 60% !important; 7 | margin-left: 30% !important; 8 | } 9 | } 10 | .newsfeed__single-post { 11 | margin-bottom: 5rem; 12 | 13 | &__tags { 14 | margin: 2rem 0; 15 | 16 | a { 17 | margin: 0 0.5rem; 18 | font-size: 1.3rem; 19 | color: var(--color-primary); 20 | 21 | &:hover { 22 | color: var(--color-primary-dark); 23 | } 24 | } 25 | } 26 | 27 | &__comment { 28 | background-color: pink; 29 | height: 5rem; 30 | overflow: hidden; 31 | display: flex; 32 | border-radius: 2rem; 33 | margin-bottom: 3rem; 34 | input { 35 | width: 80%; 36 | height: 100%; 37 | flex: 1; 38 | border: none; 39 | box-shadow: inset 0 1px 3px lightgrey; 40 | padding: 1rem; 41 | } 42 | 43 | button { 44 | padding: 1rem; 45 | font-size: 1.3rem; 46 | color: white; 47 | background-image: linear-gradient(to right bottom, #30a9de, #1977a0); 48 | } 49 | } 50 | 51 | &__text { 52 | font-size: 1.3rem; 53 | color: var(--color-grey-dark-3); 54 | margin-top: 1rem; 55 | margin-bottom: 1rem; 56 | } 57 | 58 | &__likes { 59 | font-size: 1.3rem; 60 | margin: 1rem 0; 61 | span { 62 | font-weight: 700; 63 | } 64 | } 65 | 66 | &__top { 67 | display: flex; 68 | align-items: center; 69 | justify-content: space-between; 70 | 71 | &__left { 72 | display: flex; 73 | align-items: center; 74 | a { 75 | margin-right: 1rem; 76 | img { 77 | width: 5rem; 78 | height: 5rem; 79 | border-radius: 50%; 80 | } 81 | } 82 | 83 | &__name { 84 | font-size: 1.5rem; 85 | text-transform: capitalize; 86 | } 87 | } 88 | 89 | &__right { 90 | &__icon { 91 | color: currentColor; 92 | font-size: 1.4rem; 93 | } 94 | button { 95 | width: 5rem; 96 | height: 3rem; 97 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); 98 | color: var(--color-grey-dark-3) !important; 99 | background-color: white; 100 | &:hover { 101 | color: var(--color-grey-dark-1) !important; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | .single-post__url { 109 | height: 6rem; 110 | width: 90%; 111 | display: block; 112 | background-color: var(--color-grey-light-4); 113 | margin: auto; 114 | display: flex; 115 | margin-bottom: 20px; 116 | text-decoration: none; 117 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 118 | } 119 | 120 | .single-post__url:hover { 121 | text-decoration: none; 122 | } 123 | .single-post__url__img { 124 | width: 20%; 125 | height: 100%; 126 | } 127 | 128 | .single-post__url__title { 129 | height: 100%; 130 | width: 80%; 131 | overflow: hidden; 132 | display: flex; 133 | justify-content: center; 134 | align-items: center; 135 | padding: 5px; 136 | } 137 | 138 | .share-post { 139 | margin-bottom: 5rem; 140 | } 141 | 142 | .share-post-single { 143 | border: 1px solid var(--color-grey-light-4); 144 | margin-top: 2rem; 145 | padding: 2rem; 146 | margin-bottom: 1rem; 147 | } 148 | 149 | .shared-title { 150 | font-size: 1.4rem; 151 | font-weight: 600; 152 | margin-left: 0rem; 153 | } 154 | 155 | .single-comment { 156 | background-color: var(--color-grey-light-3); 157 | box-shadow: inset 0 1px 3px lightgrey; 158 | border-top-left-radius: 10px; 159 | border-bottom-right-radius: 10px; 160 | padding: 10px; 161 | display: flex; 162 | align-items: center; 163 | justify-content: space-between; 164 | margin-bottom: 15px; 165 | 166 | &__delete-button { 167 | width: 5rem; 168 | height: 4rem; 169 | background-color: white; 170 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); 171 | color: var(--color-grey-light-4); 172 | 173 | &:hover { 174 | color: var(--color-grey-dark-3); 175 | } 176 | 177 | &__icon { 178 | font-size: 1.4rem; 179 | color: currentColor; 180 | } 181 | } 182 | 183 | &__profile { 184 | width: 15%; 185 | display: flex; 186 | flex-direction: column; 187 | justify-content: center; 188 | align-items: center; 189 | &__img { 190 | width: 5rem; 191 | height: 5rem; 192 | display: block; 193 | overflow: hidden; 194 | border-radius: 50%; 195 | 196 | img { 197 | width: 100%; 198 | height: 100%; 199 | } 200 | } 201 | 202 | &__name { 203 | font-size: 1.2rem; 204 | } 205 | } 206 | 207 | &__text { 208 | width: 70%; 209 | text-align: left; 210 | font-size: 1.2rem; 211 | } 212 | } 213 | 214 | .bookmarked-icon { 215 | color: var(--color-primary) !important; 216 | } 217 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/SharedPost/SharedPost.js: -------------------------------------------------------------------------------- 1 | // import './S'; 2 | import axios from 'axios'; 3 | import React, { Component } from 'react'; 4 | import { connect } from 'react-redux'; 5 | import { toast } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | import { updateBookmarks } from '../../../../redux/auth/auth.actions'; 8 | import Comment from '../Comment/Comment'; 9 | 10 | toast.configure(); 11 | 12 | class SharedPost extends Component { 13 | constructor() { 14 | super(); 15 | this.state = { 16 | comment: '', 17 | }; 18 | } 19 | 20 | onLikeClickHandler = async (event) => { 21 | const result = await axios.get(`/like/toggle/${this.props.postData._id}`); 22 | this.props.editPostLike(result.data); 23 | }; 24 | 25 | onSharePost = async (event) => { 26 | try { 27 | const result = await axios.get(`/posts/share/${this.props.postData._id}`); 28 | } catch (error) {} 29 | }; 30 | 31 | addToBookmark = async () => { 32 | try { 33 | const result = await axios.get( 34 | `/bookmark/add/${this.props.postData._id}` 35 | ); 36 | 37 | this.props.updateBookmarks(result.data.bookmarks); 38 | 39 | toast.success('post bookmarked successfully'); 40 | } catch (error) { 41 | if (error.response.data.message) { 42 | toast.error(error.response.data.message); 43 | } else { 44 | toast.error('Something went wrong! Try again'); 45 | } 46 | } 47 | }; 48 | 49 | onChange = (event) => { 50 | this.setState({ [event.target.name]: event.target.value }); 51 | }; 52 | 53 | addComment = async () => { 54 | try { 55 | const data = { 56 | description: this.state.comment, 57 | post_value: this.props.postData._id, 58 | }; 59 | 60 | const result = await axios.post('/comment/create', data); 61 | this.props.updatePost(result.data); 62 | toast.success('comment added succesfully'); 63 | } catch (error) { 64 | if (error.response.data.message) { 65 | toast.error(error.response.data.message); 66 | } else { 67 | toast.error('Something went wrong! Try again'); 68 | } 69 | } 70 | }; 71 | 72 | render() { 73 | const { postData, bookmarks } = this.props; 74 | 75 | const isBookmarked = bookmarks.some((el) => el._id == postData._id) 76 | ? true 77 | : false; 78 | 79 | let bookmarkHtml; 80 | 81 | if (isBookmarked) { 82 | bookmarkHtml = ( 83 | 89 | ); 90 | } else { 91 | bookmarkHtml = ( 92 | 95 | ); 96 | } 97 | 98 | let likeHtml; 99 | let flag; 100 | if (postData) { 101 | for (let like of postData.likes) { 102 | if (like.user == this.props.user._id) { 103 | flag = true; 104 | likeHtml = ( 105 | 109 | ); 110 | } 111 | } 112 | 113 | if (!flag) { 114 | likeHtml = ( 115 | 116 | ); 117 | } 118 | 119 | return ( 120 |
121 |
122 | 134 |
135 | 136 | {postData.sharedUser._id === this.props.user._id ? ( 137 | 140 | ) : null} 141 | 142 | {bookmarkHtml} 143 |
144 |
145 |
146 | 159 |
160 | {postData.description} 161 |
162 | 163 | {postData.url ? ( 164 | 169 | not found 174 |
175 | {postData.urlTitle} 176 |
177 |
178 | ) : null} 179 | 180 | {postData.postImage ? ( 181 | 190 | ) : null} 191 |
192 |
193 | Liked by {postData.likes.length} people 194 |
195 | 196 |
197 | 198 | 199 |
200 | 201 |
202 | {postData.comments.map((ele) => ( 203 | 208 | ))} 209 |
210 |
211 | ); 212 | } 213 | } 214 | } 215 | const mapStateToProps = (state) => ({ 216 | user: state.auth.user, 217 | isAuth: state.auth.isAuth, 218 | }); 219 | 220 | export default connect(mapStateToProps, { updateBookmarks })(SharedPost); 221 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/SinglePost/SinglePost.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Link } from 'react-router-dom'; 5 | import { toast } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | import uniqueId from 'uniqid'; 8 | import { updateBookmarks } from '../../../../redux/auth/auth.actions'; 9 | import Comment from '../Comment/Comment'; 10 | import './SinglePost.scss'; 11 | 12 | toast.configure(); 13 | 14 | export class SinglePost extends Component { 15 | constructor() { 16 | super(); 17 | this.state = { 18 | comment: '', 19 | }; 20 | } 21 | 22 | onLikeClickHandler = async (event) => { 23 | const result = await axios.get(`/like/toggle/${this.props.postData._id}`); 24 | this.props.editPostLike(result.data); 25 | }; 26 | 27 | onChange = (event) => { 28 | this.setState({ [event.target.name]: event.target.value }); 29 | }; 30 | 31 | onSharePost = async (event) => { 32 | try { 33 | const result = await axios.get(`/posts/share/${this.props.postData._id}`); 34 | this.props.updatePost(result.data); 35 | toast.success('Post shared successfully'); 36 | } catch (error) { 37 | if (error.response.data.message) { 38 | toast.error(error.response.data.message); 39 | } else { 40 | toast.error('Something went wrong! Try again'); 41 | } 42 | } 43 | }; 44 | 45 | addToBookmark = async () => { 46 | try { 47 | const result = await axios.get( 48 | `/bookmark/add/${this.props.postData._id}` 49 | ); 50 | 51 | this.props.updateBookmarks(result.data.bookmarks); 52 | 53 | toast.success('post bookmarked successfully'); 54 | } catch (error) { 55 | if (error.response.data.message) { 56 | toast.error(error.response.data.message); 57 | } else { 58 | toast.error('Something went wrong! Try again'); 59 | } 60 | } 61 | }; 62 | 63 | addComment = async () => { 64 | try { 65 | const data = { 66 | description: this.state.comment, 67 | post_value: this.props.postData._id, 68 | }; 69 | const result = await axios.post('/comment/create', data); 70 | this.props.updatePost(result.data); 71 | 72 | toast.success('comment added succesfully'); 73 | } catch (error) { 74 | if (error.response.data.message) { 75 | toast.error(error.response.data.message); 76 | } else { 77 | toast.error('Something went wrong! Try again'); 78 | } 79 | } 80 | }; 81 | 82 | render() { 83 | const { postData, bookmarks } = this.props; 84 | 85 | const isBookmarked = bookmarks.some((el) => el._id == postData._id) 86 | ? true 87 | : false; 88 | 89 | let bookmarkHtml; 90 | 91 | if (isBookmarked) { 92 | bookmarkHtml = ( 93 | 99 | ); 100 | } else { 101 | bookmarkHtml = ( 102 | 105 | ); 106 | } 107 | 108 | let likeHtml; 109 | let flag; 110 | if (postData) { 111 | for (let like of postData.likes) { 112 | if (like.user == this.props.user._id) { 113 | flag = true; 114 | likeHtml = ( 115 | 119 | ); 120 | } 121 | } 122 | 123 | if (!flag) { 124 | likeHtml = ( 125 | 126 | ); 127 | } 128 | 129 | return ( 130 |
131 |
132 |
133 | 134 | 138 | 139 | 140 | 144 | {postData.user.name} 145 | 146 |
147 |
148 | 149 | {postData.user._id === this.props.user._id ? ( 150 | 153 | ) : null} 154 | 155 | 158 | 159 | {bookmarkHtml} 160 |
161 |
162 |
163 | {postData.description} 164 |
165 | 166 |
167 | {postData.tags.map((ele) => ( 168 | 169 | @{ele.name} 170 | 171 | ))} 172 |
173 | 174 | {postData.url ? ( 175 | 176 | not found 181 |
{postData.urlTitle}
182 |
183 | ) : null} 184 | 185 | {postData.postImage ? ( 186 | 195 | ) : null} 196 | 197 |
198 | Liked by {postData.likes.length} people 199 |
200 |
201 | 202 | 203 |
204 | 205 |
206 | {postData.comments.map((ele) => ( 207 | 212 | ))} 213 |
214 |
215 | ); 216 | } 217 | } 218 | } 219 | const mapStateToProps = (state) => ({ 220 | user: state.auth.user, 221 | isAuth: state.auth.isAuth, 222 | }); 223 | 224 | export default connect(mapStateToProps, { updateBookmarks })(SinglePost); 225 | -------------------------------------------------------------------------------- /client/src/pages/Home/NewsFeed/SinglePost/SinglePost.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/client/src/pages/Home/NewsFeed/SinglePost/SinglePost.scss -------------------------------------------------------------------------------- /client/src/pages/Management/Management.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { toast } from 'react-toastify'; 5 | import 'react-toastify/dist/ReactToastify.css'; 6 | import uniqueId from 'uniqid'; 7 | import './Management.scss'; 8 | 9 | toast.configure(); 10 | 11 | class Management extends Component { 12 | constructor() { 13 | super(); 14 | this.state = { 15 | users: [], 16 | }; 17 | } 18 | 19 | async componentDidMount() { 20 | const result = await axios.get('/users/get-users'); 21 | 22 | this.setState({ users: result.data.users }); 23 | } 24 | 25 | deleteUser = async (email) => { 26 | try { 27 | const result = await axios.post('/users/management/delete-user', { 28 | email, 29 | }); 30 | this.setState({ users: result.data.users }); 31 | toast.success(result.data.message); 32 | } catch (error) { 33 | if (error.response.data.message) { 34 | toast.error(error.response.data.message); 35 | } else { 36 | toast.error('Something went wrong! Try again'); 37 | } 38 | } 39 | }; 40 | 41 | render() { 42 | if (this.props.user.email != 'admin@threadsapp.co.in') { 43 | return ( 44 |
45 |

Not authorize

46 |
47 | ); 48 | } 49 | 50 | return ( 51 |
52 |
53 |
#
54 |
Name
55 |
Email
56 |
Status
57 |
Action
58 |
59 | {this.state.users.map((ele, index) => { 60 | return ( 61 |
62 |
{index + 1}
63 |
{ele.name}
64 |
{ele.email}
65 |
66 | 67 | Active 68 |
69 |
70 |
this.deleteUser(ele.email)} 72 | className="manage__item__action__container" 73 | > 74 | 75 |
76 |
77 |
78 | ); 79 | })} 80 |
81 | ); 82 | } 83 | } 84 | 85 | const mapStateToProps = (state) => ({ 86 | user: state.auth.user, 87 | }); 88 | 89 | export default connect(mapStateToProps)(Management); 90 | -------------------------------------------------------------------------------- /client/src/pages/Management/Management.scss: -------------------------------------------------------------------------------- 1 | .manage { 2 | margin-left: 20%; 3 | padding: 1rem; 4 | margin-bottom: 20rem; 5 | 6 | h3 { 7 | color: var(--color-primary); 8 | font-size: 4rem; 9 | margin-top: 2rem; 10 | text-align: center; 11 | text-transform: capitalize; 12 | } 13 | 14 | &__header { 15 | width: 100%; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | 20 | & > div { 21 | font-size: 1.5rem; 22 | font-weight: 600; 23 | text-align: center; 24 | padding: 1rem 0; 25 | border-bottom: 1px solid black; 26 | border-top: 1px solid black; 27 | } 28 | 29 | &__number { 30 | width: 10%; 31 | } 32 | 33 | &__name { 34 | width: 20%; 35 | } 36 | 37 | &__email { 38 | width: 50%; 39 | } 40 | 41 | &__status { 42 | width: 10%; 43 | } 44 | 45 | &__action { 46 | width: 20%; 47 | } 48 | } 49 | 50 | &__item { 51 | width: 100%; 52 | display: flex; 53 | justify-content: space-between; 54 | align-items: center; 55 | 56 | & > div { 57 | font-size: 1.2rem; 58 | text-align: center; 59 | padding: 1rem 0; 60 | border-bottom: 1px solid black; 61 | } 62 | 63 | &__number { 64 | width: 10%; 65 | } 66 | 67 | &__name { 68 | width: 20%; 69 | } 70 | 71 | &__email { 72 | width: 50%; 73 | } 74 | 75 | &__status { 76 | width: 10%; 77 | &__icon { 78 | color: green; 79 | margin-right: 1rem; 80 | } 81 | } 82 | 83 | &__action { 84 | width: 20%; 85 | 86 | &__container { 87 | cursor: pointer; 88 | } 89 | &__icon { 90 | color: red; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/src/pages/Profile/Profile.scss: -------------------------------------------------------------------------------- 1 | .profile-page { 2 | margin: auto; 3 | padding-top: 5rem; 4 | margin-left: 20%; 5 | // background-color: blue; 6 | padding-bottom: 15rem; 7 | 8 | &__follow-button { 9 | background-color: var(--color-primary); 10 | padding: 1rem 2rem; 11 | color: white; 12 | font-size: 1.2rem; 13 | display: block; 14 | margin: auto; 15 | margin-bottom: 2rem; 16 | } 17 | } 18 | 19 | .img-profile { 20 | height: 20rem; 21 | width: 20rem; 22 | border-radius: 50%; 23 | display: block; 24 | margin: auto; 25 | overflow: hidden; 26 | margin-bottom: 2rem; 27 | 28 | img { 29 | width: 100%; 30 | height: 100%; 31 | } 32 | } 33 | 34 | .profile-container1 { 35 | display: flex; 36 | align-items: center; 37 | // justify-content: center; 38 | margin-bottom: 1.5rem; 39 | margin-left: 10%; 40 | 41 | a { 42 | margin-right: 2rem; 43 | } 44 | } 45 | 46 | .profile-button { 47 | width: 80%; 48 | margin-left: 10%; 49 | margin-bottom: 2rem; 50 | } 51 | -------------------------------------------------------------------------------- /client/src/pages/SearchPage/SearchPage.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { Component } from 'react'; 3 | import { toast } from 'react-toastify'; 4 | import 'react-toastify/dist/ReactToastify.css'; 5 | import uniqid from 'uniqid'; 6 | import Search from '../../components/Search/Search'; 7 | import SmallProfile from '../../components/SmallProfile/SmallProfile'; 8 | import '../AllUser/AllUser.scss'; 9 | 10 | toast.configure(); 11 | 12 | class SearchPage extends Component { 13 | constructor() { 14 | super(); 15 | this.state = { 16 | users: [], 17 | }; 18 | } 19 | 20 | async componentDidMount() { 21 | try { 22 | const result = await axios.post('/search', { 23 | search_text: this.props.match.params.text, 24 | }); 25 | 26 | this.setState({ users: result.data }); 27 | } catch (error) { 28 | if (error.response.data.message) { 29 | toast.error(error.response.data.message); 30 | } else { 31 | toast.error('Something went wrong! Try again'); 32 | } 33 | } 34 | } 35 | 36 | async componentDidUpdate(prevProps) { 37 | if (prevProps.match.params.text != this.props.match.params.text) { 38 | try { 39 | const result = await axios.post('/search', { 40 | search_text: this.props.match.params.text, 41 | }); 42 | 43 | this.setState({ users: result.data }); 44 | } catch (error) { 45 | if (error.response.data.message) { 46 | toast.error(error.response.data.message); 47 | } else { 48 | toast.error('Something went wrong! Try again'); 49 | } 50 | } 51 | } 52 | } 53 | 54 | render() { 55 | return ( 56 |
57 | 58 | {this.state.users.length > 0 ? ( 59 | this.state.users.map((ele) => ( 60 | 61 | )) 62 | ) : ( 63 |

No users found

64 | )} 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default SearchPage; 71 | -------------------------------------------------------------------------------- /client/src/redux/auth/auth.actions.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { toast } from 'react-toastify'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | import { AUTH_TYPES } from './auth.types'; 5 | import { logOut, setAuthUser } from './auth.utils'; 6 | 7 | 8 | toast.configure(); 9 | 10 | export const signUpUser = (data) => async (dispatch) => { 11 | try { 12 | dispatch({ type: AUTH_TYPES.AUTH_START }); 13 | const result = await axios.post('users/register', data); 14 | 15 | setAuthUser(result.data.token, dispatch, result.data); 16 | toast.success('Created account successfully'); 17 | } catch (error) { 18 | if (error.response && error.response.data.message) { 19 | toast.error(error.response.data.message); 20 | } else { 21 | toast.error('Something went wrong! Try again'); 22 | } 23 | } 24 | }; 25 | 26 | export const singInUser = (data) => async (dispatch) => { 27 | try { 28 | dispatch({ type: AUTH_TYPES.AUTH_START }); 29 | 30 | const result = await axios.post('/users/login', data); 31 | 32 | setAuthUser(result.data.token, dispatch, result.data); 33 | } catch (error) { 34 | if (error.response.data.message) { 35 | return toast.error(error.response.data.message); 36 | } else { 37 | return toast.error('Something went wrong! Try again'); 38 | } 39 | } 40 | }; 41 | 42 | export const logOutUser = () => (dispatch) => { 43 | logOut(dispatch); 44 | }; 45 | 46 | export const fetchUserData = () => async (dispatch) => { 47 | try { 48 | dispatch({ 49 | type: AUTH_TYPES.FETCH_USER_DATA_START, 50 | }); 51 | 52 | const result = await axios.get('/users/single'); 53 | 54 | dispatch({ 55 | type: AUTH_TYPES.FETCH_USER_DATA, 56 | payload: result.data.user, 57 | }); 58 | } catch (error) { 59 | dispatch({ type: AUTH_TYPES.FETCH_USER_DATA_END }); 60 | toast.error('Something is wrong'); 61 | } 62 | }; 63 | 64 | export const updateUserData = (data) => async (dispatch) => { 65 | dispatch({ 66 | type: AUTH_TYPES.FETCH_USER_DATA, 67 | payload: data, 68 | }); 69 | }; 70 | 71 | export const fetchBookmarks = () => async (dispatch) => { 72 | try { 73 | const result = await axios.get('/bookmark'); 74 | 75 | dispatch({ 76 | type: AUTH_TYPES.FETCH_BOOKMARKS, 77 | payload: result.data, 78 | }); 79 | } catch (error) { 80 | if (error.response.data.message) { 81 | toast.error(error.response.data.message); 82 | } else { 83 | toast.error('Something went wrong! Try again'); 84 | } 85 | } 86 | }; 87 | 88 | export const updateBookmarks = (data) => async (dispatch) => { 89 | try { 90 | dispatch({ 91 | type: AUTH_TYPES.FETCH_BOOKMARKS, 92 | payload: data, 93 | }); 94 | } catch (error) { 95 | if (error.response.data.message) { 96 | toast.error(error.response.data.message); 97 | } else { 98 | toast.error('Something went wrong! Try again'); 99 | } 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /client/src/redux/auth/auth.reducer.js: -------------------------------------------------------------------------------- 1 | import { AUTH_TYPES } from './auth.types'; 2 | 3 | const INITIAL_STATE = { 4 | user: null, 5 | bookmarks: [], 6 | error: null, 7 | token: null, 8 | isAuth: false, 9 | loginError: null, 10 | registerError: {}, 11 | isLoading: false, 12 | fetchUserLoading: false, 13 | }; 14 | 15 | const authReducer = (state = INITIAL_STATE, action) => { 16 | switch (action.type) { 17 | case AUTH_TYPES.FETCH_USER_DATA_START: 18 | return { 19 | ...state, 20 | fetchUserLoading: true, 21 | }; 22 | 23 | case AUTH_TYPES.FETCH_BOOKMARKS: 24 | return { 25 | ...state, 26 | bookmarks: action.payload, 27 | }; 28 | 29 | case AUTH_TYPES.FETCH_USER_DATA_END: 30 | return { 31 | ...state, 32 | fetchUserLoading: false, 33 | }; 34 | 35 | case AUTH_TYPES.SET_AUTH_USER: 36 | return { 37 | ...state, 38 | user: action.payload.user, 39 | token: action.payload.token, 40 | isAuth: true, 41 | loginError: null, 42 | isLoading: false, 43 | }; 44 | 45 | case AUTH_TYPES.LOG_OUT_USER: 46 | window.localStorage.removeItem('jwt_token'); 47 | return { 48 | ...state, 49 | user: null, 50 | token: null, 51 | isAuth: false, 52 | }; 53 | 54 | case AUTH_TYPES.FETCH_USER_DATA: 55 | return { 56 | ...state, 57 | user: action.payload, 58 | isAuth: true, 59 | fetchUserLoading: false, 60 | }; 61 | 62 | case AUTH_TYPES.SET_AUTH_LOGIN_ERROR: 63 | return { 64 | ...state, 65 | loginError: action.payload, 66 | isLoading: false, 67 | }; 68 | 69 | case AUTH_TYPES.SET_AUTH_REGISTER_ERROR: 70 | return { 71 | ...state, 72 | registerError: action.payload, 73 | isLoading: false, 74 | }; 75 | 76 | case AUTH_TYPES.AUTH_START: 77 | return { 78 | ...state, 79 | isLoading: true, 80 | }; 81 | 82 | default: 83 | return state; 84 | } 85 | }; 86 | 87 | export default authReducer; 88 | -------------------------------------------------------------------------------- /client/src/redux/auth/auth.types.js: -------------------------------------------------------------------------------- 1 | export const AUTH_TYPES = { 2 | SIGN_UP_FAILURE: 'SING_UP_FAILURE', 3 | SET_AUTH_USER: 'SET_AUTH_USER', 4 | LOG_OUT_USER: 'lOG_OUT_USER', 5 | FETCH_USER_DATA: 'FETCH_USER_DATA', 6 | SET_AUTH_LOGIN_ERROR: 'SET_AUTH_LOGIN_ERROR', 7 | SET_AUTH_REGISTER_ERROR: 'SET_AUTH_REGISTER_ERROR', 8 | AUTH_START: 'AUTH_START', 9 | FETCH_USER_DATA_START: 'FETCH_USER_DATA_START', 10 | FETCH_USER_DATA_END: 'FETCH_USER_DATA_END', 11 | FETCH_BOOKMARKS: 'FETCH_BOOKMARKS', 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/redux/auth/auth.utils.js: -------------------------------------------------------------------------------- 1 | import { AUTH_TYPES } from './auth.types'; 2 | 3 | export const setAuthUser = (token, dispatch, data) => { 4 | if (!token) { 5 | return; 6 | } 7 | 8 | window.localStorage.setItem('jwt_token', token); 9 | console.log(data); 10 | 11 | dispatch({ 12 | type: AUTH_TYPES.SET_AUTH_USER, 13 | payload: data 14 | }); 15 | }; 16 | 17 | export const logOut = (dispatch) => { 18 | dispatch({ 19 | type: AUTH_TYPES.LOG_OUT_USER 20 | }); 21 | 22 | window.localStorage.removeItem('jwt_token'); 23 | }; 24 | -------------------------------------------------------------------------------- /client/src/redux/root-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authReducer from './auth/auth.reducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | auth: authReducer, 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import thunk from 'redux-thunk'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import rootReducer from './root-reducer'; 4 | import { composeWithDevTools } from 'redux-devtools-extension'; 5 | 6 | const middlewares = [thunk]; 7 | 8 | export const store = createStore( 9 | rootReducer, 10 | composeWithDevTools(applyMiddleware(...middlewares)) 11 | ); 12 | 13 | export default store; 14 | -------------------------------------------------------------------------------- /config.env: -------------------------------------------------------------------------------- 1 | # key=value 2 | 3 | JWT_KEY=thr3@ds@000 4 | # multer s3 configuration 5 | ACCESSKEYID= 6 | SECRETACCESSKEY= 7 | REGION= 8 | BUCKET_NAME= 9 | 10 | 11 | DATABASE_NAME=vulnerable_dev_database 12 | PORT=4000 -------------------------------------------------------------------------------- /config/flask_message.js: -------------------------------------------------------------------------------- 1 | module.exports.setFashMessage = (req, res, next) => { 2 | res.locals.flash = { 3 | success: req.flash('success'), 4 | error: req.flash('error'), 5 | }; 6 | next(); 7 | }; 8 | -------------------------------------------------------------------------------- /config/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | // Use MONGODB_URI if defined, else default to localhost connection 4 | const mongoURI = process.env.MONGODB_URI || `mongodb://localhost/${process.env.DATABASE_NAME}`; 5 | 6 | mongoose.connect(mongoURI, { useNewUrlParser: true, useUnifiedTopology: true }); 7 | 8 | const db = mongoose.connection; 9 | 10 | db.on('error', console.error.bind(console, 'Error connecting to MongoDB')); 11 | 12 | db.once('open', function () { 13 | console.log('Connected to Database :: MongoDB'); 14 | }); 15 | 16 | module.exports = db; 17 | 18 | -------------------------------------------------------------------------------- /config/multer_s3.js: -------------------------------------------------------------------------------- 1 | const multerS3 = require('multer-s3'); 2 | const multer = require('multer'); 3 | const AWS = require('aws-sdk'); 4 | 5 | const s3 = new AWS.S3({ 6 | accessKeyId: process.env.ACCESSKEYID, 7 | secretAccessKey: process.env.SECRETACCESSKEY, 8 | region: process.env.REGION, 9 | }); 10 | const imageFilter = (req, file, cb) => { 11 | if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') { 12 | cb(null, true); 13 | } else { 14 | cb(null, false); 15 | } 16 | }; 17 | 18 | const middleware = function (req, res, next) { 19 | var upload = multer({ 20 | limits: { 21 | fileSize: 7 * 1024 * 1024, // 7MB 22 | }, 23 | fileFilter: imageFilter, 24 | storage: multerS3({ 25 | s3: s3, 26 | bucket: process.env.BUCKET_NAME, 27 | acl: 'public-read', 28 | cacheControl: 'max-age=31536000', 29 | contentType: multerS3.AUTO_CONTENT_TYPE, 30 | metadata: function (req, file, cb) { 31 | cb(null, { fieldName: file.fieldname }); 32 | }, 33 | key: function (req, file, cb) { 34 | cb(null, Date.now().toString() + '-' + file.originalname); 35 | }, 36 | }), 37 | }).single('postImage'); 38 | 39 | upload(req, res, function (err) { 40 | if (err instanceof multer.MulterError) { 41 | // if error happens because of file size 42 | if (err.code === 'LIMIT_FILE_SIZE') { 43 | // req.flash('error', 'Large file size'); 44 | return res.status(400).json({ message: 'File size is big' }); 45 | } 46 | 47 | // req.flash('error', 'Something went wrong try again'); 48 | console.log(err); 49 | 50 | return res.status(400).json({ 51 | message: 'Something went wrong in uploading images! Try again', 52 | }); 53 | } else if (err) { 54 | // req.flash('error', 'Something went wrong try again'); 55 | console.log(err); 56 | return res.status(400).json({ 57 | message: 'Something went wrong in uploading images! Try again', 58 | }); 59 | } 60 | next(); 61 | }); 62 | }; 63 | 64 | module.exports = middleware; 65 | -------------------------------------------------------------------------------- /config/muter_upload.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer'); 2 | const path = require('path'); 3 | // File Upload Config 4 | const storage = multer.diskStorage({ 5 | destination: function (req, file, cb) { 6 | // cb(null, '') 7 | cb(null, path.join(__dirname, '../uploads/')); 8 | }, 9 | filename: function (req, file, cb) { 10 | // const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) 11 | const uniqueSuffix = Date.now(); 12 | cb(null, file.fieldname + '-' + uniqueSuffix); 13 | }, 14 | }); 15 | 16 | const middleware = function (req, res, next) { 17 | var upload = multer({ 18 | storage: storage, 19 | limits: { fileSize: 5 * 1024 * 1024 }, 20 | }).single('profileImage'); 21 | 22 | upload(req, res, function (err) { 23 | if (err instanceof multer.MulterError) { 24 | // if error happens because of file size 25 | if (err.code === 'LIMIT_FILE_SIZE') { 26 | // req.flash('error', 'Large file size'); 27 | return res.status(400).json({ message: 'File size is big' }); 28 | } 29 | 30 | // req.flash('error', 'Something went wrong try again'); 31 | return res.status(400).json({ 32 | message: 'Something went wrong in uploading images! Try again', 33 | }); 34 | } else if (err) { 35 | // req.flash('error', 'Something went wrong try again'); 36 | return res.status(400).json({ 37 | message: 'Something went wrong in uploading images! Try again', 38 | }); 39 | } 40 | next(); 41 | }); 42 | }; 43 | 44 | module.exports = middleware; 45 | -------------------------------------------------------------------------------- /controllers/bookmark_controllers.js: -------------------------------------------------------------------------------- 1 | const BookMark = require('../models/BookMark'); 2 | const Post = require('../models/post'); 3 | 4 | // Helper function to populate additional fields of 'post' 5 | async function populatePostFields(post) { 6 | if (!post) { 7 | return null; 8 | } 9 | 10 | const populatedPost = await Post.populate(post, [ 11 | { path: 'user' }, 12 | { path: 'comments.user' }, 13 | { path: 'likes' }, 14 | { path: 'tags' }, 15 | { path: 'sharedUser' }, 16 | ]); 17 | 18 | return populatedPost; 19 | } 20 | 21 | async function populateBookmarkedPosts(bookmarks) { 22 | const populatedBookmarks = []; 23 | 24 | for (const bookmark of bookmarks) { 25 | const populatedBookmark = await BookMark.populate(bookmark, { 26 | path: 'post', 27 | populate: { 28 | path: 'user comments.user likes tags sharedUser', 29 | } 30 | }); 31 | 32 | populatedBookmarks.push(populatedBookmark); 33 | } 34 | 35 | return populatedBookmarks; 36 | } 37 | 38 | const getAllBookMarks = async (id) => { 39 | const data = await BookMark.find({ user: id }).sort('-createdAt'); 40 | 41 | const bookmarkIds = data.map(item => item._id); 42 | const populatedBookmarks = await populateBookmarkedPosts(data); 43 | 44 | const posts = populatedBookmarks.map((bookmark, index) => { 45 | const post = bookmark.post; 46 | const populatedPost = { 47 | ...post.toObject(), // Convert to plain object 48 | bookMarkId: bookmarkIds[index] 49 | }; 50 | return populatedPost; 51 | }); 52 | return posts; 53 | }; 54 | 55 | module.exports.getBookMarks = async (req, res) => { 56 | try { 57 | const id = req.user.id; 58 | let bookmarks = await BookMark.find({ user: id }) 59 | .sort('-createdAt') 60 | .populate('post') 61 | .lean(); 62 | 63 | for (const bookmark of bookmarks) { 64 | const populatedPost = await populatePostFields(bookmark.post); 65 | bookmark.post = populatedPost; 66 | bookmark.post.bookMarkId = bookmark._id; 67 | } 68 | 69 | bookmarks = bookmarks.map((b) => ({...b, ...b.post})) 70 | 71 | res.status(200).json(bookmarks); 72 | } catch (error) { 73 | console.log(error); 74 | res.status(400).json({ message: 'Internal Server Error! Try again' }); 75 | } 76 | }; 77 | 78 | module.exports.addBookMarks = async (req, res) => { 79 | try { 80 | const id = req.user.id; 81 | const postId = req.params.postId; 82 | 83 | let bookMark = await BookMark.findOne({ post: postId, user: id }); 84 | 85 | if (bookMark) { 86 | return res.status(400).json({ message: 'Post already bookmarked' }); 87 | } 88 | 89 | const data = { 90 | post: req.params.postId, 91 | user: id, 92 | }; 93 | 94 | const saveBookMark = await BookMark.create(data); 95 | const allBookmarks = await getAllBookMarks(req.user.id); 96 | 97 | res.status(201).json({ 98 | message: 'Post Bookmarked successfully', 99 | bookmarks: allBookmarks, 100 | }); 101 | } catch (error) { 102 | return res 103 | .status(400) 104 | .json({ message: 'Internal Server Error! Try again ' }); 105 | } 106 | }; 107 | 108 | module.exports.deleteBookMark = async (req, res) => { 109 | try { 110 | const id = req.params.id; 111 | 112 | const bookMarkDeleteResult = await BookMark.deleteOne({ 113 | _id: id, 114 | }); 115 | 116 | 117 | const allBookmarks = await getAllBookMarks(req.user.id); 118 | 119 | // return; 120 | return res.status(200).json({ 121 | bookmarks: allBookmarks, 122 | message: 'bookmark removed succesfully', 123 | }); 124 | } catch (error) { 125 | console.log(error); 126 | return res.status(400).json({ 127 | message: 'Internal Server Error! Try again ', 128 | }); 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /controllers/comment_controllers.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const Post = require('../models/post'); 3 | const Comment = require('../models/comment'); 4 | const { getAllPosts } = require('./utils'); 5 | 6 | module.exports.createComment = async (req, res) => { 7 | try { 8 | if (req.body.description && req.body.description.length > 200) { 9 | return res 10 | .status(400) 11 | .json({ message: 'Comment should be smaller than 200 character' }); 12 | } 13 | 14 | if (!req.body.description || req.body.description.length == 0) { 15 | return res.status(400).json({ message: "Comment shouldn't be empty" }); 16 | } 17 | 18 | const post = await Post.findById(req.body.post_value); 19 | 20 | if (post) { 21 | const comment = await Comment.create( 22 | { 23 | description: req.body.description, 24 | post: req.body.post_value, 25 | user: req.user._id, 26 | } 27 | ); 28 | await post.comments.push(comment); 29 | await post.save(); 30 | const allPost = await getAllPosts(); 31 | 32 | return res.status(200).json(allPost); 33 | } else { 34 | return res 35 | .status(404) 36 | .json({ message: 'Looks like post does not exist!' }); 37 | } 38 | } catch (error) { 39 | console.log(error) 40 | return res 41 | .status(400) 42 | .json({ message: 'Something went wrong in adding comment!', error }); 43 | } 44 | }; 45 | 46 | module.exports.deleteComment = async (req, res) => { 47 | try { 48 | // IDOR 49 | // Deleting the Comment without checking who is the creator 50 | const comment = await Comment.findById(req.params.id); 51 | 52 | if (!comment) { 53 | return res.status(404).json({ message: 'Comment not found' }); 54 | } 55 | 56 | const post_id = comment.post; 57 | 58 | // Remove the comment using deleteOne or findByIdAndDelete 59 | await Comment.deleteOne({ _id: req.params.id }); 60 | 61 | await Post.findByIdAndUpdate( 62 | post_id, 63 | { $pull: { comments: req.params.id } } 64 | ); 65 | 66 | const allPost = await getAllPosts(); // Define the implementation of getAllPosts function 67 | return res.status(200).json(allPost); 68 | } catch (error) { 69 | console.log(error); 70 | return res.status(400).json({ message: 'Something went wrong' }); 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /controllers/home_controller.js: -------------------------------------------------------------------------------- 1 | const Post = require('../models/post'); 2 | const Comment = require('../models/comment'); 3 | module.exports.home = async (req, res) => { 4 | const posts = await Post.find({}) 5 | .sort('-createdAt') 6 | .populate('user') 7 | .populate({ 8 | path: 'comments', 9 | populate: { 10 | path: 'user', 11 | }, 12 | }) 13 | .populate({ 14 | path: 'likes', 15 | }) 16 | .populate({ 17 | path: 'tags', 18 | }) 19 | .populate({ 20 | path: 'sharedUser', 21 | }); 22 | 23 | return res.render('home', { 24 | layout: 'layout', 25 | name: 'shivam', 26 | posts: posts, 27 | }); 28 | }; 29 | module.exports; 30 | -------------------------------------------------------------------------------- /controllers/likes_controllers.js: -------------------------------------------------------------------------------- 1 | const Like = require('./../models/like'); 2 | const Post = require('./../models/post'); 3 | 4 | const { getAllPosts } = require('./utils'); 5 | 6 | module.exports = async (req, res) => { 7 | try { 8 | const userId = req.user._id; 9 | const postId = req.params.postId; 10 | 11 | const post = await Post.findById(postId); 12 | 13 | const existingLike = await Like.findOne({ 14 | postId: postId, 15 | user: userId, 16 | }); 17 | 18 | let deleted = false; 19 | 20 | if (existingLike) { 21 | post.likes.pull(existingLike._id); 22 | await post.save(); 23 | await existingLike.remove(); 24 | deleted = true; 25 | } else { 26 | const newLike = await Like.create({ 27 | user: userId, 28 | postId: postId, 29 | }); 30 | post.likes.push(newLike._id); 31 | await post.save(); 32 | } 33 | 34 | let allposts = await getAllPosts(); 35 | return res.status(200).json(allposts); 36 | } catch (error) { 37 | console.log(error); 38 | return res.status(400).json({ 39 | message: 'something went wrong in liking post! Try again later', 40 | }); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /controllers/post_controllers.js: -------------------------------------------------------------------------------- 1 | const Post = require("../models/post"); 2 | const { getAllPosts } = require("./utils"); 3 | const Comment = require("../models/comment"); 4 | const BookMark = require("../models/BookMark"); 5 | const getPageMetadata = require("../util/helper_functions"); 6 | 7 | module.exports.sharePost = async (req, res) => { 8 | try { 9 | let post = await Post.findById(req.params.postId); 10 | if (post.isShared) { 11 | return res.status(400).json({ message: "post already shared type" }); 12 | } 13 | if (req.user._id.equals(post.user)) { 14 | return res.status(400).json({ message: "You can't share your own post" }); 15 | } 16 | 17 | let newPost = { 18 | comments: [], 19 | likes: [], 20 | tags: post.tags, 21 | description: post.description, 22 | user: post.user, 23 | postImage: post.postImage, 24 | urlTitle: post.urlTitle, 25 | url: post.url, 26 | urlImage: post.urlImage, 27 | isShared: true, 28 | sharedUser: req.user._id, 29 | }; 30 | 31 | const postCreate = await Post.create(newPost); 32 | 33 | const allPost = await getAllPosts(); 34 | 35 | res.status(200).json(allPost); 36 | } catch (error) { 37 | return res.status(400).json({ 38 | message: "Internal Server Error! Try again ", 39 | }); 40 | } 41 | }; 42 | 43 | module.exports.createPost = async (req, res) => { 44 | req.body.tags = JSON.parse(req.body.tags); 45 | if (req.body.content.length <= 0) { 46 | // req.flash('error', 'You need to add text to create post'); 47 | return res 48 | .status(400) 49 | .json({ message: "You need to add text to create post" }); 50 | } 51 | 52 | // return; 53 | if (req.body.content && req.body.content.length > 200) { 54 | return res 55 | .status(400) 56 | .json({ message: "Post should be smaller than 200 character" }); 57 | } 58 | 59 | // return; 60 | //if url exist 61 | if (req.body.post_url) { 62 | const post_url = req.body.post_url; 63 | try { 64 | const data = await getPageMetadata(post_url); 65 | const { error, result } = data; 66 | 67 | if (error || !result) { 68 | return res.status(400).json({ message: "Invalid url" }); 69 | } else { 70 | let postData = { 71 | description: req.body.content, 72 | user: req.user._id, 73 | urlTitle: result.og.title || result.meta.title, 74 | urlImage: result.og.image 75 | ? result.og.image 76 | : result.images[0] 77 | ? result.images[0]["src"] 78 | : post_url, 79 | url: result.og.url ? result.og.url : post_url, 80 | tags: req.body.tags ? req.body.tags : [], 81 | isShared: false, 82 | }; 83 | 84 | if (req.file) { 85 | postData.postImage = req.file.location; 86 | } 87 | 88 | await Post.create(postData); 89 | 90 | let posts = await getAllPosts(); 91 | 92 | return res.status(201).json(posts); 93 | } 94 | } catch (err) { 95 | console.error("Caught error:", err); 96 | return res 97 | .status(400) 98 | .json({ message: "Error in creating post! Try again" }); 99 | } 100 | } else { 101 | if (req.file) { 102 | try { 103 | await Post.create({ 104 | description: req.body.content, 105 | user: req.user._id, 106 | postImage: req.file.location, 107 | tags: req.body.tags ? req.body.tags : [], 108 | isShared: false, 109 | }); 110 | 111 | let result = await getAllPosts(); 112 | return res.status(201).json(result); 113 | } catch { 114 | return res 115 | .status(400) 116 | .json({ message: "Error in creating post ! Try again" }); 117 | } 118 | } else { 119 | try { 120 | await Post.create({ 121 | description: req.body.content, 122 | user: req.user._id, 123 | tags: req.body.tags ? req.body.tags : [], 124 | isShared: false, 125 | }); 126 | 127 | let result = await getAllPosts(); 128 | return res.status(201).json(result); 129 | } catch { 130 | return res 131 | .status(400) 132 | .json({ message: "Error in creating post ! Try again" }); 133 | } 134 | } 135 | } 136 | }; 137 | 138 | module.exports.deletePost = async (req, res) => { 139 | try { 140 | const deleteBookMark = await BookMark.deleteMany({ post: req.params.id }); 141 | 142 | const post = Post.findById(req.params.id); 143 | // IDOR 144 | // Deleting the post without checking who is the creator 145 | if (!post) { 146 | return res 147 | .status(400) 148 | .json({ message: "Error in creating post ! Try again" }); 149 | } else { 150 | await post.deleteOne(); 151 | await Comment.deleteMany({ post: req.params.id }); 152 | 153 | const posts = await getAllPosts(); 154 | return res.status(200).json(posts); 155 | } 156 | } catch (error) { 157 | console.log(error); 158 | return res 159 | .status(400) 160 | .json({ message: "Error in creating post ! Try again" }); 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /controllers/search_controllers.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const db = require('../config/mongoose'); 3 | const router = express.Router(); 4 | const User = require('../models/user'); 5 | 6 | module.exports.search = async (req, res) => { 7 | try { 8 | let searchText = req.body.search_text; 9 | 10 | let finalResult = []; 11 | 12 | let temp; 13 | 14 | try { 15 | temp = JSON.parse(searchText); 16 | } catch (err) { 17 | temp = searchText; 18 | } 19 | let result = await User.collection.find({ name: temp }).forEach((data) => { 20 | finalResult.push(data); 21 | }); 22 | res.status(200).json(finalResult); 23 | } catch (error) { 24 | res.status(400).json({ message: 'Something went worong! Try again later' }); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /controllers/utils.js: -------------------------------------------------------------------------------- 1 | const Post = require('../models/post'); 2 | const User = require('../models/user'); 3 | 4 | module.exports.getAllPosts = async () => { 5 | const posts = await Post.find({}) 6 | .sort('-createdAt') 7 | .populate('user') 8 | .populate({ 9 | path: 'comments', 10 | populate: { 11 | path: 'user', 12 | }, 13 | }) 14 | .populate({ 15 | path: 'likes', 16 | }) 17 | .populate({ 18 | path: 'tags', 19 | }) 20 | .populate({ 21 | path: 'sharedUser', 22 | }); 23 | return posts; 24 | }; 25 | 26 | module.exports.getSingleUser = async (id) => { 27 | const user = await User.findOne({ _id: id }); 28 | 29 | return user; 30 | }; 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mongodb: 4 | image: mongo:6.0 5 | volumes: 6 | - mongodb_data:/data/db 7 | networks: 8 | - app-network 9 | 10 | threadsapp: 11 | build: . 12 | image: threadsapp:latest 13 | ports: 14 | - "3000:3000" 15 | - "4000:4000" # Backend port 16 | environment: 17 | - MONGODB_URI=mongodb://mongodb:27017/vulnerable_dev_database 18 | - RUNNING_IN_DOCKER=true 19 | depends_on: 20 | - mongodb 21 | networks: 22 | - app-network 23 | command: sh -c "npm run add_users && npm start" 24 | 25 | volumes: 26 | mongodb_data: 27 | 28 | networks: 29 | app-network: 30 | driver: bridge 31 | 32 | -------------------------------------------------------------------------------- /dummy_data/create_user.js: -------------------------------------------------------------------------------- 1 | const userData = require('./user_data'); 2 | const dotenv = require('dotenv'); 3 | dotenv.config({ path: '../config.env' }); 4 | require('../config/mongoose'); 5 | const User = require('../models/user'); 6 | const Post = require('../models/post'); 7 | 8 | const mongoose = require('mongoose'); 9 | const postData = require('./post_data'); 10 | const deleteUser = require('../util/delete_user'); 11 | const getPageMetadata = require('../util/helper_functions'); 12 | 13 | const createFollowers = (interval) => { 14 | console.log('--------completed adding user and posts---------------'); 15 | clearInterval(interval); 16 | 17 | let i = 0; 18 | 19 | const newInterval = setInterval(async () => { 20 | if (i == userData.length) { 21 | console.log('***********Follower adding done *************'); 22 | console.log('-----------closing database connection ------------'); 23 | mongoose.connection.close(); 24 | 25 | clearInterval(newInterval); 26 | return; 27 | } 28 | 29 | let currentUser = userData[i]; 30 | let followingId = await User.findOne({ email: currentUser.email }); 31 | followingId = followingId._id; 32 | 33 | let start = Math.floor(Math.random() * 24) + 1; 34 | let end = Math.floor(Math.random() * 24) + 1; 35 | 36 | if (start > end) { 37 | let temp = start; 38 | start = end; 39 | end = temp; 40 | } 41 | 42 | for (let i = start; i <= end; i++) { 43 | let followerId = await User.findOne({ email: userData[i].email }); 44 | followerId = followerId._id; 45 | 46 | if (followingId.equals(followerId)) { 47 | continue; 48 | } 49 | 50 | await User.updateOne( 51 | { _id: followerId }, 52 | { $push: { following: followingId } } 53 | ); 54 | 55 | await User.updateOne( 56 | { _id: followingId }, 57 | { $push: { follower: followerId } } 58 | ); 59 | } 60 | i++; 61 | }, 500); 62 | }; 63 | 64 | const createPost = async (data) => { 65 | if (data.post_url) { 66 | const post_url = data.post_url; 67 | const metaData = await getPageMetadata(post_url); 68 | const { error, result } = metaData; 69 | 70 | if (error || !result) { 71 | return; 72 | } else { 73 | let postData = { 74 | description: data.description, 75 | user: data._id, 76 | postImage: data.imageUrl, 77 | urlTitle: result.og.title || result.meta.title, 78 | urlImage: result.og.image 79 | ? result.og.image 80 | : result.images[0] 81 | ? result.images[0]["src"] 82 | : post_url, 83 | url: result.og.url ? result.og.url : post_url, 84 | tags: data.tags ? data.tags : [], 85 | isShared: false, 86 | }; 87 | 88 | await Post.create(postData); 89 | } 90 | } else { 91 | const post = await Post.create({ 92 | description: data.description, 93 | user: data._id, 94 | postImage: data.imageUrl, 95 | tags: data.tags ? data.tags : [], 96 | isShared: false, 97 | }); 98 | console.log(post); 99 | } 100 | }; 101 | 102 | async function createUser2(usersToAdd) { 103 | try { 104 | let i = 0; 105 | const interval = setInterval(async () => { 106 | if (i == usersToAdd.length) { 107 | createFollowers(interval); 108 | return; 109 | } 110 | 111 | let data = usersToAdd[i]; 112 | 113 | let findUser = await User.findOne({ email: data.email }); 114 | 115 | if (findUser) { 116 | await deleteUser(findUser._id); 117 | } 118 | 119 | let user = await User.create(data); 120 | 121 | if (postData[user.email]) { 122 | const post = { 123 | description: postData[user.email].description, 124 | post_url: postData[user.email].url, 125 | imageUrl: postData[user.email].postImage, 126 | _id: user._id, 127 | email: user.email, 128 | }; 129 | createPost(post); 130 | } 131 | i++; 132 | }, 500); 133 | } catch (error) { 134 | console.log(error); 135 | } 136 | } 137 | 138 | // Modify to receive user data directly from parent process 139 | if (process.send) { 140 | process.on('message', (usersToAdd) => { 141 | createUser2(usersToAdd); 142 | }); 143 | } else { 144 | createUser2(userData); // Fallback if not run as a child process 145 | } 146 | 147 | -------------------------------------------------------------------------------- /dummy_data/post_data.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | 'jyearsley0@reference.com': { 3 | description: 4 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh', 5 | postImage: '/uploads/img1.jpeg', 6 | url: 'https://www.facebook.com/', 7 | }, 8 | 'talgie1@msu.edu': { 9 | description: 10 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 11 | postImage: '/uploads/img2.jpeg', 12 | }, 13 | 'bszimon2@cdc.gov': { 14 | description: 15 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 16 | }, 17 | 'oonion3@si.edu': { 18 | description: 19 | 'himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante', 20 | postImage: '/uploads/img3.jpeg', 21 | url: 'https://www.youtube.com/', 22 | }, 23 | 'hfurphy4@amazonaws.com': { 24 | description: 25 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 26 | }, 27 | 'pboliver5@alexa.com': { 28 | description: 29 | 'Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum ', 30 | postImage: '/uploads/img4.jpeg', 31 | }, 32 | 'megarr6@smh.com.au': { 33 | description: 34 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 35 | }, 36 | 'cgregore7@angelfire.com': { 37 | description: 38 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 39 | }, 40 | 'bblaes8@alexa.com': { 41 | description: 42 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh', 43 | postImage: '/uploads/img5.jpeg', 44 | url: 'https://www.amazon.in/', 45 | }, 46 | 'sbricket9@cyberchimps.com': { 47 | description: 48 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 49 | }, 50 | 'ahinckesa@tmall.com': { 51 | description: 52 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 53 | }, 54 | 'cmccroryb@bbc.co.uk': { 55 | description: 56 | 'is ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class', 57 | postImage: '/uploads/img6.jpeg', 58 | url: 'https://www.facebook.com/', 59 | }, 60 | 'rspawellc@diigo.com': { 61 | description: 62 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 63 | postImage: null, 64 | url: null, 65 | }, 66 | 'dbinningd@elegantthemes.com': { 67 | description: 68 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 69 | postImage: null, 70 | url: null, 71 | }, 72 | 'aterbruge@techcrunch.com': { 73 | description: 74 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 75 | postImage: null, 76 | url: null, 77 | }, 78 | 'cparfettf@wiley.com': { 79 | description: 80 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh', 81 | postImage: '/uploads/img7.jpeg', 82 | url: null, 83 | }, 84 | 'fmckellerg@networksolutions.com': { 85 | description: 86 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 87 | postImage: null, 88 | url: null, 89 | }, 90 | 'tberrygunh@ox.ac.uk': { 91 | description: 92 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 93 | postImage: null, 94 | url: null, 95 | }, 96 | 'mbenedictoi@lulu.com': { 97 | description: 98 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 99 | postImage: null, 100 | url: null, 101 | }, 102 | 'scrimj@ed.gov': { 103 | description: 104 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh', 105 | postImage: '/uploads/img8.jpeg', 106 | url: 'https://www.youtube.com/', 107 | }, 108 | 'rmountaink@china.com.cn': { 109 | description: 110 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 111 | postImage: null, 112 | url: null, 113 | }, 114 | 'sculverhousel@icq.com': { 115 | description: 116 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh', 117 | postImage: '/uploads/img9.jpeg', 118 | url: 'https://www.facebook.com/', 119 | }, 120 | 'kmcgloughlinm@wikia.com': { 121 | description: 122 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 123 | postImage: null, 124 | url: null, 125 | }, 126 | 'mdeanen@icio.us': { 127 | description: 128 | 'is ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class', 129 | postImage: '/uploads/img10.jpeg', 130 | url: null, 131 | }, 132 | 'middinso@csmonitor.com': { 133 | description: 134 | 'egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. M', 135 | postImage: null, 136 | url: null, 137 | }, 138 | }; 139 | 140 | 141 | module.exports = data; 142 | -------------------------------------------------------------------------------- /dummy_data/user_data.js: -------------------------------------------------------------------------------- 1 | const data = [ 2 | { 3 | email: "benjamin.brian@mysecurecorp.com", 4 | password: "xUxZu4LaV", 5 | name: "Benjamin.brian", 6 | bio: "Enthusiastic developer with a passion for coding and innovation.", 7 | webLink: "https://mysecurecorp.com/benjamin.brian" 8 | }, 9 | { 10 | email: "kenneth.jack@mysecurecorp.com", 11 | password: "&syyLq6wBt", 12 | name: "Kenneth.jack", 13 | bio: "Experienced engineer always looking for new challenges.", 14 | webLink: "https://mysecurecorp.com/kenneth.jack" 15 | }, 16 | { 17 | email: "larry.justin@mysecurecorp.com", 18 | password: "3nALMRSJ=b", 19 | name: "Larry.justin", 20 | bio: "Tech enthusiast who loves to learn and explore new technologies.", 21 | webLink: "https://mysecurecorp.com/larry.justin" 22 | }, 23 | { 24 | email: "michael.joseph@mysecurecorp.com", 25 | password: "?EtZdX9tRQ", 26 | name: "Michael.joseph", 27 | bio: "Dedicated professional with a keen eye for detail.", 28 | webLink: "https://mysecurecorp.com/michael.joseph" 29 | }, 30 | { 31 | email: "jack.joshua@mysecurecorp.com", 32 | password: "UrW&bA5jMp", 33 | name: "Jack.joshua", 34 | bio: "Creative problem-solver with a background in software development.", 35 | webLink: "https://mysecurecorp.com/jack.joshua" 36 | }, 37 | { 38 | email: "scott.brandon@mysecurecorp.com", 39 | password: "+jnSvEY2tz", 40 | name: "Scott.brandon", 41 | bio: "Committed team player who thrives in collaborative environments.", 42 | webLink: "https://mysecurecorp.com/scott.brandon" 43 | }, 44 | { 45 | email: "ryan.dennis@mysecurecorp.com", 46 | password: "ss_4jTtDGB", 47 | name: "Ryan.dennis", 48 | bio: "Innovative thinker with a strong technical background.", 49 | webLink: "https://mysecurecorp.com/ryan.dennis" 50 | }, 51 | { 52 | email: "justin.gregory@mysecurecorp.com", 53 | password: "kFDh3d@meF", 54 | name: "Justin.gregory", 55 | bio: "Results-driven individual with a focus on quality and efficiency.", 56 | webLink: "https://mysecurecorp.com/justin.gregory" 57 | }, 58 | { 59 | email: "alexander.joshua@mysecurecorp.com", 60 | password: "HpRB@8yHuy", 61 | name: "Alexander.joshua", 62 | bio: "Motivated self-starter who excels in dynamic settings.", 63 | webLink: "https://mysecurecorp.com/alexander.joshua" 64 | }, 65 | { 66 | email: "jack.joseph@mysecurecorp.com", 67 | password: "vDa4x/Lqyr", 68 | name: "Jack.joseph", 69 | bio: "Versatile programmer with a love for continuous learning.", 70 | webLink: "https://mysecurecorp.com/jack.joseph" 71 | }, 72 | { 73 | email: 'jyearsley0@reference.com', 74 | password: 'R9B0cCRz90lo', 75 | name: 'Jojo', 76 | bio: 77 | 'Cras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', 78 | webLink: 'https://www.facebook.com/', 79 | }, 80 | { 81 | email: 'talgie1@msu.edu', 82 | password: 'BhqHAu', 83 | name: 'Town', 84 | bio: 85 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh.', 86 | webLink: 'https://www.youtube.com/', 87 | }, 88 | { 89 | email: 'bszimon2@cdc.gov', 90 | password: 'igIbk2AHaX', 91 | name: 'Bobby', 92 | bio: 93 | 'Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy. Integer non velit.', 94 | webLink: 'https://www.youtube.com/', 95 | }, 96 | { 97 | email: 'oonion3@si.edu', 98 | password: 'ySWNyLFluk', 99 | name: 'Ortensia', 100 | bio: 101 | 'Cras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', 102 | webLink: 'https://www.quora.com/', 103 | }, 104 | { 105 | email: 'hfurphy4@amazonaws.com', 106 | password: 'EPD3wXkHQoi', 107 | name: 'Hanson', 108 | bio: 'Phasellus in felis. Donec semper sapien a libero. Nam dui.', 109 | webLink: 'https://www.cricbuzz.com/', 110 | }, 111 | { 112 | email: 'pboliver5@alexa.com', 113 | password: 'b1aUMHqBC', 114 | name: 'Piper', 115 | bio: 116 | 'Curabitur at ipsum ac tellus semper interdum. Mauris ullamcorper purus sit amet nulla. Quisque arcu libero, rutrum ac, lobortis vel, dapibus at, diam.', 117 | webLink: 'https://www.facebook.com/', 118 | }, 119 | { 120 | email: 'megarr6@smh.com.au', 121 | password: '2OI6ru5FybdZ', 122 | name: 'Monique', 123 | bio: 124 | 'Proin leo odio, porttitor id, consequat in, consequat ut, nulla. Sed accumsan felis. Ut at dolor quis odio consequat varius.', 125 | webLink: 'https://www.quora.com/', 126 | }, 127 | { 128 | email: 'cgregore7@angelfire.com', 129 | password: 'sYY3j5gfqY', 130 | name: 'Con', 131 | bio: 132 | 'Integer tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat.', 133 | webLink: 'https://www.youtube.com/', 134 | }, 135 | { 136 | email: 'bblaes8@alexa.com', 137 | password: 'ldklqe', 138 | name: 'Barnaby', 139 | bio: 140 | 'Nullam sit amet turpis elementum ligula vehicula consequat. Morbi a ipsum. Integer a nibh.', 141 | webLink: 'https://www.cricbuzz.com/', 142 | }, 143 | { 144 | email: 'sbricket9@cyberchimps.com', 145 | password: 'Aav1yy0qt', 146 | name: 'Sigfried', 147 | bio: 148 | 'In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.', 149 | webLink: 'https://www.facebook.com/', 150 | }, 151 | { 152 | email: 'ahinckesa@tmall.com', 153 | password: 'xgAjfe6wifA', 154 | name: 'Ailsun', 155 | bio: 156 | 'Proin interdum mauris non ligula pellentesque ultrices. Phasellus id sapien in sapien iaculis congue. Vivamus metus arcu, adipiscing molestie, hendrerit at, vulputate vitae, nisl.', 157 | webLink: 'https://www.youtube.com/', 158 | }, 159 | { 160 | email: 'cmccroryb@bbc.co.uk', 161 | password: 'tsD3fPMqa', 162 | name: 'Cleavland', 163 | bio: 164 | 'Aenean fermentum. Donec ut mauris eget massa tempor convallis. Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.', 165 | webLink: 'https://www.quora.com/', 166 | }, 167 | { 168 | email: 'rspawellc@diigo.com', 169 | password: 'ZFmmeBn23aos', 170 | name: 'Rutger', 171 | bio: 172 | 'Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.', 173 | webLink: 'https://www.youtube.com/', 174 | }, 175 | { 176 | email: 'dbinningd@elegantthemes.com', 177 | password: '8sayDAcZul', 178 | name: 'Daphna', 179 | bio: 180 | 'Maecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.', 181 | webLink: 'https://www.facebook.com/', 182 | }, 183 | { 184 | email: 'aterbruge@techcrunch.com', 185 | password: 'Qu3ZSoPbYvUM', 186 | name: 'Any', 187 | bio: 188 | 'Curabitur in libero ut massa volutpat convallis. Morbi odio odio, elementum eu, interdum eu, tincidunt in, leo. Maecenas pulvinar lobortis est.', 189 | webLink: 'https://www.cricbuzz.com/', 190 | }, 191 | { 192 | email: 'cparfettf@wiley.com', 193 | password: '5uKMTJ', 194 | name: 'Carroll', 195 | bio: 196 | 'Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec pharetra, magna vestibulum aliquet ultrices, erat tortor sollicitudin mi, sit amet lobortis sapien sapien non mi. Integer ac neque.', 197 | webLink: 'https://www.youtube.com/', 198 | }, 199 | { 200 | email: 'fmckellerg@networksolutions.com', 201 | password: 'MCQkaP', 202 | name: 'Frieda', 203 | bio: 204 | 'Morbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.', 205 | webLink: 'https://www.quora.com/', 206 | }, 207 | { 208 | email: 'tberrygunh@ox.ac.uk', 209 | password: 'TJVmlNa3iv', 210 | name: 'Timoteo', 211 | bio: 212 | 'Phasellus sit amet erat. Nulla tempus. Vivamus in felis eu sapien cursus vestibulum.', 213 | webLink: 'https://www.youtube.com/', 214 | }, 215 | { 216 | email: 'mbenedictoi@lulu.com', 217 | password: 'u6Mjr6OaD', 218 | name: 'Mitzi', 219 | bio: 220 | 'Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.', 221 | webLink: 'https://www.quora.com/', 222 | }, 223 | { 224 | email: 'scrimj@ed.gov', 225 | password: 'sIqCiFSpVW', 226 | name: 'Sherri', 227 | bio: 228 | 'In sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus.', 229 | webLink: 'https://www.quora.com/', 230 | }, 231 | { 232 | email: 'rmountaink@china.com.cn', 233 | password: 'MIprF0sSINSJ', 234 | name: 'Raul', 235 | bio: 236 | 'Sed sagittis. Nam congue, risus semper porta volutpat, quam pede lobortis ligula, sit amet eleifend pede libero quis orci. Nullam molestie nibh in lectus.', 237 | webLink: 'https://www.youtube.com/', 238 | }, 239 | { 240 | email: 'sculverhousel@icq.com', 241 | password: 'DpwCiheFMMHb', 242 | name: 'Sarge', 243 | bio: 244 | 'Maecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat.', 245 | webLink: 'https://www.youtube.com/', 246 | }, 247 | { 248 | email: 'kmcgloughlinm@wikia.com', 249 | password: 'jR6FBs84', 250 | name: 'Kaitlyn', 251 | bio: 'Integer ac leo. Pellentesque ultrices mattis odio. Donec vitae nisi.', 252 | webLink: 'https://www.facebook.com/', 253 | }, 254 | { 255 | email: 'mdeanen@icio.us', 256 | password: 'A0R1BR6d', 257 | name: 'Meggy', 258 | bio: 259 | 'Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.', 260 | webLink: 'https://www.cricbuzz.com/', 261 | }, 262 | { 263 | email: 'middinso@csmonitor.com', 264 | password: '2l7alH', 265 | name: 'Marilee', 266 | bio: 267 | 'Nulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.', 268 | webLink: 'https://www.cricbuzz.com/', 269 | }, 270 | ]; 271 | 272 | module.exports = data; 273 | -------------------------------------------------------------------------------- /models/BookMark.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | var deepPopulate = require('mongoose-deep-populate')(mongoose); 3 | 4 | const bookMarkSchema = new mongoose.Schema({ 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: 'User', 8 | }, 9 | post: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'Post', 12 | }, 13 | }); 14 | 15 | bookMarkSchema.plugin(deepPopulate); 16 | module.exports = mongoose.model('BookMark', bookMarkSchema); 17 | -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const commentSchema = new mongoose.Schema({ 4 | description: { 5 | type: String, 6 | required: true, 7 | }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | ref: 'User', 11 | }, 12 | post: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: 'Post', 15 | }, 16 | }); 17 | module.exports = mongoose.model('Comment', commentSchema); 18 | -------------------------------------------------------------------------------- /models/like.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const likeSchema = new mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.ObjectId, 7 | }, 8 | postId: { 9 | type: mongoose.Schema.ObjectId, 10 | required: true, 11 | ref: 'Post', 12 | }, 13 | }, 14 | { timestamps: true } 15 | ); 16 | 17 | const Like = mongoose.model('Like', likeSchema); 18 | 19 | module.exports = Like; 20 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const postSchema = new mongoose.Schema( 4 | { 5 | description: { 6 | type: String, 7 | required: true, 8 | }, 9 | user: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: 'User', 12 | }, 13 | postImage: { 14 | type: String, 15 | }, 16 | comments: [ 17 | { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'Comment', 20 | }, 21 | ], 22 | likes: [ 23 | { 24 | type: mongoose.Schema.Types.ObjectId, 25 | ref: 'Like', 26 | }, 27 | ], 28 | tags: [ 29 | { 30 | type: mongoose.Schema.Types.ObjectId, 31 | ref: 'User', 32 | }, 33 | ], 34 | urlTitle: { 35 | type: String, 36 | }, 37 | urlImage: { 38 | type: String, 39 | }, 40 | url: { 41 | type: String, 42 | }, 43 | isShared: { 44 | type: Boolean, 45 | }, 46 | sharedUser: { 47 | type: mongoose.Schema.Types.ObjectId, 48 | ref: 'User', 49 | }, 50 | }, 51 | { timestamps: true } 52 | ); 53 | module.exports = mongoose.model('Post', postSchema); 54 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | email: { 6 | type: String, 7 | required: true, 8 | unique: true, 9 | }, 10 | password: { 11 | type: String, 12 | required: true, 13 | }, 14 | name: { 15 | type: String, 16 | required: true, 17 | }, 18 | webLink: { 19 | type: String, 20 | }, 21 | profileImage: { 22 | type: String, 23 | default: '/uploads/avatar.png', 24 | }, 25 | bio: { 26 | type: String, 27 | }, 28 | follower: [ 29 | { 30 | type: mongoose.Schema.Types.ObjectId, 31 | ref: 'User', 32 | }, 33 | ], 34 | following: [ 35 | { 36 | type: mongoose.Schema.Types.ObjectId, 37 | ref: 'User', 38 | }, 39 | ], 40 | }, 41 | { timestamps: true } 42 | ); 43 | module.exports = mongoose.model('User', userSchema); 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vulnerable_app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "add_users": "node add_user.js", 9 | "client": "cd client && npm start", 10 | "client_install": "cd client && npm install", 11 | "start": "concurrently -n 'server,client' -c 'red,blue' \"nodemon server.js\" \"npm run client\"", 12 | "install:all_deps": "npm install && npm run client_install" 13 | }, 14 | "author": "enciphers", 15 | "license": "ISC", 16 | "dependencies": { 17 | "aws-sdk": "^2.781.0", 18 | "axios": "^0.21.1", 19 | "concurrently": "^5.3.0", 20 | "connect-flash": "^0.1.1", 21 | "connect-mongo": "^5.0.0", 22 | "cors": "^2.8.5", 23 | "dotenv": "^8.2.0", 24 | "ejs": "^3.1.5", 25 | "express": "^4.17.1", 26 | "express-ejs-layouts": "^2.5.0", 27 | "express-session": "^1.17.1", 28 | "express-validator": "^6.6.1", 29 | "html-metadata-parser": "^2.0.4", 30 | "jsonwebtoken": "^8.5.1", 31 | "mongoose": "^7.4.2", 32 | "mongoose-deep-populate": "^3.2.0", 33 | "multer": "^1.4.2", 34 | "multer-s3": "^2.9.0", 35 | "npm": "^6.14.11", 36 | "passport": "^0.4.1", 37 | "passport-jwt": "^4.0.0", 38 | "passport-local": "^1.0.0", 39 | "request": "^2.88.2", 40 | "socket.io": "^3.0.1", 41 | "uniqid": "^5.3.0" 42 | }, 43 | "devDependencies": { 44 | "nodemon": "^2.0.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /routes/bookmark.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const router = express.Router(); 4 | const userControllers = require('../controllers/user_controller'); 5 | 6 | const bookmarkControllers = require('../controllers/bookmark_controllers'); 7 | 8 | router.get( 9 | '/add/:postId', 10 | userControllers.isAuth, 11 | bookmarkControllers.addBookMarks 12 | ); 13 | router.get( 14 | '/delete/:id', 15 | userControllers.isAuth, 16 | bookmarkControllers.deleteBookMark 17 | ); 18 | router.get('/', userControllers.isAuth, bookmarkControllers.getBookMarks); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /routes/comments.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const Post = require('../models/post'); 4 | const Comment = require('../models/comment'); 5 | const commentControllers = require('../controllers/comment_controllers'); 6 | const userController = require('../controllers/user_controller'); 7 | 8 | router.post('/create', userController.isAuth, commentControllers.createComment); 9 | router.get('/delete/:id', commentControllers.deleteComment); 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const homeController = require('../controllers/home_controller'); 5 | // Show public feed 6 | router.get('/home', homeController.home); 7 | router.get('/management', (req, res) => { 8 | res.render('managementLogin', { layout: 'management' }); 9 | }); 10 | router.get('/managementUser', (req, res) => { 11 | res.render('managementUser', { layout: 'management' }); 12 | }); 13 | router.use('/users', require('./users')); 14 | router.use('/posts', require('./posts')); 15 | router.use('/comment', require('./comments')); 16 | router.use('/search', require('./search')); 17 | router.use('/like', require('./likes')); 18 | router.use('/bookmark', require('./bookmark')); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /routes/likes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const Like = require('./../models/like'); 3 | const Post = require('./../models/post'); 4 | const router = express.Router(); 5 | 6 | const likesControllers = require('../controllers/likes_controllers'); 7 | const usersController = require('../controllers/user_controller'); 8 | 9 | router.get('/toggle/:postId', usersController.isAuth, likesControllers); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /routes/posts.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const uploadS3 = require('../config/multer_s3'); 5 | 6 | const postControllers = require('../controllers/post_controllers'); 7 | const userControllers = require('../controllers/user_controller'); 8 | 9 | router.get('/share/:postId', userControllers.isAuth, postControllers.sharePost); 10 | router.post( 11 | '/create', 12 | userControllers.isAuth, 13 | uploadS3, 14 | postControllers.createPost 15 | ); 16 | router.get('/delete/:id', postControllers.deletePost); 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /routes/search.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const searchControllers = require('../controllers/search_controllers'); 5 | 6 | router.post('/', searchControllers.search); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const usersController = require('../controllers/user_controller'); 4 | const upload = require('../config/muter_upload'); 5 | const user = require('../models/user'); 6 | 7 | router.get('/profile/:id', usersController.profile); 8 | 9 | router.post( 10 | '/update', 11 | // usersController.isAuth, 12 | upload, 13 | usersController.update 14 | ); 15 | 16 | // router.get('/sign-up', usersController.signUp); 17 | router.post('/login', usersController.signIn); 18 | router.post('/register', usersController.create); 19 | // router.post( 20 | // '/create-session', 21 | // passport.authenticate('local', { failureRedirect: '/users/login' }), 22 | // usersController.createSession 23 | // ); 24 | // router.get('/sign-out', usersController.destroySession); 25 | router.get('/get-users', usersController.isAuth, usersController.getUsers); 26 | router.get('/delete', usersController.isAuth, usersController.deleteAccount); 27 | router.get('/', usersController.getUsers2); 28 | 29 | router.get('/follow/:id', usersController.isAuth, usersController.followUser); 30 | router.get( 31 | '/unfollow/:id', 32 | usersController.isAuth, 33 | usersController.unFollowUser 34 | ); 35 | 36 | router.get('/followers/:userId', usersController.getFollowers); 37 | router.get('/following/:userId', usersController.getFollowing); 38 | 39 | router.get('/single', usersController.isAuth, usersController.getSingleUser); 40 | 41 | router.get('/get-posts', usersController.isAuth, usersController.getAllPosts); 42 | 43 | router.post( 44 | '/management/delete-user', 45 | usersController.isAuth, 46 | usersController.deleteUserManagement 47 | ); 48 | 49 | module.exports = router; 50 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const uniqueId = require('uniqid'); 4 | 5 | // var cors = require('cors'); 6 | // app.use(cors()); 7 | 8 | // app.options('*', cors()); 9 | 10 | const dotenv = require('dotenv'); 11 | dotenv.config({ path: './config.env' }); 12 | const mongoose = require('mongoose'); 13 | const socketio = require('socket.io'); 14 | const User = require('./models/user'); 15 | 16 | // app.options('*', cors()); 17 | 18 | // app.use(express.urlencoded()); 19 | 20 | process.env.ACCESSKEYID = 21 | process.env.ACCESSKEYID.length == 0 ? undefined : process.env.ACCESSKEYID; 22 | process.env.SECRETACCESSKEY = 23 | process.env.SECRETACCESSKEY.length == 0 24 | ? undefined 25 | : process.env.SECRETACCESSKEY; 26 | process.env.REGION = 27 | process.env.REGION.length == 0 ? undefined : process.env.REGION; 28 | process.env.BUCKET_NAME = 29 | process.env.BUCKET_NAME.length == 0 ? undefined : process.env.BUCKET_NAME; 30 | 31 | app.use(express.json()); 32 | app.use(express.urlencoded({ extended: true })); 33 | 34 | app.use((req, res, next) => { 35 | res.setHeader('Access-Control-Allow-Origin', '*'); 36 | res.setHeader( 37 | 'Access-Control-Allow-Methods', 38 | 'GET, POST, PUT, PATCH, DELETE' 39 | ); 40 | res.setHeader('Access-Control-Allow-Credentials', 'true'); 41 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); 42 | next(); 43 | }); 44 | 45 | require('./config/mongoose'); 46 | const db = mongoose.connection; 47 | db.on('error', (err) => { 48 | console.log(`Databsase Error ${err}`); 49 | }); 50 | db.once('open', () => { 51 | console.log('Connected to Moongose'); 52 | }); 53 | 54 | // Developement mode 55 | const port = process.env.PORT || 3000; 56 | app.use(express.static('./assets')); 57 | app.use('/uploads', express.static(__dirname + '/uploads')); 58 | 59 | // app.use( 60 | // session({ 61 | // name: 'ck', 62 | // secret: 'thr3@ds@000', 63 | // saveUninitialized: false, 64 | // resave: false, 65 | // cookie: { 66 | // maxAge: 1000 * 60 * 100, 67 | // }, 68 | // store: new MongoStore({ 69 | // mongooseConnection: db, 70 | // autoRemove: 'disabled', 71 | // }), 72 | // }) 73 | // ); 74 | 75 | // app.use(passport.initialize()); 76 | // app.use(passport.session()); 77 | // app.use(passport.setAuthentication); 78 | // app.use(flash()); 79 | // app.use(flashSetting.setFashMessage); 80 | // app.set('layout extractStyles', true); 81 | 82 | // app.use(expressLayouts); 83 | app.use('/', require('./routes/index')); 84 | 85 | const server = app.listen(port, (err) => { 86 | if (err) { 87 | `Something wrong with running the server --> ${err}`; 88 | return; 89 | } 90 | console.log(`connected to server on port:${port}`); 91 | }); 92 | 93 | const io = require('socket.io')(server, { 94 | cors: { 95 | origin: '*', 96 | }, 97 | }); 98 | io.on('connection', (socket) => { 99 | console.log('client connected'); 100 | socket.on('send_message', async (data) => { 101 | console.log('sending message to all'); 102 | console.log(data); 103 | const user = await User.findOne({ email: data.email }); 104 | data = { 105 | ...data, 106 | id: user._id, 107 | uniqueId: uniqueId(), 108 | }; 109 | console.log(data); 110 | io.emit('send_message_to_all', data); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /uploads/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/avatar.png -------------------------------------------------------------------------------- /uploads/img1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img1.jpeg -------------------------------------------------------------------------------- /uploads/img10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img10.jpeg -------------------------------------------------------------------------------- /uploads/img2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img2.jpeg -------------------------------------------------------------------------------- /uploads/img3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img3.jpeg -------------------------------------------------------------------------------- /uploads/img4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img4.jpeg -------------------------------------------------------------------------------- /uploads/img5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img5.jpeg -------------------------------------------------------------------------------- /uploads/img6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img6.jpeg -------------------------------------------------------------------------------- /uploads/img7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img7.jpeg -------------------------------------------------------------------------------- /uploads/img8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img8.jpeg -------------------------------------------------------------------------------- /uploads/img9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/img9.jpeg -------------------------------------------------------------------------------- /uploads/screely-1614355791180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/screely-1614355791180.png -------------------------------------------------------------------------------- /uploads/screely-1614355834783.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/screely-1614355834783.png -------------------------------------------------------------------------------- /uploads/screely-1614355858120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/screely-1614355858120.png -------------------------------------------------------------------------------- /uploads/screely-1614355873639.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enciphers-team/ThreadsApp/f4cbc257b57802ab4fd5cae2529d870ad030415d/uploads/screely-1614355873639.png -------------------------------------------------------------------------------- /util/delete_user.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/user'); 2 | const Post = require('../models/post'); 3 | const Like = require('../models/like'); 4 | const Comment = require('../models/comment'); 5 | const BookMark = require('../models/BookMark'); 6 | 7 | const deleteUser = async (id) => { 8 | try { 9 | // delete all comment of posts 10 | const posts = await Post.find({ $or: [{ user: id }, { sharedUser: id }] }); 11 | 12 | for (postResult of posts) { 13 | let deleteBookMark = await BookMark.deleteMany({ post: postResult._id }); 14 | 15 | for (commentId of postResult.comments) { 16 | const deleteCommentResult = await Comment.deleteOne({ _id: commentId }); 17 | } 18 | const deleteLikesResult = await Like.deleteMany({ 19 | postId: postResult._id, 20 | }); 21 | let deleteComment = await Comment.deleteMany({ post: postResult._id }); 22 | } 23 | 24 | const deleteCommentResult = await Comment.deleteMany({ user: id }); 25 | let deleteBookMark = await BookMark.deleteMany({ user: id }); 26 | 27 | const deletePostResult = await Post.deleteMany({ 28 | $or: [{ user: id }, { sharedUser: id }], 29 | }); 30 | const deleteLike = await Like.deleteMany({ user: id }); 31 | const deleteAccountResult = await User.deleteOne({ _id: id }); 32 | } catch (error) { 33 | console.log(error); 34 | } 35 | }; 36 | 37 | module.exports = deleteUser; 38 | -------------------------------------------------------------------------------- /util/helper_functions.js: -------------------------------------------------------------------------------- 1 | const Meta = require("html-metadata-parser"); 2 | 3 | const getPageMetadata = async (url) => { 4 | try { 5 | const result = await Meta.parser(url); 6 | 7 | console.log("get metadata response:", JSON.stringify(result, null, 3)); 8 | return { error: false, result }; 9 | } catch (error) { 10 | return { error: true, errorDetails: error }; 11 | } 12 | }; 13 | 14 | module.exports = getPageMetadata --------------------------------------------------------------------------------